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
相关产品推荐
相关产品推荐

