我有一个配置为 Spring Security OAuth2 客户端的 Spring Boot 应用程序。我使用 Keycloak 作为我的 OIDC 提供商。我从 Spring Boot 应用程序中提供编译后的 Angular 应用程序。 Angular 应用程序每秒都会向 Spring Boot 应用程序中的 REST API 发出请求。
当用户在浏览器中的 localhost:4881 打开此应用程序时,他们将被重定向到 Keycloak 登录页面,成功登录后,他们将被重定向回 localhost:4881,其中呈现编译后的 Angular 应用程序。
当用户登录时,我看到在 Keycloak 中创建了两个会话,一个名为
Regular SSO
,另一个名为 Offline
。根据 SSO 空闲超时,Regular SSO
会话将被清除。但是,离线会话在 Keycloak 中保留的时间较长,但最终会从 Keycloak 中清除。
在 SSO 会话空闲/SSO 会话最大超时后,我无法将 UI 重定向到 Keycloak 登录页面。我的假设是 Keycloak 应该将此会话超时事件传达给 OAuth2 客户端,并且 OAuth2 客户端应该将 UI 重定向到 Keycloak 登录页面以处理所有后续请求。
如果我在这里遗漏了什么,有人可以告诉我吗?我没有成功找到该问题的相关文档。
我在领域级别设置了
SSO Session Idle
、SSO Session Max
和 Offline Session Settings
。
我也在客户端中设置了Admin URL
。
我创建了一个具有完全相同设置的示例应用程序here:https://github.com/sakethsusarla/keycloak-oauth2-client-ui
backend
:配置为 OAuth2 客户端的 Spring Boot 应用程序,通过 /api/currentTime
公开的 REST API 返回当前时间
frontend
:一个从后端获取当前时间的 Angular 应用程序
realm
:领域配置位于“keycloak-oauth2-client-ui ackend\src\main
esources\demo-realm.json"
登录的用户名和密码是
demo
和 demo
在了解到 Keycloak 在会话过期时不会启动反向通道注销后,我决定添加一个过滤器来检查令牌的有效性并在需要时使会话无效。
添加以下过滤器对我有用。我已使用
main
branch 中的更改更新了我的存储库。
@Component
public class TokenExpirationFilter extends OncePerRequestFilter {
private final OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository;
@Autowired
TokenExpirationFilter(OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository) {
this.oAuth2AuthorizedClientRepository = oAuth2AuthorizedClientRepository;
}
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof OAuth2AuthenticationToken token) {
final OAuth2AuthorizedClient client =
oAuth2AuthorizedClientRepository.loadAuthorizedClient(
token.getAuthorizedClientRegistrationId(),
authentication,
request);
final OAuth2AccessToken accessToken = client.getAccessToken();
if (accessToken.getExpiresAt() != null && accessToken.getExpiresAt().isBefore(Instant.now())) {
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
final HttpSession httpSession = request.getSession();
if (httpSession != null) {
httpSession.invalidate();
}
}
}
filterChain.doFilter(request, response);
}