网关鉴权失败需主动抛出RuntimeException(如AccessForbiddenException)并用ErrorWebExceptionHandler或GlobalFilter统一返回403。推荐自定义异常类+响应式全局处理器,避免使用Spring Security专用异常,确保异常能被WebFlux错误机制感知并异步写出响应。

在网关(如 Spring Cloud Gateway)中,鉴权失败通常不会抛出标准 Java 异常(如 RuntimeException),而是通过响应状态码(如 403)或自定义错误流程体现。但若你使用了自定义鉴权逻辑(例如在 GlobalFilter 中手动校验 token、权限),并在校验不通过时主动抛出异常(如 AccessDeniedException 或自定义异常),就可以统一捕获并返回 403。
1. 自定义鉴权异常类(推荐)
定义一个运行时异常,明确表示“访问被拒绝”:
public class AccessForbiddenException extends RuntimeException {
public AccessForbiddenException(String message) {
super(message);
}
}在鉴权逻辑中主动抛出:
if (!hasPermission(token, requiredRole)) {
throw new AccessForbiddenException("Insufficient permissions");
}2. 全局异常处理器(针对 WebFlux 网关)
Spring Cloud Gateway 基于 WebFlux(响应式),不能用传统的 @ControllerAdvice 处理全局异常。需使用 ErrorWebExceptionHandler 实现自定义错误处理:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 创建 Bean 实现
ErrorWebExceptionHandler - 重写
handle方法,匹配异常类型 - 设置响应状态为
HttpStatus.FORBIDDEN(403) - 写入 JSON 错误体(如
{"code":403,"msg":"Forbidden"})
@Bean
@Order(-2) // 确保优先级高于默认处理器
public ErrorWebExceptionHandler errorWebExceptionHandler(
ObjectMapper objectMapper,
Resources resources,
ServerCodecConfigurer configurer,
HttpMessageWriters writers) {
<pre class="brush:php;toolbar:false;">return new AbstractErrorWebExceptionHandler(
new ErrorAttributes() { /* 可选:提供错误属性 */ },
new ResourceProperties(),
resources) {
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> errorProperties = getErrorAttributes(request, false);
Throwable error = getError(request);
if (error instanceof AccessForbiddenException) {
return ServerResponse.status(HttpStatus.FORBIDDEN)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 403, "msg", error.getMessage()));
}
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("code", 500, "msg", "Internal Error"));
}
};}
3. 替代方案:在 GlobalFilter 中直接响应(更轻量)
若不想引入复杂异常处理器,可在鉴权 GlobalFilter 中直接终止链并写响应:
- 校验失败时调用
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN) - 写入 JSON body(需用
DataBuffer) - 返回
Mono.empty()阻止后续过滤器执行
return Mono.just(exchange)
.filterWhen(ex -> {
String token = extractToken(ex);
return Mono.just(isValidAndAuthorized(token));
})
.switchIfEmpty(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
<pre class="brush:php;toolbar:false;"> String body = "{\"code\":403,\"msg\":\"Access denied\"}";
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
response.writeWith(Mono.just(buffer));
}))
.then(Mono.empty());</code>4. 注意事项
- 不要在网关中抛出
AuthenticationException 或 <code>AccessDeniedException(Spring Security 的异常),它们默认由 Security 过滤器链处理,在 Gateway 中未启用 Security 时无效 - 确保自定义异常是
RuntimeException子类,否则 WebFlux 不会自动传播 - 若集成 Spring Security for Gateway,应使用
ServerHttpSecurity配置authorizeExchange+authenticationManager,失败时自动返回 401/403,无需手动捕获 - 日志中建议记录异常堆栈和请求 ID,便于排查权限问题
不复杂但容易忽略:关键不在“捕获异常”,而在于让异常能被网关的响应式错误机制感知到,并以非阻塞方式写出 403 响应。

















