后端API一旦暴露在公网,就等于把数据库大门开在了陌生人面前。token鉴权不是可选项,而是防止接口被滥用、数据被拖走的第一道闸门。很多团队对token的理解停留在“登录后返回一个字符串,请求时带上就行”,但真正的问题往往出在token的生成、存储、传递和失效策略上。下面直接拆解一套可落地的后端API token鉴权管控方案。
token的本质是临时身份凭证,不是密码
token要解决的核心问题是:HTTP请求是无状态的,服务器无法记住上一次请求是谁发的。token就是让客户端在后续请求中证明“我已经通过验证”的凭证。与密码不同,token应该有有效期、可撤销、可限定权限范围。把token当密码用,长期有效、明文传输、到处复制,是API安全事故里最常见的原因。
设计token体系时,先明确两个概念:访问令牌(Access Token)和刷新令牌(Refresh Token)。访问令牌寿命短,通常15分钟到2小时,直接用于调用业务接口。刷新令牌寿命长,通常7天到30天,只用于换取新的访问令牌。这样即使访问令牌泄露,攻击者能利用的时间窗口也极短。
token生成必须使用密码学安全的随机数
如果token是可预测的,比如用时间戳加用户ID做MD5,攻击者可以批量伪造。正确做法是使用足够强度的随机源生成不可猜测的字符串。以Java为例,推荐使用SecureRandom生成32字节以上的随机数,再用Base64URL编码:
SecureRandom secureRandom = new SecureRandom(); byte[] randomBytes = new byte[32]; secureRandom.nextBytes(randomBytes); String token = Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
如果是JWT,必须使用HS256以上的签名算法,密钥长度至少256位,并且密钥只能存在于服务端配置或密钥管理服务中,绝不能硬编码在代码仓库里。JWT的payload不要放敏感信息,因为payload只是Base64编码,任何人解码都能看到内容。
token存储位置决定泄露风险等级
Web前端存储token有三个选择:localStorage、sessionStorage、Cookie。localStorage最方便,但XSS攻击一旦成功,攻击者可以直接读取token。Cookie配合HttpOnly属性可以防止JavaScript读取,但需要配合CSRF防护。对于高安全要求的后台系统,推荐使用HttpOnly + Secure + SameSite=Strict的Cookie存储token,同时后端校验Origin和Referer头。
移动端不要把token存在SharedPreferences或NSUserDefaults的明文里,应使用系统级安全存储,如Android Keystore或iOS Keychain。服务端如果需要在数据库存token,只存token的哈希值,不要存明文。验证时对请求中的token做哈希后与数据库比对,这样数据库泄露也不会直接暴露有效token。
token传递必须走HTTPS,禁止URL携带
任何情况下都不要把token拼在URL查询参数里。URL会被浏览器历史、服务器日志、代理服务器、CDN节点记录,泄露面极大。token应放在Authorization头中,格式为Bearer加空格加token:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
同时强制所有API请求走HTTPS。HTTP是明文传输,任何中间节点都能截获请求头和响应体。在网关层配置HTTP到HTTPS的强制跳转,并对非HTTPS请求直接返回403或444状态码。
鉴权中间件要覆盖所有接口,不能有漏网之鱼
很多后端框架的鉴权中间件只配置在部分路由上,新加的接口忘记挂中间件,就成了未授权访问的入口。正确做法是在网关或框架的全局入口统一做token校验,默认所有接口都需要鉴权,只有明确标记为公开的接口(如登录、注册、健康检查)才跳过校验。
以Spring Boot为例,使用拦截器或过滤器统一处理:
@Component
public class TokenAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String path = request.getRequestURI();
if (isPublicPath(path)) {
filterChain.doFilter(request, response);
return;
}
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String token = authHeader.substring(7);
if (!tokenService.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
filterChain.doFilter(request, response);
}
}
这个过滤器注册为全局过滤器后,所有请求都会先经过token校验,新增接口不需要单独配置鉴权,从架构上杜绝了遗漏。
token过期与刷新机制要平滑,不能粗暴踢人
访问令牌过期后直接返回401,客户端跳转登录页,这是最粗暴的做法,用户体验差,而且频繁登录会降低安全性(用户会倾向于使用简单密码)。正确做法是引入刷新令牌静默续期。客户端在访问令牌即将过期或收到401且错误码为token_expired时,用刷新令牌换取新的访问令牌,然后重试原请求。
刷新令牌本身也要有过期时间和轮换机制。每次刷新成功后,旧的刷新令牌立即失效,返回一个新的刷新令牌。这样即使刷新令牌被截获,攻击者与合法用户同时使用时,其中一方刷新后另一方立即失效,服务端可以检测到异常并强制下线。
token权限管控要细粒度,不能只验真伪
验证token有效只是第一步,更重要的是验证这个token能做什么。很多系统只检查token是否存在和过期,不检查用户是否有权限访问当前资源。攻击者拿到一个普通用户的token,就能访问管理员的接口,这就是水平越权和垂直越权。
在token中携带用户ID和角色信息,每次请求时从数据库或缓存中加载用户的权限列表,与请求的资源进行比对。对于高敏感操作(删除、导出、转账),除了token鉴权外,还应增加二次验证,如短信验证码、操作密码或审批流。
权限缓存要设置合理的过期时间。如果用户权限被管理员修改,缓存时间过长会导致旧权限继续生效。建议权限缓存TTL不超过5分钟,并且在管理员修改权限后主动失效相关缓存。
token注销与黑名单机制要实时生效
用户主动退出登录、修改密码、账号被冻结时,token必须立即失效。如果token是无状态的JWT,服务端无法主动使其失效,除非引入黑名单。黑名单可以存储在Redis中,key为token的哈希值或JWT的jti字段,value为失效原因,过期时间设为token的剩余有效期。
每次鉴权时先查黑名单,命中则直接拒绝。黑名单查询要快,Redis是最常用的选择。对于高并发系统,可以在网关层做本地缓存,但本地缓存会带来短暂延迟,需要在安全性和性能之间权衡。对于安全要求极高的系统,建议不做本地缓存,每次实时查Redis。
防暴力破解与异常检测是最后一道防线
即使token机制设计得再完善,攻击者仍可能通过撞库、钓鱼等方式获取合法token。因此需要在API层做异常行为检测。对同一token的请求频率、请求来源IP、请求的地理位置、请求的时间分布进行监控。如果一个token在短时间内从多个地理位置发起请求,或者请求频率远超正常用户,立即触发告警并临时冻结该token。
实现上可以在网关层记录每个token的请求日志,用滑动窗口算法统计频率。对于登录接口,连续失败5次后锁定账号15分钟,并增加验证码。对于API接口,同一token每分钟请求超过阈值时返回429状态码,并记录到安全审计日志。
日志记录要完整,但绝不能记录token本身
审计日志是事后追溯的关键,但日志中如果记录了完整的token,等于把凭证泄露给了所有能查看日志的人。日志中应记录用户ID、请求路径、请求时间、响应状态码、来源IP,但token只记录前8位和后4位,中间用星号代替。这样既能定位问题,又不会泄露完整凭证。
同时日志要设置访问权限,只有安全审计人员可以查看,日志保存时间不少于90天。对于涉及敏感操作的请求,如删除、导出、权限变更,日志要记录请求参数和响应结果的摘要,便于事后审计。
token鉴权管控是一个系统工程,从生成、存储、传递、校验、过期、刷新、注销到监控,每个环节都有被攻破的案例。把每个环节都做扎实,才能让API在开放的同时守住安全底线。安全没有银弹,只有持续投入和不断加固。
