
一、先说结论面试官真正想听什么当面试官问出「如何保证 token 的安全」时他通常不是在等一句「用 JWT」或者「放 Redis 里」这样的答案。这个问题的考察点往往有三层第一层你知不知道 token 有哪些风险。能不能把泄露、篡改、重放、伪造、存储不当、传输劫持这些威胁说清楚。第二层你知不知道每种风险应该用什么手段去防。能不能把 HTTPS、签名、加密、过期、刷新、吊销、设备绑定、限流这些手段对应到具体攻击场景。第三层你能不能结合业务给出可落地的整体方案。也就是从登录签发、传输、前端/客户端存储、后端校验、退出吊销、异常监控形成完整闭环。换句话说token 安全不是一个单一知识点而是一个贯穿认证、会话管理、密码学基础、Web 安全和工程实践的综合题。下面我们按照「风险分析 → 防御手段 → 工程落地 → 面试表达」的路径把这个问题彻底拆开讲透。在开始之前先明确一个常见误区token 本身并不是为了「完全避免被盗」而设计的而是为了在无状态、分布式环境下实现可校验的访问凭证。所谓「保证 token 安全」本质是把「被盗的概率降到最低、被盗后的损失控制在最小、发现被盗的时间缩到最短」。理解了这句话后面所有措施都有了统一的判断标准。二、Token 基础面试前必须建立的概念框架2.1 什么是 TokenToken 是用户在完成身份认证之后由认证服务签发的一串凭证。客户端在后续请求中携带这串凭证服务端通过校验它来判断「你是谁、有什么权限、这次请求是否可信」。它的存在主要是为了解决两个问题HTTP 本身是无状态的。服务端无法天然记住上一次请求是谁发来的因此需要一个可验证的身份标识。分布式系统中 Session 难以共享。传统 Session 通常存于单台服务器内存或集中式存储中而 token 则可以通过签名自包含信息减少服务端状态依赖。这里要注意Token 并不天然等于 JWT。JWT 只是 token 的一种实现规范。除了 JWT 之外还有「不透明 token」这种常见形式。2.2 不透明 TokenOpaque Token不透明 token 自身不携带业务信息通常是一串随机生成的字符串例如a8f3c9d2e1b6475f9c2d1a3b4e5f6a7b8c9d0e1f它的典型特点是信息存储在服务端例如 Redis、数据库或内存缓存中token 只是一个「钥匙」或「索引」。客户端无法从 token 中读取任何信息。服务端收到 token 后需要查询存储才能拿到用户身份、权限、过期时间等内容。优点是可以随时撤销、状态可控缺点是每个请求都可能产生存储查询开销。为了降低开销工程上通常会配合本地缓存和短期 TTL 使用。2.3 JWT 的结构JWT 全称是 JSON Web Token由三部分组成中间用点号分隔header.payload.signature例如eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cHeader描述签名算法和 token 类型例如{alg:HS256,typ:JWT}。Payload存放声明信息例如用户 ID、角色、过期时间、签发时间等。Signature使用 Header 中声明的算法对「Header Payload」进行签名防止内容被篡改。需要特别强调JWT 默认只是签名不是加密。Payload 中的内容经过 Base64Url 编码后可以直接解码查看。任何拿到 token 的人都能看到里面的信息只是无法在不知道密钥的情况下修改后仍然通过校验。因此敏感信息绝对不能放进 JWT Payload。2.4 签名算法HS256 与 RS256JWT 常用的签名算法可以分两类对称算法如 HS256签名和验签使用同一个密钥。实现简单、性能较好但密钥需要在签发方和校验方之间共享。密钥一旦泄露攻击者就可以伪造任意 token。非对称算法如 RS256使用私钥签名、公钥验签。认证服务持有私钥下游服务只持有公钥。即使公钥被广泛分发攻击者也无法伪造 token私钥泄露的风险面被缩小到认证服务一侧。在分布式微服务场景下通常更推荐RS256。因为如果多个服务都用 HS256 共享同一个密钥任何一个服务被攻破都可能导致整个认证体系失效。而 RS256 让每个服务只拿公钥权限边界更清晰。在 Java 生态中可以使用 JJWT 库配合 RSA 密钥对来完成签名和验签。2.5 面试常见认知误区误区一JWT 就是加密的。错误。JWT 通常只签名、不加密。需要加密时可以使用 JWE。误区二JWT 越大越好把权限全部塞进去。错误。Payload 过大会增加网络开销且敏感信息暴露风险更高。误区三JWT 无状态所以完全不需要服务端存储。不完全正确。为了实现退出登录、踢人、吊销仍可能需要黑名单或版本号等轻量状态。误区四token 永不过期更省事。非常危险。有效期越长泄露后的损失窗口越大。三、Token 的六类核心安全风险只有先搞清楚攻击者能从哪些环节下手防御措施才有针对性。下面按攻击链条梳理最常见的六类风险。3.1 传输过程被抓包如果客户端与服务端之间的通信使用明文 HTTP攻击者可以经过中间人攻击、公共 WiFi 嗅探、DNS 劫持等方式截获请求中的 token。危害非常直接拿到 token 就相当于拿到了用户的登录态。对于没有绑定设备、没有第二因子的系统攻击者几乎可以完全冒充用户。防御核心所有涉及 token 的传输必须走 HTTPS/TLS。同时要配置 TLS 版本和加密套件禁用已经不安全的老旧协议。3.2 前端/客户端存储被窃取很多前后端分离项目会把 token 存在浏览器 localStorage 中然后通过Authorization请求头传给后端。这种做法在隐私性上有一个大问题任何能执行 JavaScript 的 XSS 攻击都可以通过脚本读取 localStorage 中的 token。一旦存在 XSS 漏洞token 会在不被用户察觉的情况下被发送到攻击者服务器。相比之下HttpOnly Cookie不允许 JavaScript 访问能够显著降低 XSS 场景下 token 被直接窃取的概率。但它也并非万能还需要配合 SameSite、Secure、CSRF 防护等措施。3.3 Token 被篡改与伪造如果使用签名算法不当例如把 HS256 的密钥写死在代码里并提交到公开仓库。密钥强度过低可以被暴力破解。错误地允许alg字段为none导致跳过签名校验。攻击者就可能修改 JWT 中的用户 ID、角色等信息或者直接生成全新的伪造 token。历史上一些 JWT 库由于对algnone处理不严谨曾导致严重的安全漏洞。防御核心验签时必须强制指定允许的算法白名单明确拒绝none。密钥必须从配置中心或密钥管理系统中加载不能硬编码。HS256 密钥要足够长RS256 私钥要妥善保存公钥可以分发。校验签名后再信任 Payload 中的任何业务字段。3.4 重放攻击重放攻击是指攻击者截获一个有效 token或截获携带 token 的完整请求后在授权范围内重复发起请求。与「密码被盗」不同重放时 token 本身依然有效服务端很难仅凭签名判断这次请求是否来自攻击者。典型场景包括支付接口被截获后重复提交。短信验证码接口被重放。管理后台操作请求被重放。防御思路通常分为两类对于普通访问令牌缩短 token 有效期配合刷新令牌控制生命周期。对于关键操作引入一次性 nonce、时间戳校验、请求幂等键甚至二次确认。3.5 跨站请求伪造 CSRF当系统使用 Cookie 自动携带凭证时攻击者可以诱导用户在第三方页面点击链接或提交表单使浏览器在用户不知情的情况下发出携带 Cookie 的请求。例如用户已经登录银行网站攻击者构造一个页面img srchttps://bank.example.com/transfer?toattackeramount1000 /浏览器加载该图片时会自动携带银行域名下的 Cookie如果银行接口只依赖 Cookie 鉴权且没有 CSRF 防护攻击就成功了。防御手段包括使用 SameSite Cookie 属性限制跨站请求携带 Cookie。校验请求来源 Origin 和 Referer。关键请求使用一次性 CSRF Token。如果 token 只放在Authorization请求头中而不是 Cookie 中浏览器不会自动携带天然降低 CSRF 风险。3.6 用户身份被横向越权有些系统只校验「用户是否登录」却忽略「当前用户是否有权访问这个资源」。例如用户 A 登录后修改请求中的资源 ID就可能读取用户 B 的数据。这严格来说不只是 token 的问题而是授权模型的问题。但面试中经常把它和 token 安全放在一起讨论因为 token 往往承担了「识别用户 携带角色权限」的职责。防御要点后端不能只信任前端传来的身份字段必须基于可信凭证做访问控制。做对象级授权检查确保用户只能访问属于自己或明确授权范围内的资源。权限变更时要及时反映到 token 或服务端会话状态中。四、防御措施全景从签发到销毁的完整闭环接下来我们从工程实践角度给出一个可落地的 token 安全体系。它覆盖了 token 的整个生命周期签发、传输、存储、校验、刷新、吊销和监控。4.1 登录与签发阶段签发是 token 生命周期的起点。这里的安全要点包括登录凭证必须安全传输用户名、密码、短信验证码等必须通过 HTTPS 提交。登录过程要防暴力破解对同一账号、同一 IP 的失败登录进行限流和锁定。登录成功后按需生成凭证避免在前端返回多余信息不要把敏感字段放进 JWT。记录登录元数据设备指纹、IP、登录时间、用户代理等为后续风险识别做准备。4.2 Token 内容设计JWT Payload 中建议包含的声明sub用户唯一标识。iat签发时间。exp过期时间。jtitoken 唯一 ID用于防重放和黑名单。iss签发方标识。aud接收方标识防止 token 被用于错误的服务。不需要放进 token 的内容密码、手机号、身份证号、家庭住址等敏感信息。过于细粒度的权限位除非你有完整的权限同步与失效策略。大量业务数据这会导致 token 体积膨胀。4.3 传输安全全站启用 HTTPS必要时使用 HSTS 强制浏览器只走 HTTPS。避免在 URL 查询参数中传 token。URL 会被记录在浏览器历史、服务器日志、代理日志和 Referer 中泄露面非常大。优先使用Authorization: Bearer token请求头或安全的 HttpOnly Cookie。移动端要做好证书校验防止中间人证书被替换。4.4 客户端与浏览器存储Web 前端的存储选择需要权衡安全性和易用性存储方式是否可被 JS 读取主要风险适用场景localStorage是XSS 可直接窃取需要前端手动附带请求头且能接受 XSS 风险sessionStorage是XSS 可直接窃取仅限单标签页临时会话HttpOnly Cookie否需防 CSRF同域 Web 应用浏览器自动携带内存变量是但停留时间短刷新页面即失效高安全场景如后台管理很多安全要求较高的系统会选择「短期 access token 存内存 刷新令牌放 HttpOnly Cookie」的组合方案。这样即便发生 XSS攻击者通常只能拿到短期 token而很难读到刷新令牌。4.5 后端校验与验签后端收到请求后校验流程建议如下从请求头或 Cookie 中提取 token。校验 token 格式是否合法是否是 JWT 或预期的凭证类型。校验签名算法必须处于白名单中。校验exp、nbf、iss、aud等声明。校验 token 是否在黑名单中或jti是否已被使用。从可信来源加载用户当前状态判断用户是否仍然有效。完成资源授权检查而不是只判断「已登录」。这里必须再次强调JWT 验签通过只能说明 token 没有被篡改、确实由你签发过并不能说明这个 token 当前应该被继续信任。是否信任还必须结合过期时间、黑名单、用户状态等上下文。4.6 有效期与刷新策略访问令牌和刷新令牌分开是成熟系统的常见做法。Access Token有效期短例如 15 分钟到 2 小时。它用于实际业务请求。Refresh Token有效期长例如 7 天到 30 天。它只用于换取新的 Access Token。为什么要分成两个访问令牌暴露在更频繁的请求中短期可以有限缩小泄露后的影响范围即便泄露攻击者可以利用的时间窗口也很短。刷新令牌使用频率低、暴露面小可以设置更长的有效期以提升用户体验同时通过服务端存储和吊销机制维持可控性。一个常见做法是让 Access Token 短期有效Refresh Token 长期有效再把 Refresh Token 存放到 HttpOnly Cookie 或安全存储中。刷新接口要单独设计并施加更严格的校验。4.7 刷新令牌安全刷新令牌是整个认证体系中「能换来新凭证」的关键一旦泄露攻击者可以持续刷新并长期持有访问权限。因此它的保护等级要高于普通访问令牌。限制刷新接口的调用频率单个用户或 IP 短时间高频刷新应触发预警或临时冻结。绑定设备与使用场景在校验时比对设备指纹、登录 IP 或渠道标识出现明显变化时要求重新登录。支持轮换每次刷新成功后同时签发新的 Refresh Token并让旧 Refresh Token 失效可以降低被盗后的复用窗口。设置最大续期避免 Refresh Token 无限续期例如连续使用 30 天后必须重新认证。Refresh Token 建议使用不透明 token 或结合服务端状态管理。如果使用 JWT 作为刷新令牌也要为其设置 jti方便在服务端做吊销和轮换判断。4.8 吊销与退出登录「退出登录是不是真的生效」经常被面试官用来判断候选人是否真正做过工程落地。JWT 本身无状态并不天然支持指定某个 token 立即失效需要靠额外机制弥补服务端黑名单退出登录、强制下线、修改密码后把 token 的唯一标识放入 Redis 等存储设置与该 token 剩余有效期一致的过期时间。Token 版本号在用户数据中维护 token_version签发时写入 JWT校验时比对当前版本做批量吊销。原地重签名或缩短有效期关键操作后立即触发刷新缩短旧凭证的有效期。WebSocket 或网关拦截对长连接场景在吊销后主动断开连接避免已建立的通道继续使用旧 token。面试时可以把黑名单用于「细粒度吊销」token 版本号用于「批量、低存储开销吊销」两者组合是常见方案。4.9 异常监控与风险响应安全不是一次性配置而是一个持续运营的过程。面试中如果能补上监控视角答案会明显更完整。登录异常检测统计异常失败、异地登录、短时间多设备登录等信号。凭证异常识别监控 token 使用频率异常、高权限操作后的敏感行为。实时阻断对接风控系统在网关层根据风险评分拒绝高风险请求。安全处置闭环发现风险后能立即吊销、通知用户、记录审计日志并触发重新认证。五、实战落地一个可参考的 Token 安全方案这一节我们把前面分散的措施串起来用 Java 生态给出一套从登录到访问控制的完整示例帮助你在面试中讲出「有架构、有细节、有代码」的感觉。5.1 整体架构与流程一个典型的 token 安全体系可以分为以下模块认证服务负责登录、签发 Access Token 和 Refresh Token管理签发密钥。网关或过滤器统一校验签名、过期、黑名单并把用户身份写入上下文。资源服务基于网关传递的身份完成业务授权和对象级权限检查。凭证存储Redis 用于黑名单、刷新令牌和限流数据库保存用户、设备和授权记录。风控服务接收行为日志输出风险评分和处置建议。请求链路可以概括为用户提交登录信息到认证服务。认证服务校验成功后使用 RS256 签发 Access Token同时生成 Refresh Token 并写入 Redis。客户端保存凭证后续请求携带 Bearer Token。网关验证签名和过期时间查询黑名单必要时校验刷新令牌并触发轮换。网关把用户标识传给后端后端完成资源级鉴权。退出、封禁、修改密码时通过 jti 或 token 版本号完成吊销。5.2 使用 JJWT 签发和校验 Token先定义一个统一的令牌实体和声明常量public class TokenClaim { public static final String USER_ID uid; public static final String USERNAME username; public static final String TOKEN_VERSION tokenVersion; }签发 Access Token 与 Refresh Token 的示例代码import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.security.KeyPair; import java.time.Instant; import java.util.Date; import java.util.UUID; public class TokenService { private final KeyPair keyPair; public TokenService(KeyPair keyPair) { this.keyPair keyPair; } /** 使用 RS256 签发访问令牌。 */ public String issueAccessToken(String userId, String username, int tokenVersion, long ttlSeconds) { Instant now Instant.now(); return Jwts.builder() .setHeaderParam(alg, RS256) .setId(UUID.randomUUID().toString()) .setSubject(userId) .claim(TokenClaim.USERNAME, username) .claim(TokenClaim.TOKEN_VERSION, tokenVersion) .setIssuer(auth-service) .setAudience(user-service) .setIssuedAt(Date.from(now)) .setExpiration(Date.from(now.plusSeconds(ttlSeconds))) .signWith(keyPair.getPrivate(), SignatureAlgorithm.RS256) .compact(); } /** 严格校验签名和声明。 */ public Claims parseAccessToken(String token) { return Jwts.parserBuilder() .setSigningKey(keyPair.getPublic()) .requireIssuer(auth-service) .requireAudience(user-service) .build() .parseClaimsJws(token) .getBody(); } }这里有几个安全细节签名算法在 builder 中被明确指定为 RS256不会受到alg字段影响。解析时强制iss和aud避免 token 在不同服务间混用。私钥从密钥对注入不应硬编码在服务代码中。5.3 刷新令牌和轮换示例Refresh Token 建议使用随机字符串并写入 Redis避免把全部信息都暴露在客户端。刷新流程大致如下public class RefreshTokenService { private final TokenStore tokenStore; private final TokenService tokenService; public RefreshTokenService(TokenStore tokenStore, TokenService tokenService) { this.tokenStore tokenStore; this.tokenService tokenService; } public TokenPair refresh(String refreshToken) { RefreshTokenDetail detail tokenStore.loadRefreshToken(refreshToken); if (detail null || detail.isExpired() || detail.isRevoked()) { throw new InvalidTokenException(refresh token is invalid); } // 生成新的访问令牌和刷新令牌 String newRefreshToken UUID.randomUUID().toString().replace(-, ); String accessToken tokenService.issueAccessToken( detail.getUserId(), detail.getUsername(), detail.getTokenVersion(), 30 * 60 ); // 轮换刷新令牌旧令牌立即失效 tokenStore.removeRefreshToken(refreshToken); tokenStore.saveRefreshToken(newRefreshToken, new RefreshTokenDetail( detail.getUserId(), detail.getUsername(), detail.getTokenVersion(), detail.getMaxRefreshCount() - 1, detail.getExpireAt() )); return new TokenPair(accessToken, newRefreshToken); } }这种轮换方式最大的好处是即使某个 Refresh Token 被窃取只要服务端坚持「一次刷新只能成功一次」攻击者和用户中必有一方会被强制重新登录从而缩小损失。5.4 黑名单和登出示例对于需要立即失效的场景可以把 Access Token 的jti写入 Redispublic class TokenBlacklist { private final RedisTemplatelt;String, Stringgt; redisTemplate; public TokenBlacklist(RedisTemplatelt;String, Stringgt; redisTemplate) { this.redisTemplate redisTemplate; } public void revoke(String jti, long remainingSeconds) { if (remainingSeconds 0) { redisTemplate.opsForValue().set(token:blacklist: jti, 1, remainingSeconds, TimeUnit.SECONDS); } } public boolean isBlacklisted(String jti) { return Boolean.TRUE.equals(redisTemplate.hasKey(token:blacklist: jti)); } }网关层在验签通过后检查 JWT 中的jti是否在黑名单中。存在即拒绝请求或返回 401 并要求客户端重新登录。5.5 网关统一鉴权思路在实际项目中网关过滤器通常承担以下职责从Authorization请求头中提取 token。解析 JWT校验签名、过期时间、issuer 与 audience。读取黑名单或 token 版本号确认该 token 仍然可用。把用户标识、角色、请求 ID 写入传递上下文向后端转发。不建议让每个业务微服务各自重复实现签名校验逻辑否则很容易出现版本不一致、算法配置错误等漏洞。六、面试表达如何把这个问题答出层次感很多候选人不是不会而是表达没有层次。面试官听到「用 HTTPS、不要把 token 放 localStorage、要设过期时间」这类零散回答时会感觉候选人只背了要点没有形成体系。6.1 推荐的答题框架建议按照「先说结论再讲风险最后给落地闭环」的顺序回答先给结论token 安全不是单一手段而是覆盖全生命周期的体系。核心是降低被盗概率、缩小被盗损失、加快发现速度。讲清风险传输抓包、存储被窃、篡改伪造、重放攻击、CSRF、越权授权三类以上让面试官知道你有风险视角。讲清防御HTTPS 传输、签名与密钥管理、过期刷新、服务端状态、异常监控逐条对应风险。讲清落地给出 Access Token 短存活、Refresh Token 轮换、网关鉴权、黑名单吊销等具体方案。收尾升华点出「安全是纵深防御不是某一个技术点」并给出项目中需要持续加固的项。6.2 常见的深入追问JWT 存哪最好没有绝对答案要结合应用类型、XSS 与 CSRF 威胁模型。可以回答 HttpOnly Cookie 或内存存储的适用场景。为什么 Refresh Token 不能放 localStorage因为 XSS 读取后可以长期换新 token扩大损失窗口。JWT 过期后怎么办通过刷新令牌完成续期而不是继续无限延长有效期。如何让 JWT 立即失效短有效期加黑名单、token 版本号、刷新轮换三种方式组合。6.3 面试中要避开的答案「JWT 已经加密了所以安全。」这是对签名和加密的混淆容易被追问。「把 token 放 localStorage 就行。」完全忽视了 XSS 风险。「设置很长过期时间少登录几次。」忽略了泄露后的损失窗口。「验签通过就说明用户可以信任。」缺少黑名单、用户状态和授权状态的校验。七、总结「如何保证 token 的安全」看似简单但它是少数能同时考察系统设计、密码学基础、Web 攻击面、工程实践和风险意识的综合题。回到开头的判断token 安全的目标可以浓缩为三句话降低泄露概率HTTPS、安全存储、防 XSS 与 CSRF。缩小泄露影响短期访问令牌、刷新轮换、权限最小化。加快发现和处置监控、告警、黑名单、版本吊销和审计。面试时先把这三点立起来再顺着「签发到传输、传输到存储、存储到校验、校验到刷新、刷新到吊销、吊销到监控」这条链路补充细节就能形成既有框架又能追问的完整答案。