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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 09:45:41