JWT(JSON Web Token)是现在最流行的身份认证方案之一,几乎所有的前后端分离项目、移动端项目、微服务项目都在用JWT。但是很多人虽然天天在用JWT,但是对JWT的配置了解不多,只是简单地生成一个令牌,验证一下签名就完了,很多安全配置都没做,存在很多安全隐患。今天就来详细讲解一下JWT令牌的配置,从基础到高级,包括JWT的结构、常用字段、签名算法、安全配置、最佳实践等,让你全面掌握JWT的配置,用好JWT,避免安全隐患。
一、JWT的基本结构
在讲配置之前,先简单回顾一下JWT的基本结构,这是理解JWT配置的基础。
JWT是一个紧凑的、URL安全的字符串,由三部分组成,用点(.)分隔,分别是Header(头部)、Payload(载荷)、Signature(签名),格式是Header.Payload.Signature。
1. Header(头部)
Header是一个JSON对象,描述JWT的元数据,通常包含两个字段:
alg:签名算法,比如HS256(HMAC SHA256)、RS256(RSA SHA256)、ES256(ECDSA SHA256)等。typ:令牌类型,通常是"JWT"。
比如:
{
"alg": "HS256",
"typ": "JWT"
}这个JSON对象会被Base64Url编码,变成JWT的第一部分。
2. Payload(载荷)
Payload也是一个JSON对象,包含要传输的数据,也就是声明(Claims)。声明分为三种:注册声明、公共声明、私有声明。
注册声明是JWT规范预定义的,推荐使用但不强制,常用的有:
iss(Issuer):签发者,是谁签发的这个JWT。sub(Subject):主题,这个JWT是给谁的,通常是用户ID。aud(Audience):受众,这个JWT是给谁用的,通常是应用名或者URL。exp(Expiration Time):过期时间,JWT什么时候失效,是一个Unix时间戳。nbf(Not Before):生效时间,JWT什么时候开始生效,之前无效。iat(Issued At):签发时间,JWT是什么时候签发的。jti(JWT ID):JWT的唯一标识,用来防止重放攻击。
公共声明是自己定义的,但是要避免冲突,比如用户名、角色、权限等。
私有声明是自定义的,用来在双方之间传递信息,比如用户昵称、头像等。
比如:
{
"sub": "1234567890",
"name": "张三",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}这个JSON对象也会被Base64Url编码,变成JWT的第二部分。
3. Signature(签名)
Signature是用Header里指定的签名算法,把编码后的Header、编码后的Payload、密钥三者组合起来签名得到的,用来验证JWT有没有被篡改。
比如用HS256算法的话,签名就是:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)签名的作用是,接收方拿到JWT之后,用同样的算法和密钥重新计算签名,如果和JWT里的签名一致,说明JWT没有被篡改,是可信的。
这三部分组合起来,就是一个完整的JWT,比如: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuW8oOS4iSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjQyNjIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
二、基础配置:必做的几个配置
讲完了基本结构,接下来讲JWT的基础配置,这几个是必须要做的,不做就会有安全隐患。
1. 设置过期时间(exp)
这是最基础也是最重要的配置,JWT一定要设置过期时间,不能让JWT永久有效。如果JWT永久有效,一旦泄露,别人就可以永久使用这个JWT,危害很大。
过期时间的设置要根据业务场景来,一般来说:
- 访问令牌(Access Token):有效期短一些,比如15分钟到2小时,这样就算泄露了,危害也有限,因为很快就过期了。
- 刷新令牌(Refresh Token):有效期长一些,比如7天到30天,用来刷新访问令牌,但是刷新令牌要存在服务端,可以主动撤销。
不要把访问令牌的有效期设得太长,比如几天甚至几个月,那样太不安全了。也不要设得太短,比如几分钟,那样用户会频繁被要求重新登录,体验很差。15分钟到2小时是比较合理的范围。
2. 设置签发者(iss)和受众(aud)
签发者(iss)和受众(aud)也是很重要的配置,用来验证JWT是不是发给你的,是不是你信任的签发者签发的。
签发者(iss)是标识是谁签发的这个JWT,比如你的认证服务的名字或者URL,验证的时候要检查iss是不是你信任的签发者,如果不是,就拒绝。这样可以防止别人用其他系统的JWT来访问你的系统。
受众(aud)是标识这个JWT是给谁用的,比如你的应用名或者URL,验证的时候要检查aud是不是包含你的应用,如果不是,就拒绝。这样可以防止别人把发给其他应用的JWT拿来访问你的应用。
很多人验证JWT的时候只验证签名,不验证iss和aud,这是有安全隐患的。如果你的系统和其他系统共享同一个密钥,或者用的是同一个认证中心,不验证iss和aud的话,其他系统的JWT也能访问你的系统,这就不安全了。所以,iss和aud一定要设置,验证的时候一定要检查。
3. 设置生效时间(nbf)和签发时间(iat)
生效时间(nbf)是JWT什么时候开始生效,在这个时间之前,JWT是无效的。这个字段可以用来防止时钟不同步的问题,比如签发的时候把nbf设为当前时间减一分钟,这样就算验证方的时钟比签发方慢一分钟,也不会认为JWT还没生效。
签发时间(iat)是JWT是什么时候签发的,这个字段可以用来做一些逻辑,比如判断JWT是不是太老了,或者用户改密码之后,让改密码之前签发的JWT失效。
这两个字段不是必须的,但是建议加上,能提升安全性和灵活性。
4. 设置JWT ID(jti)
JWT ID(jti)是JWT的唯一标识,每个JWT的jti都不一样。这个字段可以用来防止重放攻击,也可以用来做黑名单,主动撤销JWT。
比如,你可以把jti存在Redis里,设置和JWT一样的过期时间,如果需要主动撤销某个JWT,就把对应的jti加入黑名单,验证的时候检查jti是不是在黑名单里,如果在就拒绝。这样就实现了JWT的主动撤销,解决了JWT一旦签发就无法撤销的问题。
jti不是必须的,但是如果需要主动撤销JWT,或者需要防止重放攻击,建议加上。
三、签名算法的选择和配置
签名算法是JWT配置的核心,选择合适的签名算法,正确配置密钥,是JWT安全的基础。
JWT常用的签名算法有两类:对称加密算法和非对称加密算法。
1. 对称加密算法:HS256、HS384、HS512
对称加密算法就是签名和验证用同一个密钥,常用的有HS256(HMAC SHA256)、HS384、HS512,数字代表哈希算法的位数,位数越高越安全,但是性能稍差。
对称加密算法的优点:
- 简单,容易实现和理解。
- 性能好,签名和验证速度快。
- 密钥短,管理简单。
缺点:
- 签名和验证用同一个密钥,所以所有需要验证JWT的服务都要有密钥,密钥泄露的风险大。
- 无法区分签发者和验证者,只要有密钥就能签发JWT,所以如果某个验证服务的密钥泄露了,攻击者就可以签发任意JWT。
- 不适合第三方验证,如果你需要让第三方验证你的JWT,你不能把密钥给第三方,那样第三方就能签发JWT了。
对称加密算法适合用在单体应用或者内部服务之间,所有服务都是你自己控制的,密钥管理比较安全的场景。
使用对称加密算法的注意事项:
- 密钥要足够复杂,不要用简单的字符串,比如"123456"、"secret",要用足够长的随机字符串,至少32位以上,最好用密码学安全的随机数生成器生成。
- 密钥要妥善保管,不要硬编码在代码里,不要提交到Git仓库,要存在配置中心或者环境变量里,而且要加密存储。
- 密钥要定期更换,比如每3个月或者半年更换一次,更换的时候要有过渡期,同时支持新旧密钥,等旧密钥签发的JWT都过期了,再彻底废弃旧密钥。
- 不同的环境要用不同的密钥,开发环境、测试环境、生产环境的密钥要分开,不要用同一个密钥。
2. 非对称加密算法:RS256、RS384、RS512、ES256、ES384、ES512
非对称加密算法就是签名和验证用不同的密钥,有一对密钥,私钥用来签名,公钥用来验证。私钥只有签发者有,公钥可以公开给任何人,验证者用公钥验证签名,但是无法用公钥签发JWT。
常用的非对称加密算法有:
- RS256、RS384、RS512:RSA算法,数字代表哈希位数,RSA是最常用的非对称加密算法,兼容性好。
- ES256、ES384、ES512:ECDSA算法,基于椭圆曲线,比RSA更安全,密钥更短,性能更好,但是兼容性稍差。
非对称加密算法的优点:
- 私钥只有签发者有,公钥可以公开,密钥泄露的风险小,因为就算公钥泄露了也没关系,公钥本来就是公开的。
- 可以区分签发者和验证者,只有持有私钥的签发者才能签发JWT,验证者只有公钥,无法签发JWT,更安全。
- 适合第三方验证,你可以把公钥公开给第三方,让第三方验证你的JWT,但是第三方无法签发JWT,很安全。
缺点:
- 复杂,实现和理解难度大一些。
- 性能比对称加密差一些,特别是RSA,签名和验证速度比HMAC慢。
- 密钥长,管理复杂一些,RSA密钥至少2048位,推荐4096位。
非对称加密算法适合用在微服务架构、分布式系统、需要第三方验证的场景,签发者统一用私钥签发JWT,各个服务用公钥验证,这样密钥管理更安全,也更容易扩展。
使用非对称加密算法的注意事项:
- 私钥要妥善保管,这是最重要的,私钥一旦泄露,攻击者就可以签发任意JWT,危害极大。私钥要存在安全的地方,比如硬件安全模块(HSM)、密钥管理服务(KMS),不要硬编码在代码里,不要提交到Git仓库。
- 公钥可以公开,但是要确保公钥的完整性,防止被篡改,可以通过HTTPS分发公钥,或者把公钥的指纹提前配置在验证方。
- 密钥长度要足够,RSA至少2048位,推荐4096位;ECDSA至少256位。
- 密钥要定期更换,非对称加密的密钥更换比对称加密麻烦一些,因为要更新公钥,但是也要定期更换,比如每年更换一次。
3. 算法选择建议
那么,到底该选对称加密还是非对称加密呢?我的建议是:
- 如果是单体应用,或者只有少数几个内部服务,用对称加密(HS256)就够了,简单、性能好、密钥管理也不复杂。
- 如果是微服务架构、分布式系统,或者需要第三方验证,建议用非对称加密(RS256或者ES256),更安全,更容易扩展。
- 不管用哪种算法,都不要用
none算法,也就是不签名,那样JWT可以被任意篡改,完全不安全。验证的时候一定要检查alg字段,不允许none算法,防止算法降级攻击。
四、高级配置:提升安全性的配置
讲完了基础配置和签名算法,再讲一些高级配置,这些配置能进一步提升JWT的安全性,适合对安全要求高的系统。
1. 令牌刷新机制:Access Token + Refresh Token
前面说过,Access Token的有效期要短,这样就算泄露了危害也小,但是有效期短的话,用户会频繁被要求重新登录,体验很差。怎么解决这个问题呢?就是用Access Token + Refresh Token的机制。
Access Token:有效期短,比如15分钟到2小时,用来访问业务接口,存在客户端(比如内存、Cookie)。 Refresh Token:有效期长,比如7天到30天,用来刷新Access Token,存在服务端(比如Redis),可以主动撤销。
流程是这样的:
- 用户登录成功,认证服务同时签发Access Token和Refresh Token,返回给客户端。
- 客户端用Access Token访问业务接口,Access Token过期后,业务接口返回401。
- 客户端收到401后,用Refresh Token调用刷新接口,申请新的Access Token。
- 认证服务验证Refresh Token有效后,签发新的Access Token(也可以同时签发新的Refresh Token,也就是滚动刷新),返回给客户端。
- 客户端用新的Access Token继续访问业务接口。
这样,Access Token有效期短,安全;Refresh Token有效期长,但是存在服务端,可以主动撤销,用户不用频繁登录,体验好。兼顾了安全性和用户体验。
使用刷新机制的注意事项:
- Refresh Token一定要存在服务端,不要存在客户端,不然泄露了无法撤销。
- Refresh Token要一次性使用,用了之后就失效,签发新的Refresh Token,也就是滚动刷新,这样就算Refresh Token泄露了,也只能用一次,危害小。
- 刷新的时候要验证Refresh Token的有效性,检查是不是在黑名单里,是不是已经被使用过了。
- 用户登出的时候,要把Refresh Token加入黑名单,让它失效,这样就算Refresh Token泄露了,也不能再用来刷新Access Token。
2. 令牌撤销机制:黑名单方案
JWT最大的问题就是一旦签发,在过期之前无法主动撤销,用户登出、改密码、权限变更的时候,旧的JWT还是有效的,这是一个安全隐患。怎么解决这个问题呢?就是用黑名单方案。
黑名单方案的思路是:把需要撤销的JWT的唯一标识(jti)加入黑名单,设置和JWT一样的过期时间,验证JWT的时候,检查jti是不是在黑名单里,如果在就拒绝。
具体实现:
- 签发JWT的时候,生成一个唯一的jti,存在JWT的Payload里。
- 验证JWT的时候,除了验证签名、过期时间、iss、aud等,还要检查jti是不是在黑名单里(比如Redis里),如果在就拒绝。
- 需要撤销JWT的时候(用户登出、改密码、权限变更、管理员踢人),把对应的jti加入黑名单,设置过期时间和JWT的过期时间一致,等JWT自然过期后,黑名单里的记录也会自动过期,不会占用太多空间。
这样就实现了JWT的主动撤销,解决了JWT无法撤销的问题。
黑名单方案的优缺点:
- 优点:实现简单,能主动撤销JWT,解决了JWT最大的安全隐患。
- 缺点:验证JWT的时候需要查一次黑名单(Redis),增加了一次网络请求,性能有一点损耗,但是Redis很快,影响不大;而且如果JWT签发量很大,黑名单可能会占用一些空间,但是因为有过期时间,不会无限增长。
如果对安全要求高,需要主动撤销JWT,建议用黑名单方案,这是目前最常用的JWT撤销方案。
3. 令牌绑定:绑定客户端信息,防止盗用
还有一个高级配置,就是把JWT和客户端信息绑定,比如客户端IP、User-Agent、设备ID等,验证的时候检查这些信息是不是一致,如果不一致就拒绝,这样就算JWT泄露了,攻击者也无法在其他客户端使用,提升了安全性。
比如,签发JWT的时候,把客户端的IP和User-Agent的哈希存在JWT的Payload里,验证的时候,计算当前请求的IP和User-Agent的哈希,和JWT里的对比,如果不一致就拒绝。
但是这个方案有一些问题:
- IP可能会变,比如用户从WiFi切换到4G,IP就变了,这样就会被误判,体验不好。
- User-Agent可能会被伪造,安全性有限。
- 移动端的IP变化更频繁,误判率更高。
所以,这个方案要根据业务场景来用,如果是对安全要求很高的内部系统,客户端IP相对固定,可以用;如果是面向公众的互联网应用,客户端IP变化频繁,就不太适合,会影响用户体验。
如果要用,建议只绑定一些相对稳定的信息,比如设备ID(移动端),或者User-Agent,不要绑定IP,或者IP只绑定网段,不绑定具体IP,减少误判。
4. 加密Payload:保护敏感信息
JWT的Payload是Base64编码的,不是加密的,任何人拿到JWT都能解码看到Payload里的内容,所以不要在JWT里存敏感信息,比如密码、手机号、身份证号、银行卡号等。
但是如果确实需要在JWT里存一些敏感信息,怎么办呢?可以对Payload进行加密,也就是JWE(JSON Web Encryption),JWE是JWT的加密版本,Payload是加密的,只有持有私钥的人才能解密,这样就算JWT泄露了,攻击者也看不到Payload里的内容。
JWE比普通的JWT复杂一些,性能也差一些,但是如果需要在JWT里存敏感信息,JWE是一个好的选择。
不过,我的建议还是尽量不要在JWT里存敏感信息,JWT里只存必要的身份信息,比如用户ID、角色、权限等,敏感信息还是存在服务端,需要的时候再查,这样更安全,也更灵活。
五、验证JWT的正确姿势
最后,讲讲验证JWT的正确姿势,很多人验证JWT的时候只验证签名,这是不够的,存在安全隐患。正确的JWT验证应该包括以下几个步骤:
- 验证JWT的格式:检查JWT是不是由三部分组成,用点分隔,每部分是不是合法的Base64Url编码。
- 验证签名算法:检查Header里的alg字段是不是你允许的算法,不允许
none算法,防止算法降级攻击。 - 验证签名:用对应的算法和密钥(或者公钥)验证签名,确保JWT没有被篡改。
- 验证过期时间(exp):检查当前时间是不是超过了exp,如果超过了,JWT已经过期,拒绝。
- 验证生效时间(nbf):如果有nbf字段,检查当前时间是不是在nbf之后,如果还没到生效时间,拒绝。
- 验证签发者(iss):检查iss是不是你信任的签发者,如果不是,拒绝。
- 验证受众(aud):检查aud是不是包含你的应用,如果不是,拒绝。
- 验证黑名单(如果用了黑名单方案):检查jti是不是在黑名单里,如果在,拒绝。
- 验证客户端绑定信息(如果用了绑定):检查客户端信息是不是一致,如果不一致,拒绝。
只有以上所有验证都通过了,才认为JWT是有效的,才能信任JWT里的信息。任何一步验证失败,都要拒绝,返回401未授权。
很多安全漏洞都是因为验证不严格导致的,比如只验证签名不验证过期时间,导致过期的JWT还能用;不验证iss和aud,导致其他系统的JWT能访问你的系统;不检查alg,导致算法降级攻击。所以,验证JWT的时候一定要严格,所有该验证的都要验证,不能偷懒。
六、写在最后
JWT令牌配置详解:从基础到高级。
以上就是JWT令牌配置的详细讲解,从JWT的基本结构,到基础配置、签名算法选择、高级配置,再到验证JWT的正确姿势,应该算是比较全面了。JWT是一个很好的身份认证方案,简单、灵活、性能好,但是要用好JWT并不容易,有很多需要注意的地方,很多安全配置都要做好,不然就会有安全隐患。
总结一下最重要的几点:
- JWT一定要设置过期时间,访问令牌有效期短一些,刷新令牌有效期长一些。
- 要设置iss和aud,验证的时候一定要检查,防止跨系统攻击。
- 选择合适的签名算法,对称加密适合单体应用,非对称加密适合微服务和分布式系统,密钥要妥善保管,定期更换。
- 对安全要求高的系统,建议用黑名单方案实现JWT的主动撤销,用Access Token + Refresh Token机制兼顾安全和体验。
- 验证JWT的时候要严格,所有该验证的都要验证,不能只验证签名。
技术是为业务服务的,JWT的配置也要根据业务场景来,没有绝对的最佳配置,只有最合适的配置。对安全要求高的系统,配置就严格一些;对安全要求不高的内部系统,配置可以简单一些。重要的是理解每个配置的作用和风险,根据自己的业务场景做出合理的选择。
希望这篇文章能帮助大家更好地理解和使用JWT,用好JWT,避免安全隐患。如果有什么不对的地方,也欢迎大家指正。
最后用一句话结尾:"安全无小事,配置要谨慎。"愿我们都能做好安全配置,让我们的系统更安全、更可靠。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录