Spring Boot集成OneLogin SSO:JWT过期后的认证流程疑问
Great question! Let's break this down step by step based on how Spring Security and OneLogin SSO typically work together:
1. Handling JWT Expiration: Clear Context & Redirect to OneLogin
Yes, you absolutely should clear the Spring Security context and redirect users back to OneLogin for re-authentication when their JWT token expires. Here's why:
- JWT is stateless by design—once it's expired, there's no way to validate the user's identity locally anymore. The Security Context holds the user's authenticated state, so keeping an expired token's context around would lead to invalid auth checks.
- Redirecting to OneLogin leverages the SSO flow: if the user still has an active session with OneLogin, they'll be redirected back to your app automatically without re-entering credentials; if not, they'll go through OneLogin's login flow first.
To implement this, you can add a custom filter that checks for JWT expiration on each request:
@Component public class JwtExpirationCheckFilter extends OncePerRequestFilter { @Autowired private JwtTokenProvider jwtTokenProvider; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null && auth.getCredentials() instanceof String jwtToken) { if (jwtTokenProvider.isTokenExpired(jwtToken)) { // Clear the invalid auth context SecurityContextHolder.clearContext(); // Redirect to OneLogin's authorization endpoint (replace with your config) String redirectUrl = "https://your-onelogin-domain.com/oidc/auth" + "?client_id=YOUR_CLIENT_ID" + "&redirect_uri=YOUR_APP_REDIRECT_URI" + "&response_type=code" + "&scope=openid email profile"; response.sendRedirect(redirectUrl); return; } } filterChain.doFilter(request, response); } }
2. Syncing Security Context with OneLogin's Auth State
Spring Security's context is local to your app, so you need to actively sync it with OneLogin's authoritative session state. Here are the most practical approaches:
Option A: Use Refresh Tokens for Silent Token Renewal
If you requested the offline_access scope during the OneLogin OIDC flow, you'll get a refresh token alongside the JWT. When the JWT expires, you can use this refresh token to fetch a new JWT from OneLogin without redirecting the user:
public Optional<String> refreshJwtToken(String refreshToken) { // Call OneLogin's token endpoint to get a new access token HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("grant_type", "refresh_token"); params.add("refresh_token", refreshToken); params.add("client_id", "YOUR_CLIENT_ID"); params.add("client_secret", "YOUR_CLIENT_SECRET"); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers); ResponseEntity<OneLoginTokenResponse> response = restTemplate.exchange( "https://your-onelogin-domain.com/oidc/token", HttpMethod.POST, request, OneLoginTokenResponse.class ); if (response.getStatusCode().is2xxSuccessful()) { String newJwt = response.getBody().getAccessToken(); // Update the Security Context with the new token Authentication currentAuth = SecurityContextHolder.getContext().getAuthentication(); UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken( currentAuth.getPrincipal(), newJwt, currentAuth.getAuthorities() ); SecurityContextHolder.getContext().setAuthentication(newAuth); return Optional.of(newJwt); } return Optional.empty(); }
If the refresh token is also expired, fall back to clearing the context and redirecting to OneLogin.
Option B: Periodically Validate OneLogin Sessions
Set up a scheduled task to check if users' OneLogin sessions are still active. Use OneLogin's /api/2/sessions/me endpoint to validate the user's session:
@Component public class OneLoginSessionValidator { @Autowired private RestTemplate restTemplate; @Scheduled(fixedRate = 300000) // Check every 5 minutes public void validateActiveSessions() { // Get all authenticated users (adjust based on your session management) Collection<Authentication> activeAuths = getActiveAuthenticatedUsers(); for (Authentication auth : activeAuths) { String oneLoginAccessToken = (String) auth.getCredentials(); HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(oneLoginAccessToken); try { ResponseEntity<Void> response = restTemplate.exchange( "https://your-onelogin-domain.com/api/2/sessions/me", HttpMethod.GET, new HttpEntity<>(headers), Void.class ); if (!response.getStatusCode().is2xxSuccessful()) { SecurityContextHolder.clearContext(); } } catch (HttpClientErrorException e) { // Session is invalid/expired SecurityContextHolder.clearContext(); } } } // Helper method to fetch active authenticated users (use SessionRegistry if applicable) private Collection<Authentication> getActiveAuthenticatedUsers() { // Implement based on your app's session storage return new ArrayList<>(); } }
Option C: Handle OneLogin Global Logout
Configure OneLogin to send a logout callback to your app (via the "Logout URL" setting in your OneLogin app config). When this callback is triggered, clear the user's Security Context and invalidate their local session:
@RestController @RequestMapping("/auth") public class LogoutController { @PostMapping("/onelogin-logout") public void handleOneLoginLogout(HttpServletRequest request, HttpServletResponse response) throws IOException { SecurityContextHolder.clearContext(); request.getSession().invalidate(); response.sendRedirect("/"); // Redirect to your app's home page } }
Key Takeaways
- Always clear the Security Context when JWT/OneLogin sessions are invalid to avoid stale auth state.
- Use refresh tokens for a seamless user experience when JWTs expire (when possible).
- Sync your app's state with OneLogin's via periodic checks or global logout callbacks to maintain consistent auth status.
内容的提问来源于stack exchange,提问作者user12980137

