根据上篇的实现思想,现在我们正式实现下内容。

需要实现的内容(基于之前的实现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,或者失效:

Spring Security5 SecurityFilterChain 动态白名单_html

正常使用token:

Spring Security5 SecurityFilterChain 动态白名单_css_02

 

Spring Security5 SecurityFilterChain 动态白名单_html_03

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