JWT基础概念详解
目录
什么是JWT
为什么选择JWT
JWT由什么组成
Header
Payload
Signature
如何基于JWT进行身份认证
如何防止JWT被篡改
JWT身份认证优缺点分析
无状态
有效的避免了CSRF攻击
适合移动端使用
单点登录友好
JWT身份认证常见问题及解决办法
JWT注销登录等场景下还有效
JWT的续签问题
总结
什么是JWT
JWT(JSON Web Token)是目前最流行的跨域认证解决方案,是一种基于Token的认证授权机制。从JWT的全称来看,JWT本身也是Token,一种规范之后的Json结构的Token。
JWT自身包含了身份验证所需要的所有信息,因此,我们服务器不需要存储Session信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
为什么选择JWT
1.简单方便。JWT认证机制不需要像传统的Seesion认证那样在服务端存储任何信息会话,所有的认证和授权信息都包含在JWT中,这种方式简化了认证流程,减少了服务端的负担。
2.安全可靠。JWT使用数字签名来验证其完整性和真实性,确保数据在传输过程中不被篡改。
3.易于扩展。JWT是无状态的,服务端不需要存储用户的会话信息,这使得应用程序宫容易进行水平扩展,这一点很适用于微服务架构。当系统需要大量用户请求时,无状态的认证方式更加适合。
4.支持跨域。JWT认证机制在客户端与服务端进行通信时,客户端会将JWT作为请求的一部分发送给服务器,不依赖浏览的cookie和session,因此不会受到同源策略的限制。这使得它非常适合处理跨域请求。
JWT由什么组成
JWT本质上就是一组字符串,通过.切分成是三个Base64编码的部分:
Header头部:描述了JWT元数据,定义了生成签名的算法以及Token的类型。Header被Base64Url编码后成为JWT的第一部分。
Payload(载荷):用来存放实际需要传递的数据,包含声明(Claims),如sub
(subject,主题)、jti
(JWT ID)。Payload 被 Base64Url 编码后成为 JWT 的第二部分。
Signature(签名):服务器是通过Payload、Header和一个密钥(Secret)使用Header里面指定签名算法(默认是 HMAC SHA256)生成。生成的签名会成为 JWT 的第三部分。
注:第三部分----签名。在解析的过程中不作为直接读取的对象,因为它的作用是验证头部和载荷。签名一般是使用一个密钥和一个哈希算法生成的,过程就是将头部和载荷进行Base64编码。将编码结果与Sercet使用HMCA算法处理,生成一个签名。
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
在一些在线网站上可以对一些jwt进行解码。
Header
1.type:令牌类型,也就是JWT
2.alg:签名算法,比如HS256。
{"alg": "HS256","typ": "JWT"
}
Payload
Payload也是JSON格式数据,其中包含了Claims(声明,包含JWT的相关信息)。
Claims 分为三种类型:
- Registered Claims(注册声明):预定义的一些声明,建议使用,但不是强制性的。
- Public Claims(公有声明):JWT 签发方可以自定义的声明,但是为了避免冲突,应该在 IANA JSON Web Token Registry 中定义它们。
- Private Claims(私有声明):JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用。
下面是一些常见的注册声明:
iss
(issuer):JWT 签发方。
iat
(issued at time):JWT 签发时间。
sub
(subject):JWT 主题。
aud
(audience):JWT 接收方。
exp
(expiration time):JWT 的过期时间。
nbf
(not before time):JWT 生效时间,早于该定义的时间的 JWT 不能被接受处理。
jti
(JWT ID):JWT 唯一标识。
Payload默认是不加密,一定不要将隐私信息存放在 Payload当中!!!
Signature
Signature是对前两部分的签名,作用是防止JWT被篡改。
这个签名需要用:
- Header + Payload。
- 存放在服务端的密钥(一定不要泄露出去)。
- 签名算法。
签名计算公式是如下:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
如何基于JWT进行身份认证
在基于JWT进行身份验证的时候,服务器器通过Payload,Header,secret(密钥)创建JWT并将发送给客户端。客户端收到这个JWT之后,会将其保存在Cookie或者是localStorage里面,以后客户端的所有请求都会携带这个令牌。
如何防止JWT被篡改
有了签名之后,即使JWT被泄露或者截获,黑客也没办法同时篡改Signature,Header,Payload。这是为什么呢?因为服务端拿到JWT之后,会解析出其中包含的Header,Payload以及Singature。服务端会根据Header,Payload,密钥再次生成一个Signature。拿新生成的Signature和JWT中Signature作对比,如果说一样就说明Header和Payload没有被篡改。
不过,如果服务端的密钥也被泄漏的话,黑客就可以同时篡改Signature,Header,Payload了。黑客直接修改了Header和Payload之后,再重新生成一个签名就可以了。
密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。
JWT身份认证优缺点分析
JWT优势
无状态
JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 JWT 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
不过,也正是由于JWT的无状态,也导致了它最大缺点:不可控。
就比如说,我们想要在 JWT 有效期内废弃一个 JWT 或者更改它的权限的话,并不会立即生效,通常需要等到有效期过后才可以。再比如说,当用户 Logout 的话,JWT 也还有效。除非,我们在后端增加额外的处理逻辑比如将失效的 JWT 存储起来,后端先验证 JWT 是否有效再进行处理。具体的解决办法,我们会在后面的内容中详细介绍到,这里只是简单提一下。
有效的避免了CSRF攻击
CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造,属于网络攻击领域范围。相比于 SQL 脚本注入、XSS 等安全攻击方式,CSRF 的知名度并没有它们高。但是,它的确是我们开发系统时必须要考虑的安全隐患。就连业内技术标杆 Google 的产品 Gmail 也曾在 2007 年的时候爆出过 CSRF 漏洞,这给 Gmail 的用户造成了很大的损失
那么究竟什么是跨站请求伪造呢?简单来说就是用你身份去做一些不好的事情
举个简单的例子:小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接,结果发现自己的账户少了 10000 元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。
<a src="http://www.mybank.com/Transfer?bankId=11&money=10000">科学理财,年盈利率过万</a
>
CSRF 攻击需要依赖 Cookie ,Session 认证中 Cookie 中的 SessionID
是由浏览器发送到服务端的,只要发出请求,Cookie 就会被携带。借助这个特性,即使黑客无法获取你的 SessionID
,只要让你误点攻击链接,就可以达到攻击效果。
另外,并不是必须点击链接才可以达到攻击效果,很多时候,只要你打开了某个页面,CSRF 攻击就会发生。
<img src="http://www.mybank.com/Transfer?bankId=11&money=10000" />
那为什么JWT不会存在这种问题呢?
一般情况下我们使用 JWT 的话,在我们登录成功获得 JWT 之后,一般会选择存放在 localStorage 中。前端的每一个请求后续都会附带上这个 JWT,整个过程压根不会涉及到 Cookie。因此,即使你点击了非法链接发送了请求到服务端,这个非法请求也是不会携带 JWT 的,所以这个请求将是非法的。
适合移动端使用
使用session进行身份认证的话,需要保存一份信息在服务器上,而且这种方式会依赖到Cookie(需要Cookie进行保存),所以不适合移动端。
为什么使用 Session 进行身份认证的话不适合移动端 ?
- 状态管理: Session 基于服务器端的状态管理,而移动端应用通常是无状态的。移动设备的连接可能不稳定或中断,因此难以维护长期的会话状态。如果使用 Session 进行身份认证,移动应用需要频繁地与服务器进行会话维护,增加了网络开销和复杂性;
- 兼容性: 移动端应用通常会面向多个平台,如 iOS、Android 和 Web。每个平台对于 Session 的管理和存储方式可能不同,可能导致跨平台兼容性的问题;
- 安全性: 移动设备通常处于不受信任的网络环境,存在数据泄露和攻击的风险。将敏感的会话信息存储在移动设备上增加了被攻击的潜在风险。
单点登录友好
使用Session进行身份认证,实现单点登录,需要我们把用户的Session信息保存在一台电脑上,并且还会遇见Cookie跨域问题,但是使用JWT进行认证的话,JWT被保存在客户端,不会存在这些问题。
为什么存在跨域问题?
是因为浏览器的安全机制限制了Cookie的跨域传输。解决办法是允许跨域。
以SpringBoot配置CORS策略
// Spring Boot示例
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("http://subdomain.example.com", "http://another.domain.example.com").allowedMethods("GET", "POST", "PUT", "DELETE").allowCredentials(true);}
}
JWT身份认证常见问题及解决办法
JWT注销登录等场景下还有效
与之类似的具体相关场景有:
- 退出登录;
- 修改密码;
- 服务端修改了某个用户具有的权限或者角色;
- 用户的帐户被封禁/删除;
- 用户被服务端强制注销;
- 用户被踢下线;
- ……
这个问题不存在于 Session 认证方式中,因为在 Session 认证方式中,遇到这种情况的话服务端删除对应的 Session 记录即可。但是,使用 JWT 认证的方式就不好解决了。我们也说过了,JWT 一旦派发出去,如果后端不增加其他逻辑的话,它在失效之前都是有效的。
1.将JWT存入数据库
将有效的JWT存入数据库中,更建议使用内存数据库比如Redis,如果需要让某个JWT失效就直接从Redis中删除这个JWT即可。但是,这样会导致每次使用JWT都要从Redis中查询JWT是否存在的步骤,而且违背了JWT的无状态的原则。
2.黑名单机制
和上面的方式类似,使用内存数据库比如 Redis 维护一个黑名单,如果想让某个 JWT 失效的话就直接将这个 JWT 加入到 黑名单 即可。然后,每次使用 JWT 进行请求的话都会先判断这个 JWT 是否存在于黑名单中。
前两种方案的核心在于将有效的 JWT 存储起来或者将指定的 JWT 拉入黑名单。
虽然这两种方案都违背了 JWT 的无状态原则,但是一般实际项目中我们通常还是会使用这两种方案
3.修改密钥
我们为每个用户都创建一个专属密钥,如果我们想让某个 JWT 失效,我们直接修改对应用户的密钥即可。但是,这样相比于前两种引入内存数据库带来了危害更大:
- 如果服务是分布式的,则每次发出新的 JWT 时都必须在多台机器同步密钥。为此,你需要将密钥存储在数据库或其他外部服务中,这样和 Session 认证就没太大区别了。
- 如果用户同时在两个浏览器打开系统,或者在手机端也打开了系统,如果它从一个地方将账号退出,那么其他地方都要重新进行登录,这是不可取的。
4.保持令牌的有效期短并经常轮换
很简单的一种方式。但是,会导致用户登录状态不会被持久记录,而且需要用户经常登录。
另外,对于修改密码后 JWT 还有效问题的解决还是比较容易的。说一种我觉得比较好的方式:使用用户的密码的哈希值对 JWT 进行签名。因此,如果密码更改,则任何先前的令牌将自动无法验证。
JWT的续签问题
JWT有效期一般建议设置不太长,那么JWT过期后如何认证,如何实现动态刷新JWT,避免用户经常需要重新登录?
我们先来看看在 Session 认证中一般的做法:假如 Session 的有效期 30 分钟,如果 30 分钟内用户有访问,就把 Session 有效期延长 30 分钟。
1.类似于Session的做法(不推荐)
这种方案满足于大部分场景。假设服务端给的 JWT 有效期设置为 30 分钟,服务端每次进行校验时,如果发现 JWT 的有效期马上快过期了,服务端就重新生成 JWT 给客户端。客户端每次请求都检查新旧 JWT,如果不一致,则更新本地的 JWT。这种做法的问题是仅仅在快过期的时候请求才会更新 JWT ,对客户端不是很友好。
2.每次请求返回新的JWT(不推荐)
这种方案的的思路很简单,但是,开销会比较大,尤其是在服务端要存储维护 JWT 的情况下。
3.JWT有效期设置到半夜(不推荐)
这种方案是一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
4. 用户登录返回两个JWT(推荐)
第一个是 accessJWT ,它的过期时间 JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。refreshJWT 只用来获取 accessJWT,不容易被泄露。
客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。
这种方案不足的是:
- 需要客户端来配合;
- 用户注销的时候需要同时保证两个 JWT 都无效;
- 重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况(可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT);
- 存在安全问题,只要拿到了未过期的 refreshJWT 就一直可以获取到 accessJWT。不过,由于 refreshJWT 只用来获取 accessJWT,不容易被泄露。
总结
JWT 其中一个很重要的优势是无状态,但实际上,我们想要在实际项目中合理使用 JWT 做认证登录的话,也还是需要保存 JWT 信息。
JWT 也不是银弹,也有很多缺陷,具体是选择 JWT 还是 Session 方案还是要看项目的具体需求。万万不可尬吹 JWT,而看不起其他身份认证方案。