根据上篇的实现思想,现在我们正式实现下内容。
需要实现的内容(基于之前的实现SpringSecurity 架子):
1. 修改 JwtAuthenticationSuccessHandler ,需要将上下文信息存入redis:
因为验证环节需要实现从redis获取上下文,设置为SecurityContextHolder。
2. 自定义实现JwtAuthorFilter,拦截所有请求,对token进行验证,验证内容:是否携带token,是 否有效等(根据需求实现):进行token验证,无效token或者无token将直接进入后续过滤器 中,但是将无法获取SecurityContextHolder上下文内容,在关闭session管理的情况下,授权认 证失败。
3. JwtAuthorFilter加入过滤链。
实现:
1. 修改 JwtAuthenticationSuccessHandler
/**
* @Author: 一只会飞的猪
* @Date: 2021/9/11 18:50
* @Version 1.0
* springsecurity 自定义认证成功handler
**/
@Component
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private static final String REDIS_KEY = "USER_LOGIN:";
@Autowired
RedisCache redisCache;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
try {
Map<String,Object> map = new HashMap <>( );
httpServletResponse.setContentType("application/json;charset=utf-8");
LoginUser user = (LoginUser) authentication.getPrincipal();
SysUser sysUser = user.getUserInfo();
// 将当前用户信息存入redis,key为 REDIS_KEY + userid 记录用户登录情况,以及后续验证失效时间5分钟
redisCache.setCacheObject( REDIS_KEY+sysUser.getId(),user,300, TimeUnit.SECONDS);
String token = JwtTokenUtils.createToken( String.valueOf( sysUser.getId() ), false );
map.put( "token",token );
map.put( "userinfo",sysUser );
map.put( "code",200 );
httpServletResponse.getWriter().write(JSONObject.toJSONString(map));
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. 自定义实现JwtAuthorFilter
/**
* @Author: 一只会飞的猪
* @Date: 2021/9/14 10:07
* @Version 1.0
**/
@Component
public class JwtAuthorFilter extends OncePerRequestFilter {
private static final String REDIS_KEY = "USER_LOGIN:";
@Autowired
RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
try {
// 获取请求头传递过来的token数据
String token = TokenUtils.getToken( httpServletRequest );
if (token != null && !"".equals( token )) {
// 验证token是否有效
boolean expiration = JwtTokenUtils.isExpiration( token );
if(expiration){
// 过期了,拦截访问
filterChain.doFilter( httpServletRequest, httpServletResponse );
return;
}
String userId = JwtTokenUtils.getUserID( token );
// 通过userID获取redis中的缓存信息
LoginUser loginUser = redisCache.getCacheObject( REDIS_KEY + userId );
if (loginUser != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 刷新令牌
redisCache.setCacheObject(REDIS_KEY + userId, loginUser, 300, TimeUnit.SECONDS);
// 将从redis获取到的用户信息set到上下文中
LoginJwtToken loginJwtToken = new LoginJwtToken( loginUser.getAuthorities(), loginUser, loginUser.getPassword() );
loginJwtToken.setDetails( new WebAuthenticationDetailsSource().buildDetails( httpServletRequest ) );
SecurityContextHolder.getContext().setAuthentication( loginJwtToken );
}
}
}catch (MalformedJwtException e){
filterChain.doFilter( httpServletRequest, httpServletResponse );
return;
}
// 如果token为空直接下一步过滤器,此时上线文中无用户信息,所有在后续认证环节失败
filterChain.doFilter( httpServletRequest, httpServletResponse );
}
}
3. JwtAuthorFilter加入过滤链
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
// 关闭security session管理,意味着用户认证之后信息不会存储到session中,下面自定义了token过滤器处理,使用redis实现缓存
http.sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS );
http.authorizeRequests().antMatchers( "/**/test" ).permitAll();
http.authorizeRequests().antMatchers( "/**/login" ).permitAll()
.antMatchers( "/system/user/list" ).authenticated();
// 设置用于认证的管理器
jwtLoginFilter.setAuthenticationManager( this.authenticationManagerBean() );
// 自定义的认证过滤器加入filter链
http.addFilterAt( jwtLoginFilter,UsernamePasswordAuthenticationFilter.class );
// 自定义认证成功Handler加入过滤器
jwtLoginFilter.setAuthenticationSuccessHandler( jwtAuthenticationSuccessHandler );
// 自定义认证失败Handler加入过滤器
jwtLoginFilter.setAuthenticationFailureHandler( jwtAuthenticationFailureHandler );
// 自定义的Provider 加入认证管理器
http.authenticationProvider( jwtAuthProvider );
// 自定义验证失败点
http.exceptionHandling().authenticationEntryPoint( myAuthenticationEntryPoint );
// 加入token验证过滤器
http.addFilterBefore( jwtAuthorFilter,JwtLoginFilter.class );
}
结果截图:
不适用token,或者失效:

正常使用token:


springsecurity 提供的了完善的过滤链来处理认证和授权,保障服务安全。针对不同企业不同需要,其同样也有高扩展性,完全可以根据各自企业的需求实现需要的拓展内容。 比如基于OAuth2的SSO单点登录的实现等等。