Java Spring安全性:用于验证密码过期错误处理的OAuth2自定义筛选器

Java Spring安全性:用于验证密码过期错误处理的OAuth2自定义筛选器,java,spring,spring-security,spring-boot,spring-security-oauth2,Java,Spring,Spring Security,Spring Boot,Spring Security Oauth2,我一直在尝试实现OAuth2密码过期过滤器,我不确定正确的方法是什么。其思路如下: 用户尝试登录 如果密码过期,用户将获得包含令牌的标头响应 用户get使用该令牌(即/password change/{token})重定向到密码更改页面 他提交了他的新旧密码,密码被更改了 某些rest控制器通过该令牌检索用户id,并执行rest密码更改逻辑 应将用户重定向回初始登录页面,在该页面上他使用新密码登录(如果他在密码更改后立即登录,即使由于某些异常等原因密码不会在后台更改,他也可以在安全页面中导航)

我一直在尝试实现OAuth2密码过期过滤器,我不确定正确的方法是什么。其思路如下:

  • 用户尝试登录
  • 如果密码过期,用户将获得包含令牌的标头响应
  • 用户get使用该令牌(即/password change/{token})重定向到密码更改页面
  • 他提交了他的新旧密码,密码被更改了
  • 某些rest控制器通过该令牌检索用户id,并执行rest密码更改逻辑
  • 应将用户重定向回初始登录页面,在该页面上他使用新密码登录(如果他在密码更改后立即登录,即使由于某些异常等原因密码不会在后台更改,他也可以在安全页面中导航)
  • 所以。。。我在用户详细信息中为密码过期设置了一个自定义标志,因为我不能使用CredentialsNoneExpired,因为它在DaoAuthenticationProvider中得到验证,并作为异常抛出,该异常作为InvalidGrantException处理,它没有给我太多控制。我发现为了在身份验证后立即访问用户详细信息,我的过滤器应该位于内部Spring安全过滤器链中,放置在OAuth2AuthenticationProcessingFilter之后:

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
    
            ...
    
            http.addFilterAfter(new PasswordExpirationFilter(), BasicAuthenticationFilter.class
        }
    }
    
  • 为什么我的筛选器放在OAuth2AuthenticationProcessingFilter之后,而链中没有基本的身份验证筛选器?我已经翻阅了SpringSecurity和OAuth2文档和源代码,但找不到正确的答案
  • 如果该用户的密码已过期,我的筛选器将生成一些随机字符串,并将其保存,以便稍后在密码更改请求期间检索用户详细信息(至少应该是):

  • 我也必须撤销OAuth令牌吗?它在以后的请求中被重用,我不断得到最后一个userDetails对象,因此我不断从我的过滤器中得到相同的响应

  • 这是进行所有验证的正确地点吗?应该如何验证具体用户而不是OAuth客户端的密码

  • 好的,我想我解决了这个问题,通过在我的过滤器中注入TokenStore(我使用BearerTokenExtractor来获取令牌值)来撤销访问令牌,这在这种情况下似乎非常合理。我还没来得及弄清楚,为什么我的过滤器放在OAuth2AuthenticationProcessingFilter之后。

    噢,我认为更合适的方法是在获得oauth2令牌之前进行整个过期密码验证。我只需要在已经建立的身份验证机制上快速完成这项工作。
    public class PasswordExpirationFilter extends OncePerRequestFilter implements Filter, InitializingBean {
    
    private static final String TOKEN_HEADER = ...;
    private ExpiredPasswordRepository repo; // gets set in a constructor and is basically holding a concurrent map of tokens
    
    ...
    
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
        UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    
        if (details.isPasswordExpired) {
            String uuid = UUID.randomUUID().toString();
            repo.push(uuid, details.getId());
    
            SecurityContextHolder.clearContext();
            SecurityContextHolder.getContext().setAuthentication(null);
            request.getSession(false).invalidate(); // don't create a new session
            response.addHeader(TOKEN_HEADER, uuid);
            response.sendError(HttpStatus.SC_PRECONDITION_FAILED, "Credentials have expired");
        } else {
            filterChain.doFilter(request, response);
        }
    }
    }