Spring Cloud Gateway OAuth2登录时XHR请求302改401的实现方案
解决方案:让AJAX请求未认证时返回401而非302重定向
针对你的Spring Cloud Gateway(WebFlux)+ OAuth2 + Keycloak场景,要区分浏览器请求和AJAX请求的未认证处理逻辑,核心是自定义认证入口点(AuthenticationEntryPoint),根据请求类型返回不同响应:
1. 自定义SPA专属认证入口点
创建一个实现ServerAuthenticationEntryPoint的类,用来判断请求是否为AJAX/JSON请求,分别处理:
@Component public class SpaAuthenticationEntryPoint implements ServerAuthenticationEntryPoint { // 默认的重定向入口点,指向Keycloak授权路径 private final ServerAuthenticationEntryPoint delegate = new RedirectServerAuthenticationEntryPoint("/oauth2/authorization/keycloak"); @Override public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) { // 判断是否为AJAX请求:检查X-Requested-With头 boolean isXhrRequest = exchange.getRequest().getHeaders().containsKey("X-Requested-With") && "XMLHttpRequest".equals(exchange.getRequest().getHeaders().getFirst("X-Requested-With")); // 或者判断是否期望JSON响应(适配更多场景) boolean expectsJson = exchange.getRequest().getHeaders().getAccept().stream() .anyMatch(MediaType.APPLICATION_JSON::isCompatibleWith); if (isXhrRequest || expectsJson) { // AJAX/JSON请求返回401 Unauthorized exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } // 普通浏览器请求走默认重定向逻辑 return delegate.commence(exchange, ex); } }
2. 修改Spring Security配置
将自定义的认证入口点注入到Security配置中,替换默认的异常处理逻辑:
@Configuration @EnableWebFluxSecurity public class SecurityConfiguration { private final SpaAuthenticationEntryPoint spaAuthenticationEntryPoint; // 构造注入自定义入口点 public SecurityConfiguration(SpaAuthenticationEntryPoint spaAuthenticationEntryPoint) { this.spaAuthenticationEntryPoint = spaAuthenticationEntryPoint; } @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.authorizeExchange(exchanges -> exchanges.anyExchange().authenticated()) .oauth2Login(withDefaults()) .csrf().disable() // 配置异常处理,使用自定义入口点 .exceptionHandling(exceptionConfig -> exceptionConfig.authenticationEntryPoint(spaAuthenticationEntryPoint) ); return http.build(); } }
3. (可选)Angular端配置HTTP拦截器
如果你的Angular请求默认没有携带X-Requested-With头,可以添加一个HTTP拦截器自动注入该头,确保网关能识别AJAX请求:
// xhr.interceptor.ts import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http'; @Injectable() export class XhrInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler) { const modifiedReq = req.clone({ headers: req.headers.set('X-Requested-With', 'XMLHttpRequest') }); return next.handle(modifiedReq); } }
在AppModule中注册拦截器:
// app.module.ts import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { XhrInterceptor } from './xhr.interceptor'; @NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor, multi: true } ] }) export class AppModule { }
关键说明
- 自定义入口点中判断请求类型的逻辑可以根据你的实际需求调整,比如只检查
X-Requested-With或者只检查Accept头。 RedirectServerAuthenticationEntryPoint的路径/oauth2/authorization/keycloak中的keycloak是你在Spring配置中注册的OAuth2客户端ID,如果你的客户端ID不同,需要替换成对应值。- 该方案保留了浏览器请求的重定向登录逻辑,同时让AJAX请求收到明确的401状态码,便于Angular端处理未认证场景(比如跳转到登录页、弹出提示等)。
内容的提问来源于stack exchange,提问作者benblan
相关产品推荐
相关产品推荐

