JWT (Json Web Token)
背景
互联网服务离不开用户认证,。由于http是无状态的,对事务处理没有记忆能力。每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的。意味着我们通过浏览器输入账户密码登录某个用户,服务端验证通过后。浏览器下一个请求继续访问服务器时,并不知道当前的用户是谁,是否验证通过,除非每次请求都携带账户密码进行验证(HTTP Basic Auth)。而这种方式有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。
认证机制
基于Session+Cookie的认证机制
在传统的用户登录系统中,一般采用Session + Cookie的模式来进行用户验证:
客户端向服务器发送用户名和密码;
服务器验证通过后,生成Session信息,来保存用户相关信息;
随即服务器将Session的session_id携带在用户请求的回复中,客户端将其写入Cookie中;
随后客户端的每一次请求,都会通过Cookie将session_id传回服务器;
服务器通过收到的session_id,找到对应的session信息,从而获取用户相关信息。
当然这种模式也存在着一些问题:
session是存储在服务器上的,会占用少了内存,如果网站用户非常多的话,一方面服务器内存压力大,另一方面大量session的存取也会影响服务器的性能。
session是保存在当前服务器上的,如果是服务器集群,或者是跨域的服务导向架构,就必须要求seesion数据共享。
session是基于cookie进行识别的,容易被CSRF跨站请求伪造拦截。
基于Token的认证机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息:
客户端向服务器发送用户名密码;
服务器对用户名密码进行验证,验证通过返回一个签名过的token;
客户端收到返回的token并保存;
后续的每次的去请求中,客户端会将token作为http header发送给服务器;
服务器只需验证token是否有效。
那么这种机制有什么好处呢?
支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
无状态(也称:服务端可扩展性):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.
去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).
JWT
什么是JWT
JWT就是建立在基于Token的认证机制上的一种规范。JWT (Json Web Token)是一种基于JSON格式的开放标准(RFC 7519),它定义了一种简洁的、自包含的用于通信双方之间以JSON对象的形式安全传递信息的方法。JWT常用于身份提供者和服务提供者之间的身份验证。例如用户登录验证。
JWT原理
JWT的原理是,服务器认证之后,返回给客户端的token是一个JSON对象,就像这样:
1 | { |
之后,客户端发送给服务器的请求,都要携带这个json对象的token。服务器依靠token完成用户认证。
为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
JWT的组成
实际上的JWT编码后的样子是:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiend6IiwiYWdlIjoiMTgifQ.UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q它是一个很长的字符串,中间由点(.)分隔成三个部分:
- Header(头部);
- Payload(负载);
- Signature(签名);
Header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子:
1 | { |
alg
属性表示签名的算法(algorithm),默认是HMAC SHA256
(写成 HS256);typ
属性表示这个令牌(token)的类型(type),JWT
令牌统一写为JWT
。最后,将
JSON
对象使用Base64URL
算法转成字符串,变成JWT
字符串的Header
部分。
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据,包含三个部分:
标准中注册的声明 (建议但不强制使用):
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
- 公共的声明。公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.;
- 私有的声明。私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:
1 | { |
通用将其进行Base64URL
加密,得到JWT的第二部分。
Signature
Signature
部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。secret
是保存在服务器端的(配置文件里),jwt的签发生成也是在服务器端的,secret
就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。
然后使用Header
里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名:
1 | HMACSHA256( |
也就是jwt的第三个部分。
最后,将把 Header、Payload、Signature 三个部分拼成一个字符串组成,每个部分之间用”点”(.)分隔,组成完整的jwt,就可以返回给客户端了。
JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization
字段里面。
1 | Authorization: Bearer <token> |
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
JWT优点
因为json的通用性,所以JWT是可以进行跨语言支持的。
因为有了payload部分,所以JWT可以在自定义一些参数,非敏感信息。
便于传输,JWT的构成非常简单,字节占用很小,所以它是非常便于传输的。
JWT不需要在服务端保存会话信息, 易于应用的扩展。
JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
JWT缺点
JWT 默认是不加密,不能将私密信息写入JWT。
由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,建议使用 HTTPS 协议传输。