网关鉴权:统一 Token 校验与白名单

登录校验如果每个服务各做一遍,规则会越改越乱、漏网越来越多。网关是所有外部流量的必经入口,把鉴权放在网关做一次统一收口,后端服务就能专注于业务。本章实现"白名单放行 + Token 校验 + 用户上下文透传"的网关鉴权骨架。

前提认知:信任边界

网关鉴权解决的是"外部请求有没有资格进来"。但要清醒:服务之间的内部调用不经过网关(Feign 直连),所以"网关校验过"不等于"请求可信"。标准做法分两层:网关对公网请求做完整鉴权;服务间调用使用内部凭证或至少不盲目信任上游传入的业务头(第 22 章会讲头防伪)。若所有服务都强制走网关(严格拓扑),则另当别论。

两种 Token 校验风格

一是 JWT 自校验:网关用本地保存的公钥解析并验签 Token,无状态、不依赖认证中心在线,适合高并发网关;缺点是 Token 撤销不实时,依赖过期时间。二是调用认证中心校验:每次请求回调认证服务确认有效性,可实时踢人,但增加一次 RPC 与可用性依赖。常见组合:网关验 JWT 签名与有效期,拉黑/注销等强失效场景走短过期时间或 Redis 黑名单。具体选用哪种与你的认证体系绑定,本示例用校验服务接口抽象表示,不绑定实现。

白名单:先定义"不用登录"的路径

登录接口、验证码、回调、健康检查等路径天然无需 Token。把白名单做成可配置项(放 Nacos 即可动态调整,第 11 章):

app:
  auth:
    whitelist:
      - /api/auth/login
      - /api/auth/captcha
      - /actuator/**

用一个 @ConfigurationProperties 类绑定该列表,网关过滤器按前缀匹配放行。

核心:鉴权 GlobalFilter

自定义全局过滤器,order 设为很小值保证最先执行:

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    private final AuthWhitelist whitelist;
    private final TokenValidator tokenValidator;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        if (whitelist.matches(request.getURI().getPath())) {
            return chain.filter(exchange);          // 白名单直接放行
        }
        String token = resolveToken(request);        // 依次取 Header/Cookie/Query
        TokenClaims claims = tokenValidator.validate(token);  // 无效则返回 null
        if (claims == null) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();       // 401 直接结束
        }
        // 覆盖式写入用户头,防止调用方伪造(先删后写)
        ServerHttpRequest mutated = request.mutate()
                .headers(h -> h.remove("X-User-Id"))
                .header("X-User-Id", claims.getUserId())
                .build();
        return chain.filter(exchange.mutate().request(mutated).build());
    }

    @Override
    public int getOrder() { return -200; }
}

要点:失败时用 401 表示"未认证"、403 表示"无权限",语义别混用;直接 setComplete 结束请求比抛异常更干净;透传给下游的用户标识要"覆盖写"而不是"追加写",杜绝客户端伪造 X-User-Id 冒充他人。

下游如何消费用户身份

后端服务从请求头读取网关写入的用户上下文(配合第 22 章的统一上下文组件),例如 user-service 的拦截器把 X-User-Id 放入 ThreadLocal 供业务使用。注意服务只该信任"自己人"写入的头——内部服务之间调用同样要带上身份并校验来源,别把网关头当作天然可信输入。

工程化补充

网关鉴权应做到无状态可水平扩展,公钥/黑名单等数据放配置中心或 Redis 而非网关本地内存;Token 解析与验签属于 CPU 密集操作,可加缓存与限流(第 10 章)防刷;登录、刷新、登出等端点本身放在白名单并由认证服务处理,网关不掺和会话细节。Token 格式、签名算法与密钥管理以你选用的认证库/规范文档为准。

小结

网关鉴权 = 白名单放行公共路径 + 全局过滤器校验 Token + 401 快速失败 + 覆盖式透传用户头。它把"你有没有资格"收敛到一处,后端专注"你是谁、能干什么"的授权逻辑。身份信息要随调用链继续传递,这正是下一章上下文透传的主题。

笔记加载中…