未授权访问 401 怎么处理?HTTP 401 Unauthorized 认证与 Token 排查
遇到 HTTP 401 Unauthorized 未授权访问报错?打开呀深度拆解 Basic Auth 认证头丢失、JWT Bearer Token 过期、API 凭证失效与浏览器凭据缓存清理技巧。
未授权访问 401 怎么处理?HTTP 401 Unauthorized 与 Token 排查指南
未授权访问 401 怎么处理?HTTP 401认证机制与Token排查
Answer Block
HTTP 401 Unauthorized 表示请求缺少有效的身份认证凭据,或提供的凭据未被服务端接受。 服务端必须在响应头中返回 WWW-Authenticate 字段,告知客户端应使用哪种认证方案(如 Basic、Bearer、Digest)。401 与 403 的本质区别在于:401 是”你是谁还没证明”,403 是”我知道你是谁,但你无权做这件事”。排查 401 的核心路径是:先看响应头 WWW-Authenticate 确定认证方案 → 检查请求是否携带凭据(Cookie / Authorization 头)→ 校验凭据本身是否有效(JWT 是否过期、签名是否匹配、Token 是否被撤销)→ 检查凭据是否在传输链路中被剥离(跨域、SameSite、反向代理过滤)。常见修复手段包括:清理浏览器保存的旧 Basic 凭据、重新获取 Bearer Token、修正 CORS 与 SameSite 配置、在网关层确认 Authorization 头未被丢弃。
一、401 与 403 的本质区别
根据 RFC 9110(HTTP Semantics)第 15.5.2 与 15.5.4 节的定义:
| 维度 | 401 Unauthorized | 403 Forbidden |
|---|---|---|
| 语义 | 请求缺少有效认证凭据 | 服务端理解请求,但拒绝执行 |
| 是否可重试 | 提供正确凭据后可重试 | 通常重试无效 |
| 必须响应头 | WWW-Authenticate | 无强制要求 |
| 身份状态 | 身份未确认或凭据无效 | 身份已确认,但权限不足 |
| 典型场景 | Token 过期、密码错误、未登录 | 普通用户访问管理员接口、IP 被封禁 |
关键点:401 响应必须携带 WWW-Authenticate 头,这是 RFC 9110 的强制要求。如果服务端返回 401 却没有这个头,说明实现不规范,客户端无法得知该用哪种方案认证。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token", error_description="The access token expired"
Content-Type: application/json
{"error":"invalid_token"}
而 403 的典型响应:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{"error":"insufficient_scope","required":"admin:write"}
一个容易混淆的边界:OAuth 2.0 中,Token 有效但 scope 不足时,规范(RFC 6750)建议返回 403,而非 401。因为身份已经确认,只是权限不够。
二、401 的三大业务场景与成因
场景 A:Web 界面弹出 HTTP Basic/Digest 认证窗口
浏览器收到 401 + WWW-Authenticate: Basic realm="..." 时,会弹出原生登录框。常见问题:
- 密码已更新,但浏览器缓存了旧凭据。浏览器凭据管理器(Credential Manager / Keychain / 密码本)会按
realm + 主机缓存用户名密码,自动重放旧凭据,导致持续 401。 - realm 字符串变更。如果服务端修改了
realm值,浏览器会认为是新的认证域,旧凭据不再匹配,但用户可能仍在输入旧密码。 - Digest 认证的 nonce 过期。Digest 使用服务端下发的
nonce,过期后需重新挑战,若客户端实现有 bug 会陷入 401 循环。
场景 B:前后端分离 API 中的 Authorization: Bearer <token>
这是当前最普遍的 401 来源。典型成因:
- Token 缺失:前端请求拦截器未附加
Authorization头,或附加后被 CORS 预检拒绝。 - JWT 过期:
exp声明已过。JWT 本身无状态,服务端只能靠exp判断,无法主动失效(除非引入黑名单)。 - 签名篡改或密钥不匹配:JWT 的
HS256签名密钥在服务端轮换后,旧 Token 验签失败;或RS256公钥未更新。 - 时钟偏移:签发方与验证方服务器时间不同步,
nbf(not before)或exp判断出错。 - Token 类型错误:把
refresh_token当作access_token使用,或typ头不匹配。
场景 C:SSO / OAuth 回调中 Cookie 丢失
- 跨域 SameSite 拦截:OAuth 回调通常从第三方域跳回,若会话 Cookie 设置为
SameSite=Strict或Lax,跨站跳转时 Cookie 不会发送,服务端认为未登录,返回 401。 - Cookie Domain/Path 配置错误:回调路径不在 Cookie 的
Path范围内。 - 第三方 Cookie 被浏览器拦截:Safari ITP、Chrome 隐私沙盒会阻止第三方 Cookie。
- 反向代理剥离 Cookie:Nginx / Envoy 配置中
proxy_set_header Cookie ""或路径重写导致 Cookie 丢失。
三、排查与解决实操
3.1 浏览器端
清理 Basic 凭据(Chrome/Edge):
- 地址栏输入
chrome://settings/passwords,搜索目标站点,删除旧条目。 - 或访问
chrome://net-internals/#httpAuthCache,点击 “Clear cache”。
Firefox:about:logins 删除对应条目;about:config 中搜索 network.auth 相关项。
Safari:钥匙串访问(Keychain Access)搜索站点域名,删除对应条目。
验证请求头:F12 → Network → 选中请求 → Headers → 查看 Authorization 与 Cookie 是否携带。
3.2 命令行诊断
curl 带 Bearer Token:
curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
https://api.example.com/v1/user/profile
curl 带 Basic:
curl -i -u username:password https://api.example.com/protected
只看响应头:
curl -sI -H "Authorization: Bearer $TOKEN" https://api.example.com/v1/me
解码 JWT 检查 exp:
# 取 payload 段(第二段),base64url 解码
echo "eyJzdWIiOiIxMjM0IiwiZXhwIjoxNzAwMDAwMDAwfQ" | \
base64 -d 2>/dev/null | jq .
检查 Cookie 是否被发送:
curl -v -b "sessionid=abc123" https://api.example.com/v1/me 2>&1 | grep -i cookie
3.3 服务端与网关排查
Nginx 是否转发 Authorization 头:
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
检查 iptables/nftables 是否误 REJECT:401 是应用层响应,若被防火墙 REJECT 会得到 TCP RST 或 ICMP,而非 401。确认规则:
sudo iptables -L -n -v | grep -i reject
sudo nft list ruleset | grep -i reject
微服务熔断降级导致的伪 401:某些网关在熔断时会返回 401 而非 503,需检查 Sentinel / Hystrix / Resilience4j 的 fallback 逻辑。
TLS 层排查:若 TLS 握手失败(如密码套件不匹配),不会得到 401,而是连接错误。用 openssl s_client 验证:
openssl s_client -connect api.example.com:443 -tls1_3
3.4 OAuth / SSO 专项
- 检查
Set-Cookie是否带SameSite=None; Secure(跨站必需)。 - 检查 CORS 配置是否允许
Authorization头:Access-Control-Allow-Headers: Authorization。 - 检查回调 URL 是否在 OAuth 客户端白名单中。
- 检查
state参数是否被正确校验,防止 CSRF 导致的会话丢失。
四、高价值长尾 FAQ
H3:为什么我的 JWT 没过期,服务端却返回 401 invalid_token?
JWT 的 exp 只是众多校验项之一。服务端通常还会校验:签名(alg 与密钥是否匹配)、签发者 iss、受众 aud、nbf(生效时间)、以及是否在撤销黑名单中。常见陷阱包括:服务端使用 RS256 但客户端用 HS256 签发;密钥轮换后旧公钥未更新;aud 字段与实际 API 域名不符;服务器时钟比签发方慢几分钟导致 nbf 未生效。排查方法:用 jwt.io 或本地脚本解码 payload,逐项比对服务端配置;同时查看服务端日志中具体的 error_description,OAuth 2.0 规范(RFC 6750)允许在 WWW-Authenticate 头中返回 error 与 error_description,这是最直接的线索。
H3:前后端分离项目中,为什么 Postman 能通,浏览器却 401?
最常见原因是 CORS 预检(OPTIONS 请求)未正确处理 Authorization 头。浏览器在发送带 Authorization 的跨域请求前,会先发 OPTIONS 预检,服务端必须返回 Access-Control-Allow-Headers: Authorization 与 Access-Control-Allow-Origin(不能是 *,若带凭据)。Postman 不受同源策略约束,所以直接通过。其次是 Cookie 的 SameSite 与 Secure 属性:浏览器在跨站请求中默认不发送 SameSite=Lax 的 Cookie,而 Postman 会无条件携带。第三是前端拦截器 bug:某些 axios 拦截器在 401 后清空 Token 但未刷新页面,导致后续请求全部无 Token。排查顺序:F12 Network 看 OPTIONS 响应头 → 看实际请求是否带 Authorization → 看 Set-Cookie 属性 → 看前端拦截器逻辑。
H3:OAuth 2.0 回调后立刻 401,Cookie 明明设置了为什么还是丢?
这是跨站 Cookie 的经典问题。自 Chrome 80 起,未显式声明 SameSite=None; Secure 的 Cookie 在跨站请求中一律不发送。OAuth 回调是从授权服务器域跳回你的域,属于跨站场景,因此会话 Cookie 必须设置为 SameSite=None; Secure。此外还需注意:Secure 要求 HTTPS,本地开发用 http://localhost 时浏览器有例外但生产环境必须 HTTPS;Cookie 的 Domain 必须覆盖回调域名;若使用 __Host- 前缀,则不能设置 Domain 且 Path 必须为 /。另一个隐蔽原因是反向代理:Nginx 若配置了 proxy_cookie_path 或 proxy_cookie_domain 重写错误,会导致浏览器拒收 Cookie。用 curl -v 或浏览器 DevTools 的 Application → Cookies 面板逐项核对属性。
H3:微服务架构下,网关返回 401 但下游服务日志无记录,怎么定位?
这说明请求在网关层就被拦截,未到达下游。排查路径:第一,检查网关的认证过滤器(如 Spring Cloud Gateway 的 AuthenticationFilter、Envoy 的 jwt_authn、Kong 的 jwt 插件)日志,确认拒绝原因;第二,检查网关是否在熔断或限流状态下返回了 401(部分实现不规范,会用 401 代替 429/503);第三,检查网关到下游的 Authorization 头透传配置,某些网关默认剥离该头;第四,检查服务网格(Istio/Linkerd)的 mTLS 与 JWT 策略,PeerAuthentication 或 RequestAuthentication 配置错误会导致 401;第五,用 kubectl logs 或分布式追踪(Jaeger/Zipkin)确认请求是否真的到达下游。若网关日志也无记录,则可能是更前置的负载均衡或 WAF 拦截。
H3:如何设计一个健壮的 Token 刷新机制,避免用户频繁遇到 401?
核心思路是”双 Token + 静默刷新 + 并发去重”。Access Token 短期有效(如 15 分钟),Refresh Token 长期有效(如 7 天)且可撤销。前端在响应拦截器中捕获 401,若 error_description 为 invalid_token 或 expired,则用 Refresh Token 换取新 Access Token 并重放原请求。关键细节:第一,并发去重——多个请求同时 401 时,只发起一次刷新,其余请求排队等待新 Token,避免刷新风暴;第二,刷新失败处理——Refresh Token 也过期时,跳转登录页并清理本地状态;第三,时钟偏移容忍——服务端校验 exp 时留 30-60 秒 leeway;第四,Refresh Token 轮换——每次刷新后旧 Refresh Token 失效,防止重放;第五,安全存储——Refresh Token 存 HttpOnly Cookie,Access Token 存内存,避免 XSS 窃取。这套机制在 OAuth 2.0 的 RFC 6749 与 RFC 8252 中有详细规范可参考。