You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud Gateway集成SSO时JWT令牌重定向丢失问题排查

Hey there! Let's tackle your issues one by one—starting with the architecture questions, then diving into that tricky JWT handoff problem after login.

1. Should You Merge SSO and Gateway?

Short answer: Don't do it (even as a temporary fix). Your current architecture follows good microservice principles:

  • Separation of concerns: SSO handles user authentication and token generation; the gateway handles routing, authorization, and request filtering. Merging them would create a monolithic "god service" that's harder to maintain, scale, or update later.
  • Security: Keeping the firewall locked to only the gateway port is the right call—all external traffic goes through a single, controlled entry point. Internal services (like SSO) are protected from direct external access.
  • Flexibility: If you ever need to swap out the gateway (e.g., switch from Zuul to Spring Cloud Gateway) or update your SSO system (e.g., add OAuth2 support), keeping them separate makes those changes far easier.
2. Core Problem: JWT Missing in Gateway After Redirect

The root issue here is how browsers handle redirects: when you add the JWT to the response header in your SSO success handler, the browser won't automatically carry that header over to the new redirect request. Browsers only persist cookies (for matching domains) across requests, not arbitrary response headers.

Here are two solid solutions, ordered by security and best practice:

This is the standard approach for web apps—browsers will automatically send the cookie with every request to your gateway's domain.

Update SSO's JwtAuthenticationSuccessHandler:

Replace the response header logic with a secure cookie:

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
    String token = jwtTokenService.expiring(ImmutableMap.of(
            "email", auth.getName(),
            "authorities", auth.getAuthorities()
                    .stream()
                    .map(GrantedAuthority::getAuthority)
                    .collect(Collectors.joining(","))));

    // Create a secure, HttpOnly cookie for the JWT
    Cookie jwtCookie = new Cookie(jwtConfig.getHeader(), jwtConfig.getPrefix() + token);
    jwtCookie.setHttpOnly(true); // Prevents XSS attacks from stealing the token
    jwtCookie.setSecure(true); // Enable this in production (requires HTTPS)
    jwtCookie.setSameSite("Strict"); // Mitigates CSRF risks
    jwtCookie.setPath("/"); // Ensures the cookie is sent for all paths on your domain
    response.addCookie(jwtCookie);

    // Keep your existing redirect logic
    DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
    String redirectUrl = defaultSavedRequest != null ? defaultSavedRequest.getRedirectUrl() : "http://localhost:7070/web";
    getRedirectStrategy().sendRedirect(request, response, redirectUrl);
}

Update Gateway's JwtTokenAuthenticationFilter:

Modify the filter to check for the JWT in both the header and the cookie:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
    String token = null;

    // First try to get token from Authorization header
    String header = request.getHeader(jwtConfig.getHeader());
    if (header != null && header.startsWith(jwtConfig.getPrefix())) {
        token = header.replace(jwtConfig.getPrefix(), "");
    } 
    // If no header, check cookies
    else {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (jwtConfig.getHeader().equals(cookie.getName())) {
                    token = cookie.getValue().replace(jwtConfig.getPrefix(), "");
                    break;
                }
            }
        }
    }

    if (token == null) {
        chain.doFilter(request, response);
        return;
    }

    // Keep your existing JWT validation and authentication logic here...
}

Option 2: Append JWT as a Query Parameter (Temporary, Less Secure)

This works as a quick fix but is not recommended for production—URLs are often logged, which exposes the JWT to potential leaks.

Update SSO's Redirect Logic:

Add the JWT as a query parameter to the redirect URL:

String token = jwtTokenService.expiring(...); // Keep your token generation logic
String redirectUrl = defaultSavedRequest != null ? defaultSavedRequest.getRedirectUrl() : "http://localhost:7070/web";
// Append token to the redirect URL
redirectUrl += "?" + jwtConfig.getHeader() + "=" + URLEncoder.encode(jwtConfig.getPrefix() + token, StandardCharsets.UTF_8);
getRedirectStrategy().sendRedirect(request, response, redirectUrl);

Update Gateway's Filter:

Add logic to extract the token from the query parameter:

// Add this to your token retrieval section
else {
    String paramToken = request.getParameter(jwtConfig.getHeader());
    if (paramToken != null && paramToken.startsWith(jwtConfig.getPrefix())) {
        token = paramToken.replace(jwtConfig.getPrefix(), "");
    }
}
3. Other Code & Architecture Tweaks
  • SSO Session Conflict: You set SessionCreationPolicy.STATELESS but use request.getSession() to fetch the saved request—this will create a session anyway. If you want true statelessness, consider switching to OAuth2's Authorization Code Flow instead of form login. If you keep form login, you can remove the stateless session policy (since form login relies on sessions to track the original request).
  • Gateway Authentication Entry Point: Your current gateway config uses formLogin().loginPage("/sso/login"), which is intended for a gateway-hosted login page. Instead, use an authenticationEntryPoint to redirect unauthenticated requests to SSO:
    http.exceptionHandling()
        .authenticationEntryPoint((req, res, ex) -> {
            // Save the original request so SSO can redirect back after login
            HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
            requestCache.saveRequest(req, res);
            res.sendRedirect("http://localhost:8080/sso/login");
        });
    
  • AuthenticatedFilter Cleanup: Your Zuul filter has placeholder code (writeValueAsString("authenticationDto")). Make sure to replace this with the actual AuthenticationDto object once you've finalized it—this will pass user auth info to downstream services correctly.
4. Final Architecture Verdict

Your overall architecture is sound! The idea of routing all traffic through the gateway, using SSO for centralized authentication, and Eureka for service discovery aligns with microservice best practices. Stick with separating SSO and gateway—you'll thank yourself later when you need to scale or update either component.

内容的提问来源于stack exchange,提问作者Black0Jaguar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 08:07:32