栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Security + jwt 思路和代码

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Security + jwt 思路和代码

目录

1.创建User实体类实现UserDetails并重写其方法

2.创建UserDetailsServiceImpl实现UserDetailsService

3.security配置中暴露一个登录接口并处理登录逻辑

- 暴露/user/login接口

- 处理登录逻辑

4.配置jwt过滤器

5.配置未登录就访问资源和权限不足的异常处理类

- 未登录就访问资源

- 权限不足

6.在security配置类中添加jwt过滤器和异常处理类

附录:

application.yml

JwtConfig

JwtUtils

RespBean

LoginUser

项目结构


1.创建User实体类实现UserDetails并重写其方法
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("sys_user")
public class User implements UserDetails {

    @TableId
    private Long id;
    private String username;
    private String password;
    private String nickname;
    private Integer sex;
    private Boolean status;
    private Integer isAdmin;
    private String avatar;
    private String address;
    private String openId;
    private String phone;
    private String email;

    @TableField(exist = false)
    private List roles;

    @Override
    public Collection getAuthorities() {
        List authorities = new ArrayList<>();
        for (Role role : roles) {
            if (role.getCode().startsWith("ROLE_")) {
                authorities.add(new SimpleGrantedAuthority(role.getCode()));
            }
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode()));
        }
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return status;
    }
}

2.创建UserDetailsServiceImpl实现UserDetailsService
@Service
@Transactional
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if (ObjectUtils.isEmpty(user)){
            throw new UsernameNotFoundException("用户名或密码不存在");
        }
        return user;
    }
}

3.security配置中暴露一个登录接口并处理登录逻辑

  - 暴露/user/login接口
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //- 放行暴露的登录接口
                .antMatchers("/user/login").anonymous()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
    }
}

- 处理登录逻辑
@Service
@Transactional
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private JwtConfig jwtConfig;

    @Override
    public RespBean login(LoginUser loginUser) {
        log.info("1.开始登录");
        UserDetails userDetails = userDetailsService.loadUserByUsername(loginUser.getUsername());
        log.info("2.判断密码是否正确");
        if (!passwordEncoder.matches(loginUser.getPassword(),userDetails.getPassword())){
            return RespBean.fail("账号或密码错误,请重新输入");
        }
        log.info("3.判断账号是否禁用");
        if (!userDetails.isEnabled()){
            return RespBean.fail("账号已禁用,请联系管理员");
        }
        log.info("4.登录成功,将用户信息保存到SecurityContextHolder");
        SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities()));
        log.info("5.生成jwt并将tokenHead和token返回给前端");
        String token = jwtUtils.generatorToken(userDetails);
        Map map = new HashMap<>(2);
        map.put("tokenHead",jwtConfig.getTokenHead());
        map.put("token",token);
        return RespBean.success("登录成功",map);
    }
}

4.配置jwt过滤器
@Component
@Slf4j
public class JwtFilter extends oncePerRequestFilter {

    @Autowired
    private JwtConfig jwtConfig;

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws ServletException, IOException {
        log.info("1.从req中获取header");
        String header = req.getHeader(jwtConfig.getTokenHeader());

        if (StringUtils.hasText(header) && header.startsWith(jwtConfig.getTokenHead())) {
            log.info("2.获取token");
            String token = header.substring(jwtConfig.getTokenHead().length());
            log.info("3.判断token是否过期");
            if (!jwtUtils.isExpired(token)) {
                log.info("4.从token中获取用户名并从SecurityContextHolder中获取authentication");
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                //- 如果是非法token则获取不到username,会抛出异常并被OncePerRequestFilter的捕获,从而由于没有登录记录,返回401
                String username = jwtUtils.getUsernameFromToken(token);

                if (StringUtils.hasText(username) && ObjectUtils.isEmpty(authentication)) {
                    log.info("token中获取到的用户名不为空,但是没有登录记录,则先登录处理");
                    log.info("4.1 登录");
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                    log.info("4.2 将用户信息保存到SecurityContextHolder中");
                    SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()));
                    log.info("4.3 登录成功--->{}", SecurityContextHolder.getContext().getAuthentication());

                }
            }
        }
        //- 放行
        chain.doFilter(req, resp);
    }
}

5.配置未登录就访问资源和权限不足的异常处理类

- 未登录就访问资源
@Configuration
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
        resp.setContentType("application/json;charset=utf-8");
        resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter pw = resp.getWriter();
        pw.write(new ObjectMapper().writevalueAsString(RespBean.fail("请先登录")));
        pw.flush();
        pw.close();
    }
}

- 权限不足
@Configuration
public class MyAccessDenied implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse resp, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        resp.setContentType("application/json;charset=utf-8");
        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
        PrintWriter pw = resp.getWriter();
        pw.write(new ObjectMapper().writevalueAsString(RespBean.fail("权限不足,请联系管理员")));
        pw.flush();
        pw.close();
    }
}

6.在security配置类中添加jwt过滤器和异常处理类
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtFilter jwtFilter;

    @Autowired
    private MyAuthenticationEntryPoint myAuthenticationEntryPoint;

    @Autowired
    private MyAccessDenied myAccessDenied;

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //- 放行暴露的登录接口
                .antMatchers("/user/login").anonymous()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();

        //- 各种异常处理
        http.exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint).accessDeniedHandler(myAccessDenied);

        //- 添加过滤器
        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

附录:

 application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/sport?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

jwt:
  tokenHeader: Authorization
  secret: asfasfsd
  expiration: 1800
  tokenHead: 'Barber '
mybatis-plus:
  mapper-locations: classpath:mappers
    public String generatorToken(UserDetails userDetails) {
        Map map = new HashMap<>(2);
        map.put("username", userDetails.getUsername());
        map.put("created", new Date());
        return generatorJwt(map);
    }


    
    public String generatorJwt(Map map) {
        return Jwts.builder()
                .setClaims(map)
                .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
                .setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getExpiration() * 1000))
                .compact();
    }


    
    public Claims getTokenBody(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(jwtConfig.getSecret())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    
    public String getUsernameFromToken(String token) {
        return (String) getTokenBody(token).get("username");
    }



    public boolean isExpired(String token){
        return getTokenBody(token).getExpiration().before(new Date());
    }

    public String refreshTokenExpiration(String token){
        Claims claims = getTokenBody(token);
        claims.setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getExpiration() * 1000));
        return generatorJwt(claims);
    }

}

RespBean
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class RespBean {

    public boolean flag;

    private String msg;

    private Object data;

    public static RespBean success(String msg,Object o){
        return new RespBean(true,msg,o);
    }

    public static RespBean fail(String msg){
        return new RespBean(false,msg,null);
    }

}

LoginUser
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class LoginUser {

    private String username;

    private String password;
}

项目结构

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/769827.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号