带有 Spring Security 5 的 Spring Boot 2 可以配置为使用 openID 连接 ID 提供程序进行身份验证。 我设法仅通过配置 Spring Security 来设置我的项目 - 它可以与各种完美预配置的安全机制(例如缓解会话固定)很好地配合。
但是 Spring Security 似乎在令牌过期时不会自行刷新令牌(存储在会话中)。
有相关设置吗?还是我必须自己负责刷新?
更新:Spring Boot 2.1 已经发布,所以是时候重新审视这个问题了。我仍然不知道 accessToken 现在是否可以自动刷新,或者我是否必须为此编写代码......
根据文档,
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#webclient
当使用正确配置的 WebClient 时,如文档中所示,它将自动刷新。
Spring Security 将自动刷新过期的令牌(如果存在刷新令牌)
支持刷新令牌的功能矩阵也支持这一点。
https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix
Spring Security 5 上有一个较旧的博客,可以让您访问可以手动执行此操作的 Bean,
Authentication authentication =
SecurityContextHolder
.getContext()
.getAuthentication();
OAuth2AuthenticationToken oauthToken =
(OAuth2AuthenticationToken) authentication;
在 Spring 应用程序上下文中将自动配置一个 OAuth2AuthorizedClientService 作为 bean,因此您只需将其注入到您将使用它的任何地方。
OAuth2AuthorizedClient client =
clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
String refreshToken = client.getRefreshToken();
而且,现在找不到它,但我认为作为
OAuth2AuthorizedClientExchangeFilterFunction
的一部分,有进行刷新的调用。
根据https://github.com/spring-projects/spring-security/issues/6742似乎故意不刷新令牌:
ID 令牌通常带有到期日期。 RP 可能 依靠它来使 RP 会话过期。
春天没有。最后提到了两项增强功能,应该可以解决一些刷新问题 - 两者都仍然开放。
作为解决方法,我实现了一个 GenericFilterBean,它检查令牌并清除当前安全上下文中的身份验证。因此需要一个新的代币。
@Configuration
public class RefreshTokenFilterConfig {
@Bean
GenericFilterBean refreshTokenFilter(OAuth2AuthorizedClientService clientService) {
return new GenericFilterBean() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
OAuth2AuthorizedClient client =
clientService.loadAuthorizedClient(
token.getAuthorizedClientRegistrationId(),
token.getName());
OAuth2AccessToken accessToken = client.getAccessToken();
if (accessToken.getExpiresAt().isBefore(Instant.now())) {
SecurityContextHolder.getContext().setAuthentication(null);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
};
}
}
此外,我必须将过滤器添加到安全配置中:
@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurer(GenericFilterBean refreshTokenFilter) {
return new WebSecurityConfigurerAdapter() {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(refreshTokenFilter, AnonymousAuthenticationFilter.class)
在 2.2.7.RELEASE 版本中使用 spring-boot-starter-parent 和依赖项实现:
我很欣赏有关此解决方法的意见,因为我仍然不确定 Spring Boot 中是否真的需要这样的开销。
即使悬赏100点代表点也没有得到答案。所以我猜目前还没有实现使用 Spring Security 自动刷新访问令牌的机制。
一个有效的替代方案似乎是使用 spring boot keycloak 适配器,它能够刷新令牌。
以防有人去那里寻找新版本 Spring 的答案。目前我们有 Spring 6。我正在使用 oauth2Login() 功能通过 github、facebook 等社交网站登录。我想将用户会话绑定到访问令牌 expireAt 属性。如果过期,应刷新令牌并尝试延长会话,然后再强制用户再次登录。 Spring 在 oauth2Login() 功能中没有提供这种方式。但我配置了一个bean OAuth2AuthorizedClientManager 可以与 Spring 响应式的 oauth2Client() 功能一起使用。然而,在出现异常的情况下,手动刷新访问令牌并手动清除身份验证上下文可能是一种很酷的方法。
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager() {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository(), authorizedClientRepository());
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
和过滤器
public class ExpiredTokenFilter extends OncePerRequestFilter {
@Resource
private OAuth2AuthorizedClientManager authorizedClientManager;
@Resource
private OAuth2AuthorizedClientRepository authorizedClientRepository;
@Override
protected void doFilterInternal(
@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response,
@Nonnull FilterChain filterChain)
throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (Objects.nonNull(authentication) && authentication.getPrincipal() instanceof BitbarOidcUser) {
validateToken(authentication, request, response);
}
filterChain.doFilter(request, response);
}
private void validateToken(Authentication auth, HttpServletRequest request, HttpServletResponse response) {
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(MY_PROVIDER)
.principal(auth)
.attributes(attrs -> {
attrs.put(HttpServletRequest.class.getName(), request);
attrs.put(HttpServletResponse.class.getName(), response);
})
.build();
try {
authorizedClientManager.authorize(authorizeRequest);
} catch (ClientAuthorizationException e) {
SecurityContextHolder.getContext().setAuthentication(null);
}
}
}
记得在SecurityFilterChain中注册Filter
http.addFilterBefore(expiredTokenFilter, AbstractPreAuthenticatedProcessingFilter.class));