欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

什么是 JWT -- JSON WEB TOKEN 有大用 有大大用 有大大大用

什么是JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

起源

说起JWT,我们应该来谈一谈基于token的认证和传统的session认证的区别。

传统的session认证

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.

基于session认证所显露的问题

Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

基于token的鉴权机制

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程上是这样的:

  • 用户使用用户名密码来请求服务器

  • 服务器进行验证用户的信息

  • 服务器通过验证发送给用户一个token

  • 客户端存储token,并在每次请求时附送上这个token值

  • 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *

那么我们现在回到JWT的主题上。

JWT长什么样?

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT的构成

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt

  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

playload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明

  • 公共的声明

  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者

  • sub: jwt所面向的用户

  • aud: 接收jwt的一方

  • exp: jwt的过期时间,这个过期时间必须要大于签发时间

  • nbf: 定义在什么时间之前,该jwt都是不可用的.

  • iat: jwt的签发时间

  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)

  • payload (base64后的)

  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

如何应用

一般是在请求头里加入Authorization,并加上Bearer标注:

fetch('api/user/1', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:


jwt-diagram

总结

优点

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。

  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。

  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。

  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

安全相关

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。

  • 保护好secret私钥,该私钥非常重要。

  • 如果可以,请使用https协议

随笔

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!

赞赏支持
登录 后发表评论
lowkey2046
16楼 · 2017.04.10 22:41

`然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.`

更正一下,base64 不是加密,只是对字符进行编码。 :no_mouth:

 
周星星的星
39楼 · 2017.09.25 18:22

不错不错,收藏了。

推荐下,源码圈 300 胖友的书单整理:http://t.cn/R0Uflld


 
liunewshine
19楼 · 2017.05.09 09:18

我总算看明白了,JWT解决的不是数据传输安全,这是https,ssl等解决的问题,JWT解决的是服务器端不用存储TOKEN或者SESSION,因为根据JWT就能获取到用户信息,所以貌似用处不大,服务端维护token也耗费不了多少性能

BruceMA_47e5: 看使用场景,在多应用单点登录保持用户一致性效果很好,再也不用去保存用户的回话了

2017.05.14 22:39 回复

孙沛2010: 还是没明白,服务器生成token是不是要储存起来?还是要存的,不存怎么验证

2017.05.19 14:58 回复

liunewshine@孙沛2010 不用存储也可以的,验证签名正确的话就取出userid,存储了干嘛

2017.05.19 20:07 回复
添加新评论 还有5条评论, 展开查看
肖少清
29楼 · 2017.07.07 21:33

要是别人拿到你的TOKEN是不是意味着可以伪造你的身份

紫陌轻风_4865: 我觉得也是,所以token的安全性要通过https来保证

2017.07.17 00:01 回复

肖少清: 貌似就算是加上https也避免不了,别人要是截取的是你非对称加密后的数据是不是意味着还是可以伪造

2017.07.17 21:12 回复

biaoqianwo: 我也有次问题,token被别人用了,就不安全了啊。安全是怎么保证的呢?

2017.07.20 11:40 回复
添加新评论 还有3条评论, 展开查看
呆若牦牛
11楼 · 2017.03.16 12:47

解释的最清楚的一片文章:)

 
长孙门客
25楼 · 2017.06.08 15:44

有一个问题,secret私钥是如何生成的?保存在哪?如何保存?

kaoji: 自定义的

2017.06.28 16:08 回复

biaoqianwo: 可以和用户名、密码一样保存在数据库中,作为用户的一个属性存在

2017.07.20 11:39 回复

iBlocks@biaoqianwo 这不就是多此一举了吗,每次访问还需要到数据库里面取出secret解密一下,还不如用session来快,secret是系统管理员定义的全局密码,所有用户的token都是用这个secret加密的,理论上要想获得secret一种是尝试暴力破解这个token获得secret但可能需要几百年,另一种就是攻破服务器。如果系统安全做得更完善的话,这个secret需要定期更换,以免被骇客撞死猫碰对了。

2017.07.29 17:33 回复
添加新评论 还有2条评论, 展开查看
疯不息
2楼 · 2016.04.23 20:05
 
小阳撒
3楼 · 2016.04.23 22:30

:+1:楼主写的很不错。最后安全那可以使用jwe加上rsa加密。这样会安全点。

尼古拉斯松桑: 你意思是secret部分用rsa加密?

2017.04.07 15:04 回复
dongwenbo
4楼 · 2016.04.23 22:37

不错,学习了,武装到牙齿

 
choukin
5楼 · 2016.12.02 18:35

服务端如何验证jwt?

l3n641: 已经知道 header +‘.’+playload 加密得到第三部分内容。然后一起返回给客户端。客户端发送请求数据的时候会带上jwt。服务端根据 header 里面加密算法和plaload。生成第三部分内容。然后对比 第三部分内容。

2017.01.09 09:21 回复

FtdConcept@l3n641 没看懂? 是这样吗,服务器先生成jwt,然后客户端拿着第三部分,和客户端自己的第一和第二部分再发到服务端,服务端再根据客户端的第一、第二部分,算出第三部分再和客户端传过来的第三部分比较?

2017.06.26 15:24 回复

l3n641@FtdConcept 嗯。是的就是这个道理。

2017.06.26 18:50 回复
余生正清秋
6楼 · 2017.01.13 08:13

楼主 请问,用这种方法,我怎么才能在服务端上验证呢?我对比?还是存库再进行对比?

Koche: 不需要存库。jwt的payload中可以存放用户名。服务端拿到jwt之后,从中解析出用户名,然后根据用户名验证。

2017.07.29 10:03 回复

余生正清秋@Koche 都半年过去了。我会了:sweat::sweat::sweat:

2017.07.29 10:05 回复

Koche@北小余 :relaxed: 没有注意看时间。我的我的

2017.07.29 10:09 回复
添加新评论 还有1条评论, 展开查看
needrunning
7楼 · 2017.01.14 11:44

这个文章不错。 通过token 鉴别用户和权限。 通过sign保证api时效性和参数完整性。 token和sign都做了 基本上可以算是完整的安全机制了。 主流的平台验证都是基于这两方面的扩展

 
月光下的小手
8楼 · 2017.02.15 10:07

不错哦

 
yljphp
9楼 · 2017.02.15 18:59

不错的文章

 
蕲婼圵渁
10楼 · 2017.03.13 14:00



没理解这有什么卵用?
假如有一个转账的操作,user-a 转账给 user-b, 在传输的过程中被拦截到后,把 user-b 改成 user-c,token原封不动的发送过去,
这样就变成了 user-a 转账给了 user-c了

fetch('api/transaction', {
headers: {
'Authorization': 'Bearer ' + token
}
body: {
'from': 'user-a',
'to': 'user-b',
'amount': 500
}
})

baiwenl: jwt内容分三段,前两段都可以解析并被客户端修改,但是只要被修改过,那么发送到服务器后重新通过前两段计算出的第三段内容就和用户传递过来的不匹配了,这个jwt就废了。别说修改第三段,除非你先把服务器上用来加密的secret弄到手。

2017.04.13 16:04 回复

Henizyang: 通过接受到的请求的第1 2段加密得到第三段和以前正确的第三段对比。

2017.04.21 11:13 回复

蕲婼圵渁@baiwenl
看清楚了,我说的第一段,第二段都没有变化,token也没有变化,被篡改的是发送到服务器的内容



作者:Dearmadman
链接:http://www.jianshu.com/p/576dbf44b2ae
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

来自 http://www.jianshu.com/p/576dbf44b2ae


普通分类: