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

Spring Security: OAuth2 custom filter to validate password expiration error handling

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

  1. 用户尝试登录。
  2. 如果密码过期,用户会收到包含令牌的 header 响应。
  3. 用户使用该令牌(即 /password-change/{token})重定向到密码更改页面。
  4. 他提交了他的旧密码和新密码,它被更改了。
  5. 一些休息控制器通过该令牌检索用户 ID 并执行休息密码更改逻辑。
  6. 用户应该被重定向回他使用新密码登录的初始登录页面(如果他在密码更改后立即登录,他可以浏览安全页面,即使密码不会在由于某些异常等原因导致的背景)。

所以...我在用户详细信息中设置了密码过期的自定义标志,因为我无法使用 credentialsNonExpired,因为它在 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
    }
}
  1. 为什么我的过滤器放在 OAuth2AuthenticationProcessingFilter 之后,而链中没有 BasicAuthenticationFilter?我已经仔细研究了 Spring 安全和 OAuth2 文档和资源,但找不到正确的答案。
  2. 如果该用户的密码已过期,我的过滤器会生成一些随机字符串并将其保存,以便稍后在密码更改请求期间检索用户详细信息(至少应该是):

    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);
        }
    }
    }
    

我是否也必须撤销 OAuth 令牌?它在以后的请求中被重用,我不断得到最后的 userDetails object,因此我不断从我的过滤器中得到相同的响应。

  1. 这里是否适合进行所有这些验证?应该如何验证具体用户而不是 OAuth 客户端的密码?

好的,我想我通过在我的过滤器中注入 TokenStore 撤销访问令牌(我使用 BearerTokenExtractor 获取令牌值)来解决这个问题,这在这种情况下看起来很合乎逻辑。我仍然没有时间弄清楚为什么我的过滤器放在 OAuth2AuthenticationProcessingFilter 之后。