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

Spring Security集成Entra SSO微服务架构本地调试问题求助

问题:Spring Cloud Gateway 集成 Entra SSO 后认证重定向至网关而非前端WebApp

目标流程

  • 用户访问主页,因Cookie无JWT被重定向到登录页
  • 用户点击“SSO”按钮,触发GET /auth/login/oauth2/code/azure,由认证服务重定向至微软账号页面
  • 用户选择账号完成Entra认证
  • 认证服务通过证书生成自定义细粒度角色JWT
  • JWT返回WebApp并存入Cookie,用于权限验证和RBAC

现有环境

  • Svelte UI:运行在3001端口
  • Spring Cloud Gateway:运行在8080端口
  • Spring Security认证服务:运行在9000端口
  • 多个后端Spring Boot微服务
  • Azure重定向URI:http://localhost:8080/login/oauth2/code/azure

认证服务application.yml配置

server:
  port: 9000
  shutdown: graceful
  forward-headers-strategy: native

spring:
  application:
    name: authz
    version: 0.0.3
  cloud:
    azure:
      active-directory:
        enabled: true
        redirect-uri: ${AZURE_REDIRECT_URI:http://localhost:8080/login/oauth2/code/azure}
        credential:
          client-id: ${AZURE_CLIENT_ID}
          client-secret: ${AZURE_CLIENT_SECRET}
        profile:
          tenant-id: ${AZURE_TENANT_ID}
        authorization-clients:
          azure:
            scopes: openid, profile, email
            authorization-grant-type: authorization_code
  security:
    oauth2:
      client:
        registration:
          azure:
            redirect-uri: ${AZURE_REDIRECT_URI:http://localhost:8080/login/oauth2/code/azure}
            client-id: ${AZURE_CLIENT_ID}
            client-secret: ${AZURE_CLIENT_SECRET}
    session:
      store-type: hash_map

eureka:
  client:
    service-url:
      defaultZone: ${EUREKA_URL:http://localhost:8761/eureka/}
  instance:
    prefer-ip-address: true
    ip-address: ${HOST_IP:127.0.0.1}
    hostname: ${HOSTNAME:localhost}
    instance-id: ${spring.application.name}:${HOST_IP:127.0.0.1}:${server.port}

AuthController代码

@RequestMapping("/auth")
public class AuthController {

    private final JWTUtil jwtUtil;
    private static final String CALLBACK_URL = "/oauth2/authorization/azure";

    @Value("${jwt.expiration}")
    private long EXPIRATION_MILLIS;

    public AuthController(JWTUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @PostMapping("/login")
    public ResponseEntity<Void> login(HttpServletRequest request, HttpServletResponse response) {
        // Ensure session is created
        request.getSession(true);

        // Set JSESSIONID cookie
        response.addCookie(new jakarta.servlet.http.Cookie("JSESSIONID", request.getSession().getId()));

        URI redirectUri = URI.create(CALLBACK_URL);
        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(redirectUri);
        return new ResponseEntity<>(headers, HttpStatus.FOUND);
    }

    @GetMapping("/login/oauth2/code")
    public ResponseEntity<AuthResponse> handleOAuth2Callback(
            @AuthenticationPrincipal OAuth2User oAuth2User,
            @RequestParam(value = "code", required = false) String code,
            HttpServletRequest request) {
        if (oAuth2User == null && code == null) {
            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
        }

        String email = oAuth2User != null ? oAuth2User.getAttribute("email") : null;
        if (email == null) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        String jwt = Jwts.builder()
                .setSubject(email)
                .setIssuer("Me")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MILLIS))
                .signWith(jwtUtil.getPrivateKey())
                .compact();

        AuthResponse authResponse = new AuthResponse(jwt);
        return new ResponseEntity<>(authResponse, HttpStatus.OK);
    }
}

当前问题

认证成功后自动重定向到localhost:8080(网关端口),但前端WebApp运行在3001端口,导致无法回到前端完成登录流程,现有代码冗余且无法解决该问题。


解决方案

1. 传递并存储前端回调地址

前端发起登录请求时携带自身回调地址,认证服务将其存入Session,认证完成后重定向回该地址并写入JWT Cookie。

前端登录请求调整

点击SSO按钮时,请求地址改为:POST /auth/login?redirectUri=http://localhost:3001/login-success

修改AuthController的login方法

@PostMapping("/login")
public ResponseEntity<Void> login(HttpServletRequest request, HttpServletResponse response,
                                  @RequestParam(value = "redirectUri", required = true) String redirectUri) {
    // 将前端回调地址存入Session
    request.getSession(true).setAttribute("FRONT_REDIRECT_URI", redirectUri);

    URI redirectUriToAzure = URI.create(CALLBACK_URL);
    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(redirectUriToAzure);
    return new ResponseEntity<>(headers, HttpStatus.FOUND);
}

修改回调处理方法

调整接口路径匹配Entra回调,生成JWT后重定向回前端:

@GetMapping("/login/oauth2/code/azure")
public ResponseEntity<Void> handleOAuth2Callback(
        @AuthenticationPrincipal OAuth2User oAuth2User,
        HttpServletRequest request, HttpServletResponse response) {
    if (oAuth2User == null) {
        return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
    }

    String email = oAuth2User.getAttribute("email");
    if (email == null) {
        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
    }

    // 生成自定义JWT
    String jwt = Jwts.builder()
            .setSubject(email)
            .setIssuer("Me")
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MILLIS))
            .signWith(jwtUtil.getPrivateKey())
            .compact();

    // 写入JWT Cookie,配置安全属性
    Cookie jwtCookie = new Cookie("AUTH_JWT", jwt);
    jwtCookie.setHttpOnly(true);
    jwtCookie.setSecure(false); // 生产环境改为true
    jwtCookie.setPath("/");
    jwtCookie.setMaxAge((int) (EXPIRATION_MILLIS / 1000));
    response.addCookie(jwtCookie);

    // 从Session取出前端回调地址,无则用默认地址
    String frontRedirectUri = (String) request.getSession().getAttribute("FRONT_REDIRECT_URI");
    if (frontRedirectUri == null) {
        frontRedirectUri = "http://localhost:3001";
    }

    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(URI.create(frontRedirectUri));
    return new ResponseEntity<>(headers, HttpStatus.FOUND);
}

2. 调整网关配置

确保网关路由/auth/**到认证服务,并配置跨域支持:

spring:
  cloud:
    gateway:
      routes:
        - id: auth-service
          uri: lb://authz
          predicates:
            - Path=/auth/**
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "http://localhost:3001"
            allowedMethods: "*"
            allowedHeaders: "*"
            allowCredentials: true

3. 同步Azure重定向URI

在Azure门户将重定向URI修改为:http://localhost:8080/auth/login/oauth2/code/azure,同时更新认证服务application.yml中的redirect-uri配置。

4. 前端处理登录成功逻辑

在/login-success页面读取Cookie中的AUTH_JWT,后续请求携带该Cookie完成权限验证。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 13:54:51