了解什么是 JWT(JSON Web Token)、它的三部分如何工作、常见用途,以及避免常见令牌错误的关键安全建议。

一个 JWT(JSON Web Token) 是一个紧凑的、URL 安全的字符串,用于表示一组信息(通常关于用户或会话),可以在系统之间传递。你通常会看到它以类似 eyJ... 开头的长值形式出现,发送时常放在 HTTP 头里,例如 Authorization: Bearer <token>。
传统登录通常依赖 服务器会话:你登录后服务器保存会话数据并给浏览器一个会话 ID 的 cookie。每个请求都会携带该 cookie,服务器查找会话。
使用 基于令牌的认证,服务器可以避免为每个用户请求保留会话状态。相反,客户端持有一个令牌(如 JWT)并在调用 API 时附带它。这在 API 场景下很流行,因为它:
重要的细微差别: “无状态”并不意味着“永远不做服务器端检查”。许多真实系统仍会根据用户状态、轮换密钥或撤销机制来验证令牌。
JWT 常携带 认证证明(你已登录)和一些基本的 授权提示(角色、权限、scope),但服务器仍需执行完整的授权规则。
你通常会看到 JWT 用作 访问令牌 在:
一个 JWT 是由 三部分 组成的紧凑字符串,每部分都经过 base64url 编码 并用点分隔:
header.payload.signature
示例(已删减):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNzAwMDAwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c…
头部 描述令牌如何被创建——最重要的是 签名算法(例如 HS256、RS256/ES256)和令牌类型。
常见字段:
typ:通常是 "JWT"(在实践中常被忽略)alg:使用的签名算法kid:密钥标识符,帮助验证方在密钥轮换时选择正确的密钥安全提示:不要盲目信任头部。应强制允许列表(allowlist)中的算法,并且不要接受 alg: "none"。
载荷 存放关于用户和令牌上下文的“声明”(字段):令牌是谁的、由谁签发、何时过期等。
重要:JWT 默认不是加密的。 Base64url 编码只是让令牌 URL 安全;它并不隐藏数据。任何拿到令牌的人都可以解码头部和载荷。
因此应避免把秘密(密码、API 密钥)或敏感个人数据放入 JWT。
签名 是对头部 + 载荷 使用密钥签名生成的:
签名提供了 完整性:服务器可以验证令牌没有被修改并且由受信任的签发方生成。它不提供机密性。
因为 JWT 在每次发送时都包含头部和载荷,令牌越大意味着带宽和开销越大。保持声明精简,优先使用标识符而不是大块数据。
声明大体分为两类:注册声明(标准名)和 自定义声明(你应用的字段)。
iss(签发者):谁创建了令牌sub(主体):令牌关于谁(通常是用户 ID)aud(受众):令牌面向谁(例如特定 API)exp(到期时间):令牌何时应停止被接受iat(签发时间):令牌何时创建nbf(不可用于此前):令牌在该时间之前不应被接受仅包含接收方真正需要用于授权决策的信息。
合适的例子:
user_id)避免那些“方便但臃肿”的声明:它们会膨胀令牌、快速过时,并在令牌泄露时增加影响范围。
因为载荷是可读的,不要存放:
如果需要敏感信息,请把它保存在服务器端,并在令牌里只放引用(如 ID),或者在适当时使用加密令牌格式(JWE)。
签名不是加密。
当 JWT 被签发时,服务器对编码后的头部 + 载荷进行签名。令牌被提交时,服务器重新计算签名并比较。如果有人改动哪怕一个字符(例如把 "role":"user" 改为 "role":"admin"),验证会失败,令牌会被拒绝。
JWT 是一种 令牌格式。OAuth 2.0 与 OpenID Connect(OIDC)是描述应用如何请求、签发和使用令牌的 协议。
OAuth 2.0 主要处理 授权:让应用在不共享用户密码的情况下代表用户访问 API。
访问令牌通常 短期有效(几分钟)。短生命周期能限制令牌泄露时的损害。
OIDC 在 OAuth 2.0 之上增加了 认证(用户是谁),并引入了通常为 JWT 的 ID 令牌。
关键规则:不要用 ID 令牌来调用 API。
如果想了解更多实践流程,请参见 /blog/jwt-authentication-flow。
一个典型流程如下:
用户登录(邮箱/密码、SSO 等)。成功后,服务器创建一个 JWT(通常是访问令牌),包含如主体和到期时间等必要声明。
服务器签名令牌并将其返回给客户端(Web 应用、移动应用或其他服务)。
对于受保护的端点,客户端在 Authorization 头中包含 JWT:
Authorization: Bearer <JWT>
在响应请求前,API 通常会检查:
exp(未过期)iss(预期的签发者)aud(面向本 API)如果所有检查通过,API 将把该用户视为已认证并执行授权规则(例如记录级权限)。
因为系统时钟会漂移,许多系统在验证基于时间的声明(如 exp 和有时的 nbf)时允许小幅 时钟偏差(clock skew)。将偏差保持较小,以避免无意间延长令牌有效期。
存储选择会改变攻击者能窃取的目标以及他们重放令牌的难易程度。
内存存储(通常推荐用于 SPA) 将访问令牌保存在 JS 状态中。刷新页面会清除它,减少“事后抓取”风险,但 XSS 漏洞仍可在页面运行时读取它。配合短期访问令牌和刷新流程使用。
localStorage/sessionStorage 使用方便但风险较高:任何 XSS 漏洞都能将令牌导出。如果使用,必须把 XSS 防护作为非可选(使用 CSP、输出转义、依赖库卫生),并保持令牌短期有效。
安全 Cookie(通常是 Web 的默认更安全选项) 将令牌存放在 HttpOnly cookie 中,JavaScript 无法读取,从而降低 XSS 导致的令牌窃取风险。代价是会带来 CSRF 风险,因为浏览器会自动附带 Cookie。
如果使用 Cookie,请设置:
HttpOnlySecure(仅限 HTTPS)SameSite=Lax 或 SameSite=Strict(某些跨站流程可能需要 SameSite=None; Secure)并考虑对状态变更请求使用 CSRF 令牌。
在 iOS/Android 上,应将令牌存放在平台的安全存储中(Keychain / Keystore 支持的存储)。避免放在明文文件或普通偏好设置中。如果你的威胁模型包含已越狱/Root 的设备,则假定可能被提取,并依赖短期令牌与服务器端控制来降低风险。
限制令牌的能力:使用最小 scope/声明、保持访问令牌短期有效,并避免把敏感数据嵌入令牌中。
JWT 很便利,但许多事故来自可预见的错误。把 JWT 当作现金:谁得到就可能花用它。
如果令牌有效期是几天或几周,一旦泄露攻击者就有整段时间可利用。
优先使用短期访问令牌(分钟级),并通过更安全的机制刷新。需要“记住我”功能时,用刷新令牌和服务器端控制实现。
有效签名不足够。请验证 iss 和 aud,并校验时间相关声明如 exp 和 nbf。
解码不等于验证。始终在服务器端验证签名并执行权限检查。
避免把 JWT 放在查询参数中。它们可能出现在浏览器历史、服务器日志、分析工具和 Referrer 头中。
使用 Authorization: Bearer ... 代替。
假定密钥和令牌可能泄露。轮换签名密钥,使用 kid 支持平滑轮换,并制定撤销策略(短期过期 + 能禁用帐户/会话)。关于存储建议,请参见 /blog/where-to-store-jwts-safely。
JWT 很有用,但并不总是最佳选择。关键问题是你是否受益于一种自包含令牌,它可以在不进行每次请求数据库查验的情况下被验证。
对于传统的服务器渲染 Web 应用,如果需要简单的失效控制,服务器端会话 + HttpOnly Cookie 通常是更简单、更安全的默认选择。
如果你需要跨服务的无状态验证并且能保持令牌短期有效,就选 JWT。反之如果需要即时撤销、计划把敏感数据放在令牌里,或者可以无痛使用会话 Cookie,就避免使用 JWT。
使用正确的密钥和预期算法验证。拒绝无效签名——没有例外。
exp(到期)确保令牌未过期。
nbf(不可用时间之前)如存在,确保令牌尚不可提前使用。
aud(受众)确认令牌是针对 你的 API/服务 的。
iss(签发者)确认令牌来自预期的签发者。
验证令牌格式、强制最大大小,并拒绝意外的声明类型以减少边界情况错误。
HS256(对称密钥):一个共享秘密用于签名和验证。
RS256 / ES256(非对称密钥):私钥签名;公钥验证。
经验法则:如果有多个独立系统需要验证令牌(或你不完全信任每个验证方),优先选择 RS256/ES256。
iss、aud,以及在策略允许时的用户 ID)。JWT 是加密的吗?
默认不是。大多数 JWT 是签名的,不是加密的,这意味着内容任何拿到令牌的人都能读取。使用 JWE 或把敏感数据放在服务器端以保证机密性。
我能撤销 JWT 吗?
如果仅依赖自包含的访问令牌,撤销并不容易。常见做法包括短期访问令牌、高风险事件的 deny-list、或带轮换的刷新令牌。
exp 应该设多长?
尽可能短,同时兼顾用户体验与架构需求。许多 API 使用分钟级的访问令牌,配合刷新令牌实现长会话。
如果你在一个新 API 或 SPA 中实现 JWT 认证,很多工作是重复性的:接入中间件、验证 iss/aud/exp、设置 Cookie 标志、避免把令牌写入日志等。
使用 Koder.ai,你可以通过对话驱动工作流快速生成 Web 应用(React)、后端服务(Go + PostgreSQL)或 Flutter 移动应用——在 规划模式 中迭代,使用 快照与回滚 精炼安全设置,并在准备就绪时导出源码。这能帮助你加速实现基于 JWT 的认证流程,同时保留对验证逻辑、密钥轮换策略和部署/托管设置(含自定义域名)的控制。
A JWT (JSON Web Token) 是一个紧凑、URL 安全的字符串,携带声明(数据字段),服务器可以验证它。它常在 API 请求中通过以下方式发送:
Authorization: Bearer <token>关键点:服务器可以验证令牌的完整性(通过签名),而不需要为每个请求保留单独的会话记录。
会话认证通常在服务器端保存状态(通过会话记录,用 cookie/session ID 作为键)。使用 JWT 认证时,客户端每次请求携带一个签名的令牌,API 验证该令牌。
JWT 在 API 和多服务架构中很受欢迎,因为验证可以本地完成,减少对共享会话存储的需求。
“无状态”并不意味着没有服务器端校验:很多系统仍会包含撤销列表、用户状态检查或密钥轮换等服务器端检查。
一个 JWT 由三部分 Base64URL 编码并以点分隔:
header.payload.signatureheader 描述签名方式,payload 包含声明(比如 sub, exp, aud),signature 用于检测篡改。
不是。标准 JWT 通常是签名的,而不是加密的。
如果需要机密性,请考虑使用 JWE(加密令牌),或将敏感数据保留在服务器端,只在 JWT 中放置标识符。
签名让服务器能够验证令牌未被修改,并且由拥有签名密钥的一方签发。
它并不:
exp 前自动撤销令牌把令牌视为凭证:一旦泄露,通常可以在其有效期内被重放使用。
alg 告诉验证方使用了哪种算法(例如 HS256 或 RS256)。kid 是密钥标识符,有助于在密钥轮换时选择正确的验证密钥。
安全建议:
alg 值。alg: "none"。从标准声明开始,尽量让自定义声明最小化。
常见注册声明:
iss(签发者)JWT 是一种令牌格式;OAuth 2.0 和 OpenID Connect 是协议。
常见对应关系:
重要规则:不要把 用来直接调用 API,即便它看起来像访问令牌。
针对浏览器应用,常见存储选项:
如果使用 Cookie,请设置:
至少要验证:
exp(未过期)iss(来自预期的签发者)aud(面向你的 API/服务)nbf(若存在,确保不提前使用)另外添加实用的防护:
kid 导致不安全的密钥查找行为。subaud(受众 / 目标 API)exp(到期时间)iat(签发时间)nbf(不可用时间之前)避免在载荷中放入秘密或敏感的个人数据,因为一旦令牌暴露这些内容就可被读取。
HttpOnlySecure(仅 HTTPS)SameSite=Lax 或 SameSite=Strict(某些跨站流程可能需要 SameSite=None; Secure)此外,对于状态变更请求考虑使用 CSRF 令牌。
无论哪种方式,都应让访问令牌短期有效并最小化权限。