Spring 安全性:用于验证密码过期错误处理的 OAuth2 自定义过滤器
Spring Security: OAuth2 custom filter to validate password expiration error handling
我一直在尝试实施 OAuth2 密码过期过滤器,但我不确定这样做的正确方法是什么。思路如下:
- 用户尝试登录。
- 如果密码过期,用户会收到包含令牌的 header 响应。
- 用户使用该令牌(即 /password-change/{token})重定向到密码更改页面。
- 他提交了他的旧密码和新密码,它被更改了。
- 一些休息控制器通过该令牌检索用户 ID 并执行休息密码更改逻辑。
- 用户应该被重定向回他使用新密码登录的初始登录页面(如果他在密码更改后立即登录,他可以浏览安全页面,即使密码不会在由于某些异常等原因导致的背景)。
所以...我在用户详细信息中设置了密码过期的自定义标志,因为我无法使用 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
}
}
- 为什么我的过滤器放在 OAuth2AuthenticationProcessingFilter 之后,而链中没有 BasicAuthenticationFilter?我已经仔细研究了 Spring 安全和 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);
}
}
}
我是否也必须撤销 OAuth 令牌?它在以后的请求中被重用,我不断得到最后的 userDetails object,因此我不断从我的过滤器中得到相同的响应。
- 这里是否适合进行所有这些验证?应该如何验证具体用户而不是 OAuth 客户端的密码?
好的,我想我通过在我的过滤器中注入 TokenStore 撤销访问令牌(我使用 BearerTokenExtractor 获取令牌值)来解决这个问题,这在这种情况下看起来很合乎逻辑。我仍然没有时间弄清楚为什么我的过滤器放在 OAuth2AuthenticationProcessingFilter 之后。
我一直在尝试实施 OAuth2 密码过期过滤器,但我不确定这样做的正确方法是什么。思路如下:
- 用户尝试登录。
- 如果密码过期,用户会收到包含令牌的 header 响应。
- 用户使用该令牌(即 /password-change/{token})重定向到密码更改页面。
- 他提交了他的旧密码和新密码,它被更改了。
- 一些休息控制器通过该令牌检索用户 ID 并执行休息密码更改逻辑。
- 用户应该被重定向回他使用新密码登录的初始登录页面(如果他在密码更改后立即登录,他可以浏览安全页面,即使密码不会在由于某些异常等原因导致的背景)。
所以...我在用户详细信息中设置了密码过期的自定义标志,因为我无法使用 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
}
}
- 为什么我的过滤器放在 OAuth2AuthenticationProcessingFilter 之后,而链中没有 BasicAuthenticationFilter?我已经仔细研究了 Spring 安全和 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); } } }
我是否也必须撤销 OAuth 令牌?它在以后的请求中被重用,我不断得到最后的 userDetails object,因此我不断从我的过滤器中得到相同的响应。
- 这里是否适合进行所有这些验证?应该如何验证具体用户而不是 OAuth 客户端的密码?
好的,我想我通过在我的过滤器中注入 TokenStore 撤销访问令牌(我使用 BearerTokenExtractor 获取令牌值)来解决这个问题,这在这种情况下看起来很合乎逻辑。我仍然没有时间弄清楚为什么我的过滤器放在 OAuth2AuthenticationProcessingFilter 之后。