不问服务器也能校验 JWT
一句话总结
JWT验证不是向认证服务器询问,而是用公钥验证签名并检查索赔的本地运算。所以很快。
为什么需要这个?
确认令牌是否有效的方法有两种。询问认证服务器的introspection端点,或验证令牌本身的签名。
前者是正确的。刚刚取消的代币也会立即失效。相反,每次请求都会产生网络往返。如果十个微服务分别询问,认证服务器就会出现瓶颈。
后者在没有网络的情况下结束。只要有公钥就可以了,公钥可以缓存。但不能立即反映取消。在令牌到期之前,它似乎是有效的。
实际操作的答案已经确定。将访问令牌设置为短时间(5~15分钟)进行本地验证。即将取消反映延迟限制为其寿命。如果同时使用刷新令牌循环,即使没有黑名单也能获得实质性无效化效果。
怎么行动
JWT分为三个部分。头,数据包,签名。前两个只是Base64URL编码,不是加密。任何人都可以读到。所以不能把秘密放在JWT中。
验证顺序如下。
首先是标题的alg哇kid看。alg去none如果与预期不同,将立即拒绝。实际上确实发生过没有确认这一点而导致签名被绕过的事故。
kid在JWKS端点中找到相应的公钥。JWKS是缓存的kid找不到的话,请重新更新一次——旋转键时需要。
验证签名。如果是RS256,则是通过公开密钥进行RSA验证。
接下来看一下索赔。exp(到期),iss(发行者是我们知道的那台服务器吗),aud(这个代币是为我们API准备的吗),还有如果需要的话nbf哇azp是。
aud遗漏检查的错误特别常见。同一个认证服务器发行的其他API用令牌会通过我们的API。
混淆ID令牌和访问令牌也很常见。ID令牌是为了客户端应用程序确认“用户是谁”而设的。aud是客户端。API调用必须使用访问令牌。
在现场相遇的样子
如果有键旋转,JWKS中会有多个键同时存在。kid这就是选择的原因。如果将缓存TTL设置太长,旋转后所有验证都会失败,如果设置太短,请求就会涌向认证服务器。一般设置几个小时的缓存。kid写下立即更新Ms.S的方式。
在验证中必须确认的五个事项
仅仅签名就可以了,你必须看全部五个。
| 索赔 | 确认 | 不看的话 |
|---|---|---|
iss |
是我们知道的发行人吗 | 其他地方发行的代币通过 |
aud |
针对这个服务吗 | 其他服务用的代币通过 |
exp |
没有到期吗 | 永远有效 |
nbf |
还是在有效期前吗 | 未来代币通过 |
| 签名 | 是否以发行者的指纹签名 | 伪造代币通过 |
**aud遗漏是最常见的。**使用相同认证服务器的不同应用程序的
可以使用访问令牌调用我们的API。如果有多个微服务的话
一定会确认的。
算法混淆攻击
不能把算法交给库。令牌头部的alg就那样相信的瞬间
攻击者把它none伊娜HS256换成发送。
정상: {"alg": "RS256", "kid": "abc"} ← 공개키로 검증
공격: {"alg": "none"} ← 서명 없이 통과시키려는 시도
공격: {"alg": "HS256"} ← 공개키를 HMAC 비밀키로 쓰게 유도
第二个特别巧妙。RS256的公开密钥是任何人都知道的值,将其作为HMAC的 用对称密钥的话,攻击者可以制作有效的签名。
# ❌ 헤더를 믿는다
jwt.decode(token, key)
# ✅ 우리가 알고리즘을 정한다
jwt.decode(token, key, algorithms=["RS256"], audience="labhub-api",
issuer="https://auth.labhub/realms/labhub")
键旋转和JWKS缓存
认证服务器会定期更换密钥。应用程序是kid符合(键识别符)的
选择公开密钥,不知道的kid遇到的话会再次收到JWKS。
1. 토큰 헤더의 kid 를 본다
2. 캐시에 있으면 그 키로 검증
3. 없으면 JWKS 엔드포인트를 다시 받는다 (여기에 속도 제한을 건다)
如果第3次没有限制的话,可以淹没**包含不知道的kid的请求,摧毁认证服务器。 有。**限制分堂几回,在此期间只写缓存的密钥。
现金寿命短(5~15分钟),但旋转期间旧键和新键必须同时有效。 做。这是认证服务器同时发布两个密钥的期间的原因。
下次实习要做的事情
从Keycloak中接收令牌,将其拆分为三个部分,确认索赔,在JWKS中找到密钥,验证签名,甚至确认数据包被篡改而验证失败。最后,比较100次inspector和本地验证所需的时间。