2. 令牌化接口基础

聊令牌安全,必须先搞清楚令牌在哪儿跑、怎么跑。接口类型不同,令牌的传递姿势也完全不一样。我个人习惯把接口类型和令牌传递方式放在一起看——你想想看,一个 REST API 和一个 WebSocket 长连接,令牌的存活周期、传递位置、校验时机能一样吗?肯定不一样。

这一节咱们过一遍三种主流接口——REST、gRPC、WebSocket,以及令牌在它们身上的"藏身之处":Header、Cookie、Body。嗯,这里要注意,每种接口能用哪几种传递方式,是有讲究的。

2.1 接口类型概览

先简单看一眼三种接口的特征。我把它们最核心的差异列在下面:

接口类型 通信协议 消息格式 连接方式 典型场景
REST HTTP/1.1, HTTP/2 JSON, XML 短连接(请求-响应) Web API、移动端后端
gRPC HTTP/2 Protocol Buffers 长连接 / 流式 微服务间通信、高性能后端
WebSocket WS / WSS 文本 / 二进制帧 全双工长连接 实时推送、在线协作、游戏

REST 最常用,门槛低,但令牌暴露面也最大。gRPC 内部通信多,令牌通常走 Metadata。WebSocket 是长连接,令牌只在握手阶段传递一次,后续靠连接上下文维持。三种接口的安全关注点完全不一样。

2.2 令牌在 REST 接口中的传递

REST 接口的令牌传递方式最丰富,也最容易出问题。我在项目中遇到过好几次,因为选错了传递位置导致令牌被泄漏的情况。

Header 方式

最常见的方式。令牌放在 HTTP 请求头里,标准做法是用 Authorization: Bearer <>。我建议你们统一用这个格式,别自己发明头字段名。

GET /api/v1/user/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Accept: application/json
我的习惯:Header 传令牌适合绝大多数 REST 场景。注意必须走 HTTPS,否则令牌明文被截获就等于裸奔。

Cookie 方式

令牌放在 Set-Cookie 里下发,客户端自动带回来。这种方式对前端友好,但容易受到 CSRF 攻击。我曾经在审计时发现一个系统把 JWT 放 Cookie 里,没设 HttpOnly 和 SameSite,结果被 XSS 一把梭走——嗯,从那以后我每次看 Cookie 传令牌都会多瞄两眼这两属性。

Set-Cookie: access_=eyJhbGciOiJIUzI1NiIs...; Path=/; HttpOnly; Secure; SameSite=Strict
避坑指南:Cookie 传令牌一定要设 HttpOnly(防 XSS 偷取)、Secure(只走 HTTPS)、SameSite=Strict/Lax(防 CSRF)。缺一个都是漏洞。

Body 方式

令牌放在 POST 请求的请求体里。这种方式不太标准,但在某些老系统或特定场景下还能见到。说白了,GET 请求没法用 Body 传令牌,所以适用范围有限。

POST /api/v1/auth/verify HTTP/1.1
Content-Type: application/json

{
  "": "eyJhbGciOiJIUzI1NiIs...",
  "action": "verify"
}
我的建议:Body 传令牌只适合做"一次性校验"类的接口,比如令牌验证端点。日常业务接口别用这种方式,因为它不符合 REST 的无状态原则,也很难做统一的日志审计。

2.3 令牌在 gRPC 接口中的传递

gRPC 基于 HTTP/2,令牌传递主要走 Metadata。Metadata 类似于 HTTP 的 Header,也是键值对形式。你可以在客户端拦截器里统一注入令牌,服务端用拦截器统一校验——这样做的好处是业务代码里完全不用关心令牌逻辑。

// 客户端拦截器示例(Go)
func authUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    md := metadata.Pairs("authorization", "Bearer " + )
    ctx = metadata.NewOutgoingContext(ctx, md)
    return invoker(ctx, method, req, reply, cc, opts...)
}

// 服务端拦截器示例
func authServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Errorf(codes.Unauthenticated, "missing metadata")
    }
    tokens := md.Get("authorization")
    if len(tokens) == 0 {
        return nil, status.Errorf(codes.Unauthenticated, "missing ")
    }
    // 校验 ...
    return handler(ctx, req)
}

gRPC 还支持流式调用,拦截器同样生效。我记得有一次排查一个 gRPC 服务间调用问题,发现服务 A 给服务 B 传令牌时,Metadata 的 key 大小写没统一——gRPC 默认把 Metadata key 转成小写,结果两边对不上,认证一直失败。你想想看,这种小细节排查起来多折腾人。

2.4 令牌在 WebSocket 接口中的传递

WebSocket 是长连接,令牌只能在一开始握手的时候传。一旦连接建立成功,后续所有帧都复用这个连接上下文,不会再传令牌。所以核心问题就是:握手阶段怎么安全地传递令牌?

主流做法有两种:

  • URL 参数传令牌: ws://example.com/socket?=eyJ... —— 简单但 URL 参数容易被服务器日志、浏览器历史记录泄漏。我个人不推荐在生产环境这么干。
  • Header 传令牌: 在 WebSocket 握手时通过自定义 Header 传令牌。浏览器端的 WebSocket API 不支持自定义 Header,但服务端之间或客户端(如移动端、桌面端)可以用。
  • Cookie 传令牌: 浏览器端最常用。WebSocket 握手时会自动带上目标域名的 Cookie,服务端从 Cookie 里取令牌校验。
// 服务端 WebSocket 握手校验(Node.js + ws 库示例)
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (ws, req) => {
    // 从 Cookie 中取令牌
    const cookies = req.headers.cookie || '';
    const  = cookies.split(';').find(c => c.trim().startsWith('access_='));
    if (!) {
        ws.close(4001, 'Unauthorized');
        return;
    }
    // 校验 ...
    console.log('WebSocket 连接已认证');
});
避坑指南:WebSocket 握手完成后,服务端要持续校验连接的活跃性。我见过一个案例,握手时令牌校验通过,但后续客户端 过期了,服务端没有主动断开连接,导致未授权访问持续了很长时间。解决方案是在业务心跳里加入令牌有效性检查。

2.5 令牌传递方式对比

三种接口、三种传递方式,怎么选?我把它们的核心差异总结成一张表,方便你对照:

传递方式 支持的接口类型 安全性 浏览器兼容性 典型风险
Header REST、gRPC、WebSocket(服务端) 高(仅 HTTPS) 良好(fetch/XHR 支持) HTTPS 配置不当会泄漏
Cookie REST、WebSocket(浏览器) 中(依赖 HttpOnly/SameSite) 原生支持 CSRF、XSS 偷取
Body REST(POST/PUT) 低 良好 不支持 GET、日志泄漏
Metadata gRPC 高(HTTP/2 加密) 不适用 Key 大小写歧义

宏观上看,Header 是通用性最强的方案,适合 REST 和 gRPC。Cookie 在浏览器场景下绕不开,但安全配置必须做全。Body 嘛——我建议除非有特殊原因,否则尽量别用。

下面我把本章的核心知识体系整理成一张框架图,你可以用它来快速回顾:

令牌化接口基础框架 REST API gRPC WebSocket Header Cookie Body Metadata Header Header Cookie 安全评估核心关注点 传输加密(HTTPS/WSS) → 令牌注入校验 → 防重放攻击 → 令牌生命周期管理 → 日志审计 最佳实践 统一令牌校验中间件 | 最小权限原则 | 令牌绑定(IP + User-Agent)

核心要点回顾:

  • REST 接口令牌传递方式最多(Header / Cookie / Body),但也最容易出配置漏洞。优先用 Header + Bearer 方案。
  • gRPC 走 Metadata,靠拦截器统一注入和校验,注意 Key 大小写问题。
  • WebSocket 只在握手阶段传一次令牌,后续靠连接上下文维持。Cookie 是浏览器端最靠谱的方式。
  • 无论哪种接口,令牌传递必须走加密通道(HTTPS / WSS),这是底线,没得商量。

一个小建议:你在设计新系统时,先确定接口类型,再选令牌传递方式,然后围绕它配安全策略。顺序别搞反了——我见过有人先选了 Cookie 传令牌,然后发现接口是 gRPC 的,结果 Metadata 用不了,又回去改架构,折腾得够呛。