此次文章是上一篇文章进阶片,以Token的方式进行验证,Token的生成策略是JWT,如有不懂请看上一篇文章SpringBoot2.x+SpringSecurit(二)前后端分离
什么是JWT,为什么使用JWT?
- JWT是 Json Web Token 的缩写。它是基于 RFC 7519 标准定义的一种可以安全传输的小巧和 自包含的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。
- 使用 JWT 做权限验证,相比Session的优点是,Session 需要占用大量服务器内存,并且在多服务器时就会涉及到共享Session问题,而JWT无需存储在服务器,不占用服务器资源(也就是无状态的),用户在登录后拿到 Token 后,访问需要权限的请求时附上Token(一般设置在Http请求头)。
工作流程
- 输入用户名、密码,进行登录
- 服务器验证登录鉴权,如果用户合法,根据用户的信息生成JWT Token
- 服务器将该token以json形式返回(不一定要json形式)
- 前端得到token,储存起来
- 以后用户请求API时,在请求的header中加入 Authorization:
Bearer xxxx(token)。此处 注意token之前有一个7长度字符的Bearer 。 - 服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
1. 引入依赖
1 2 | //jwt compile 'io.jsonwebtoken:jjwt:0.9.0' |
2. Security 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled=true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private UserDetailsService userDetailsService; @Autowired private AuthSuccessHandler authSuccessHandler; @Autowired private AuthFailHandler authFailHandler; @Autowired private AuthAccessDeniedHandler authAccessDeniedHandler; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry=http.authorizeRequests(); for (String u:PermitUrl.permitUrls) { registry.antMatchers(u).permitAll(); } registry.and() .formLogin() .loginPage("/u/noLogin") .loginProcessingUrl("/login") .permitAll() //成功处理类 .successHandler(authSuccessHandler) //失败处理类 .failureHandler(authFailHandler) .and() .logout() .permitAll() .and() .authorizeRequests() //任何请求 .anyRequest() //都需要认证 .authenticated() .and() //关闭跨站请求防护 .csrf().disable() //前后端分离采用JWT 不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() //自定义权限拒绝处理类 .exceptionHandling().accessDeniedHandler(authAccessDeniedHandler) .and() //添加JWT过滤器 除/login其它请求都需经过此过滤器 .addFilter(new JwtAuthenticationFilter(authenticationManager())); } } |
这里不需要session储存了,以及重现验证机制
3. 成功配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @Slf4j @Component public class AuthSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { //获取登陆成功用户名 String username = ((UserDetails)authentication.getPrincipal()).getUsername(); log.info("登陆成功:{}",username); List<String> authorities=((UserDetails) authentication.getPrincipal()).getAuthorities().stream().map(a->new String(((GrantedAuthority) a).getAuthority())).collect(Collectors.toList()); //登陆成功生成JWT String token = Jwts.builder() //主题 放入用户名 .setSubject(username) //自定义属性 放入用户拥有权限 .claim(SecurityConstant.AUTHORITIES.getCode(), JSONObject.toJSONString(authorities)) //失效时间 7天 .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 *7)) //签名算法和密钥 .signWith(SignatureAlgorithm.HS512,SecurityConstant.JWT_SIGN_KEY.getCode()) .compact(); token = SecurityConstant.TOKEN_SPLIT.getCode() + token; String msg=JSON.toJSONString(ServerResponse.Success(token)); ServerResponse.out(response,msg); } } |
登陆成功后获取用户信息,以及权限,以JWT的HS512算法生成Token 然后以json返回给前端。
注意:如果你是JDK1.8以上需要加入compile 'javax.xml.bind:jaxb-api:2.3.0'
依赖
4. 失败配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Component @Slf4j public class AuthFailHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String msg=null; if (exception instanceof UsernameNotFoundException || exception instanceof BadCredentialsException) { msg="用户名或密码输入错误,登录失败!"; } else if (exception instanceof DisabledException) { msg="账户被禁用,登录失败,请联系管理员!"; } else { msg="登录失败!"; } msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),msg)); ServerResponse.out(response,msg); } } |
5. token校验类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | @Slf4j public class JwtAuthenticationFilter extends BasicAuthenticationFilter { public JwtAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String header = request.getHeader(SecurityConstant.HEADER.getCode()); if (header == null || !header.startsWith(SecurityConstant.TOKEN_SPLIT.getCode())) { chain.doFilter(request, response); return; } UsernamePasswordAuthenticationToken authentication = getAuthentication(request, response); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) { String token = request.getHeader(SecurityConstant.HEADER.getCode()); if (token!=null){ Claims claims = null; try { claims=Jwts.parser() .setSigningKey(SecurityConstant.JWT_SIGN_KEY.getCode()) .parseClaimsJws(token.replace(SecurityConstant.TOKEN_SPLIT.getCode(), "")) .getBody(); //获取用户名 String username = claims.getSubject(); //获取权限(角色) List<GrantedAuthority> authorities=null; String authority = claims.get(SecurityConstant.AUTHORITIES.getCode()).toString(); if(authority!=null){ List<String> list = JSONObject.parseArray(authority,String.class); authorities= list.stream().map(a->new SimpleGrantedAuthority(a)).collect(Collectors.toList()); } if(username!=null) { //此处password不能为null User user=new User(username,"",authorities); return new UsernamePasswordAuthenticationToken(user, null, authorities); } }catch (ExpiredJwtException e) { logger.error("Token已过期: {} " + e); String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"Token已过期")); ServerResponse.out(response,msg); } catch (UnsupportedJwtException e) { logger.error("Token格式错误: {} " + e); String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"Token格式错误")); ServerResponse.out(response,msg); } catch (MalformedJwtException e) { logger.error("Token没有被正确构造: {} " + e); String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"Token没有被正确构造")); ServerResponse.out(response,msg); } catch (SignatureException e) { logger.error("签名失败: {} " + e); String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"签名失败")); ServerResponse.out(response,msg); } catch (IllegalArgumentException e) { logger.error("非法参数异常: {} " + e); String msg=JSON.toJSONString(ServerResponse.Error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(),"非法参数异常")); ServerResponse.out(response,msg); } } return null; } } |
这里判断请求的header格式是否正确,如正确,解析Token获取用户信息
6. 测试
如果访问其他API
这里的值为你登录的Bearer +Token。
7. 总结
源码请看:源码
这里以JWT生成Token和储存Token,那么如何以redis储存呢?