Spring Security + jwt 思路和代码

6 篇文章 1 订阅
1 篇文章 0 订阅

目录

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<Role> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> 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<String ,Object> 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/*.xml

JwtConfig

@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {

    private String tokenHeader;

    private String secret;

    private Long expiration;

    private String tokenHead;


}

JwtUtils

@Component
public class JwtUtils {

    @Autowired
    private JwtConfig jwtConfig;


    /**
     * 根据用户名和创建时间生成token
     *
     * @param userDetails 用户信息
     * @return
     */
    public String generatorToken(UserDetails userDetails) {
        Map<String, Object> map = new HashMap<>(2);
        map.put("username", userDetails.getUsername());
        map.put("created", new Date());
        return generatorJwt(map);
    }


    /**
     * 根据荷载信息生成jwt
     *
     * @param map
     * @return
     */
    public String generatorJwt(Map<String, Object> map) {
        return Jwts.builder()
                .setClaims(map)
                .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
                .setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getExpiration() * 1000))
                .compact();
    }


    /**
     * 解析token并得到实体,若token不合法则返回null
     *
     * @param token
     * @return
     */
    public Claims getTokenBody(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(jwtConfig.getSecret())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 从token中得到用户名
     * @param token
     * @return
     */
    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;
}

项目结构

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值