美文网首页
Spring Security整合JWT(基于Spring Bo

Spring Security整合JWT(基于Spring Bo

作者: Exrick | 来源:发表于2019-04-25 13:07 被阅读0次

    JWT

    JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的
    官网:https://jwt.io

    • JSON Web Token由Header、Payload、Signature三部分组成,它们之间用圆点(.)连接。
      一个典型的JWT看起来是这个样子的:
      xxxxx.yyyyy.zzzzz
      分别对应:Header.Payload.Signature

    Header

    header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。

    例如:

    {
    "alg": "HS256",
    "typ": "JWT"
    }

    用Base64对这个JSON编码就得到JWT的第一部分

    Payload

    JWT的第二部分是payload,通常在这部分存入交互信息,XBoot中存入了用户名和用户权限(避免每次请求再次读取用户权限)以及token失效时间。
    例如:

    {
    "sub": "admin",
    "authorities": "["添加用户","ROLE_ADMIN"]",
    "exp": 1555554537
    }
    对payload进行Base64编码就得到JWT的第二部分

    注意:签名并不是加密,任何人都能看到JWT里的内容,除非它们是加密的,因此请勿放置明文敏感信息至JWT中

    Signature

    签名是用于验证消息在传递过程中有没有被更改,并且对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。
    例如:
    HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

    JWT缺点:JWT是无法撤销的,除非是达到了设定的过期时间,且刷新Token机制麻烦。解决方案:XBoot中可配置使用Redis记录,60分钟内(可配置)无请求自动失效,可随时管理token,详见代码

    Spring Security整合

    • 添加依赖
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- JWT -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    
    • Spring Security核心配置入口
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled=true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        ...
    
        @Autowired
        private AuthenticationSuccessHandler successHandler;
    
        @Autowired
        private AuthenticationFailHandler failHandler;
    
        @Autowired
        private RestAccessDeniedHandler accessDeniedHandler;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            ...
    
            registry.and()
                    ...
                    //成功处理类
                    .successHandler(successHandler)
                    //失败
                    .failureHandler(failHandler)
                    //关闭跨站请求防护
                    .csrf().disable()
                    //前后端分离采用JWT 不需要session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    //自定义权限拒绝处理类
                    .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
                    .and()
                    //添加JWT过滤器 除已配置的其它请求都需经过此过滤器
                    .addFilter(new JWTAuthenticationFilter(authenticationManager()));
        }
    }
    
    • JWT过滤器 认证处理类
    public class JWTAuthenticationFilter extends BasicAuthenticationFilter   {
    
        public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
            super(authenticationManager);
        }
    
        public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
            super(authenticationManager, authenticationEntryPoint);
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    
            String header = request.getHeader(SecurityConstant.HEADER);
            if(StrUtil.isBlank(header)){
                header = request.getParameter(SecurityConstant.HEADER);
            }
            Boolean notValid = StrUtil.isBlank(header) || (!tokenRedis && !header.startsWith(SecurityConstant.TOKEN_SPLIT));
            if (notValid) {
                chain.doFilter(request, response);
                return;
            }
            try {
                UsernamePasswordAuthenticationToken authentication = getAuthentication(header, response);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }catch (Exception e){
                e.toString();
            }
    
            chain.doFilter(request, response);
        }
    
        private UsernamePasswordAuthenticationToken getAuthentication(String header, HttpServletResponse response) {
    
            // 用户名
            String username = null;
            // 权限
            List<GrantedAuthority> authorities = new ArrayList<>();
                
            // JWT
            try {
                // 解析token
                Claims claims = Jwts.parser()
                       .setSigningKey(SecurityConstant.JWT_SIGN_KEY)
                        .parseClaimsJws(header.replace(SecurityConstant.TOKEN_SPLIT, ""))
                        .getBody();
    
                // 获取用户名
                username = claims.getSubject();
                String authority = claims.get(SecurityConstant.AUTHORITIES).toString();
                if(StrUtil.isNotBlank(authority)){
                    List<String> list = new Gson().fromJson(authority, new TypeToken<List<String>>(){}.getType());
                    for(String ga : list){
                         authorities.add(new SimpleGrantedAuthority(ga));
                    }
                }
           } catch (ExpiredJwtException e) {
                ResponseUtil.out(response, ResponseUtil.resultMap(false,401,"登录已失效,请重新登录"));
           } catch (Exception e){
                log.error(e.toString());
                ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"解析token错误"));
            }
    
            if(StrUtil.isNotBlank(username)) {
                // 踩坑提醒 此处password不能为null
                User principal = new User(username, "", authorities);
                return new UsernamePasswordAuthenticationToken(principal, null, authorities);
            }
            return null;
        }
    }
    
    • 成功处理类
    @Slf4j
    @Component
    public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    
        @Value("${xboot.tokenExpireTime}")
        private Integer tokenExpireTime;
    
        @Value("${xboot.saveLoginTime}")
        private Integer saveLoginTime;
    
        @Override
        @SystemLog(description = "登录系统", type = LogType.LOGIN)
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    
            //用户选择保存登录状态几天
            String saveLogin = request.getParameter(SecurityConstant.SAVE_LOGIN);
            ...
            String username = ((UserDetails)authentication.getPrincipal()).getUsername();
            List<GrantedAuthority> authorities = (List<GrantedAuthority>) ((UserDetails)authentication.getPrincipal()).getAuthorities();
            List<String> list = new ArrayList<>();
            for(GrantedAuthority g : authorities){
                list.add(g.getAuthority());
            }
            // 登陆成功生成token
            String token = SecurityConstant.TOKEN_SPLIT + Jwts.builder()
                            //主题 放入用户名
                            .setSubject(username)
                            //自定义属性 放入用户拥有请求权限
                            .claim(SecurityConstant.AUTHORITIES, new Gson().toJson(list))
                            //失效时间
                            .setExpiration(new Date(System.currentTimeMillis() + tokenExpireTime * 60 * 1000))
                            //签名算法和密钥
                            .signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY)
                            .compact();
    
            ResponseUtil.out(response, ResponseUtil.resultMap(true,200,"登录成功", token));
        }
    }
    
    • 失败处理类
    @Slf4j
    @Component
    public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
    
        ...
    
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
    
            ...
            if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
                ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"用户名或密码错误"));
            } else if (e instanceof DisabledException) {
                ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"账户被禁用,请联系管理员"));
            } else if (e instanceof LoginFailLimitException){
                ResponseUtil.out(response, ResponseUtil.resultMap(false,500,((LoginFailLimitException) e).getMsg()));
            } else {
                ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"登录失败,其他内部错误"));
            }
        }
    }
    
    • 自定义权限拒绝处理类
    @Component
    public class RestAccessDeniedHandler implements AccessDeniedHandler {
    
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
                throws IOException, ServletException {
    
            ResponseUtil.out(response, ResponseUtil.resultMap(false,403,"抱歉,您没有访问权限"));
        }
    
    }
    

    相关文章

      网友评论

          本文标题:Spring Security整合JWT(基于Spring Bo

          本文链接:https://www.haomeiwen.com/subject/vaiagqtx.html