修真院技术分享

八个方面深度解析后端知识/技能,本篇分享的是:

【JWT简单介绍。】

大家好,我是IT修真院郑州分院第11期的学员何爽,一枚正直纯洁善良的后端程序员,今天给大家分享一下,修真院官网java(职业)任务5,深度思考中的知识点——JWT简单介绍。

1.背景介绍

由于微服务大都是分布式的,需要几台服务器部署,当一个用户在其中一台服务器登录后,传统的方式是session保存其登录信息,然后可以使用共享存储共享,比如redis共享,这种方案的缺点在于共享存储需要一定保护机制,因此需要通过安全链接来访问,这时解决方案的实现就通常具有相当高的复杂性了,所以这里使用基于令牌的方式做登录。

什么是JWT?

JWT(JSON WEB TOKEN):JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。

它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。

广义上讲JWT是一个标准的名称;狭义上JWT指的就是用来传递的那个token字符串

2.知识剖析

JWT的结构:

JWT包含了使用.分隔的三部分: Header 头部 Payload 负载 Signature 签名
其结构看起来是这样的Header.Payload.Signature
JWT 的 header 中承载了两部分信息

{
“alg”: “RS256”,
“typ”: “JWT”
}
alg: 声明加密的算法
typ: 声明类型
对这个头部信息进行 base64,即可得到 header 部分

payload 是主体部分,意为载体,承载着有效的 JWT 数据包,它包含三个部分

标准声明
公共声明
私有声明

标准声明的字段

interface Stantar {
iss?: string; // JWT的签发者
sub?: string; // JWT所面向的用户
aud?: string; // 接收JWT的一方
exp?: number; // JWT的过期时间
nbf?: number; // 在xxx日期之间,该JWT都是可用的
iat?: number; // 该JWT签发的时间
jti?: number; //JWT的唯一身份标识
}

公共声明的字段

interface Public {
[key: string]: any;
}
公共声明字段可以添加任意信息,但是因为可以被解密出来,所以不要存放敏感信息。

私有声明的字段

interface Private {
[key: string]: any;
}
私有声明是 JWT 提供者添加的字段,一样可以被解密,所以也不能存放敏感信息。

signature(签名)

把header和payload对应的json结构进行base64url编码之后得到的字符串用点号拼接起来,然后根据header里面alg指定的签名算法生成出来的,然后添加自己设定的key进行加密签名。

JWT设计单点登录系统过程:

用户认证八步走

所谓用户认证(Authentication),就是让用户登录,并且在接下来的一段时间内让用户访问网站时可以使用其账户,而不需要再次登录的机制。

首先,服务器应用(下面简称“应用”)让用户通过Web表单将自己的用户名和密码发送到服务器的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。

接下来,应用和数据库核对用户名和密码。

核对用户名和密码成功后,应用将用户的id(图中的user_id)作为JWT Payload的一个属性,将其与头部分别进行Base64编码拼接后签名,形成一个JWT。这里的JWT就是一个形同lll.zzz.xxx的字符串。

应用将JWT字符串作为该请求Cookie的一部分返回给用户。注意,在这里必须使用HttpOnly属性来防止Cookie被JavaScript读取,从而避免跨站脚本攻击(XSS攻击)。

在Cookie失效或者被删除前,用户每次访问应用,应用都会接受到含有jwt的Cookie。从而应用就可以将JWT从请求中提取出来。

应用通过一系列任务检查JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。

应用在确认JWT有效之后,JWT进行Base64解码(可能在上一步中已经完成),然后在Payload中读取用户的id值,也就是user_id属性。这里用户的id为1025。

应用从数据库取到id为1025的用户的信息,加载到内存中,进行ORM之类的一系列底层逻辑初始化。

应用根据用户请求进行响应。

3.常见问题

为什么使用JWT

4.解决方案

(1)前后端分离

以前的传统模式下,后台对应的客户端就是浏览器,就可以使用session+cookies的方式实现登录,但是在前后分离的情况下,后端只负责通过暴露的RestApi提供数据,而页面的渲染、路由都由前端完成。因为rest是无状态的,因此也就不会有session记录到服务器端。

(2)传统方式带来的安全性问题

(3)性能问题

如果将验证信息保存在数据库中,后端每次都需要根据token查出用户id,这就增加了数据库的查询和存储开销

5.编码实战

6.扩展思考

为什么不推荐用JWT做单点登录我这里却做了单点登录的介绍?

通常因为续签问题导致我们不推荐使用JWT做单点登录,但续签问题可以通过以下的方案进行解决:

每次请求刷新 jwt

只要快要过期的时候刷新jwt

完善 refreshToken

使用 redis 记录独立的过期时间

用JWT做单点登录的优点:

Session方式来存储用户id,一开始用户的Session只会存储在一台服务器上。对于有多个子域名的站点,每个子域名至少会对应一台不同的服务器,例如:

www.taobao.comnz.taobao.comlogin.taobao.com

所以如果要实现在login.taobao.com登录后,在其他的子域名下依然可以取到Session,这要求我们在多台服务器上同步Session

使用JWT的方式则没有这个问题的存在,因为用户的状态已经被传送到了客户端。因此,我们只需要将含有JWT的Cookie的domain设置为顶级域名即可,例如

Set-Cookie: jwt=lll.zzz.xxx; HttpOnly; max-age=980000; domain=.taobao.com

注意domain必须设置为一个点加顶级域名,即

.taobao.com。这样,taobao.com和*.taobao.com就都可以接受到这个Cookie,并获取JWT了.

7.参考文献





8.更多讨论

Q1:提问人:周宏浩:

JWT Token需要持久化在Memcached中吗?

A1:回答人(何爽):

这个看业务需求,如果需要长期保存用户的信息那就要持久化,如果只是短时间甚至一次性的用户信息验证那就不需要做持久化。

Q2:提问人:张亚强

感觉jwt的加密只是又增加了des的加密,造成了双重加密,而不是改变了jwt头部的base64加密吧

A2:回答人(何爽):

这个确实是改变了头部的加密方式,将头部的base64加密换成了des加密方式,因为des加密在当前是一种几乎不可能被破解的加密方式。

Q3:提问人:周宏浩

服务器端是否应该从JWT中取出userid用于业务查询?

A3:回答人(何爽):

需要,我们利用JWT的时候就将userid放进了jwt的头部,因此当我们需要用到userid的时候就取出来用于业务的需求。

9.鸣谢

感谢张强,翁涵师兄,此教程是在他们之前技术分享的基础上完善而成。

10.结束语

今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~