JWT简介:
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
与传统的session的区别:
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.
基于session认证所显露的问题:
Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录(保存他的会话状态),以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
在一个商城用户登录的业务逻辑中, 用户进行注册登录,我们需要保存他的登录的状态,之前是用session来进行保存;需要在客户端的cookie中保存session_id ,后端同样保存session_id : 用户信息, 服务会占用额外的空间;
在前后端分离的应用场景中,有时候不只是pc端的浏览器,还有app的浏览器,app有可能没有办法保存cookie;在一些公共的环境进行登录的时候, 我们关闭浏览器,可是他的session_id 的有效期还在, name就还需要在服务器保留一段时间,直到有效期结束才能释放资源;
CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
现在我们要引入JWT机制来进行保存,
基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)
策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *
。
那么我们现在回到JWT的主题上。
JWT的构成
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
他的工作流程:
当用户进行注册成功的时候,JWT机制会给他进行签发token,返回返回给客户端,token是通过header,payload(userid),的两部分进行base64的加密,然后加上我们自己设定的一个secret的字符串,进行再次加密,这次加密是header中声明的加密算法(HS256),构造成一个签名信息sign,由这三部分构成加密信息,随着这次的请求直接返回给我们的客户端,服务器不用进行保存,我们只需要保存secret这个秘钥就好;
当用户下次在进行访问的时候,会带上我们给他的token信息,然后我们先将前两部分进行对应的解密,然后将解密的header,payload,加上secret再次进行解密,解密完成后对比用户传递给我们的token信息是否一致,如果不一致,那么将这写信息全部丢掉,如果一致,那么服务器拿着解密的数据返回给响应的结果;
从这可以看出JWT是有着签发和验证的两个功能;
我们在验证完用户的身份后(检验用户名和密码),需要向用户签发JWT,在需要用到用户身份信息的时候,还需核验用户的JWT。
关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。
安装 : pip install djangorestframework-jwt ;
配置 :
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
JWT_AUTH = {
# 指明token的有效期
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
Django REST framework JWT 扩展的说明文档中提供了手动签发JWT的方法
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
我们可以将JWT保存在cookie中,也可以保存在浏览器的本地存储里,我们保存在浏览器本地存储中
浏览器的本地存储提供了sessionStorage 和 localStorage 两种:
sessionStorage 浏览器关闭即失效
localStorage 长期有效
使用方法 :
sessionStorage.变量名 = 变量值 // 保存数据
sessionStorage.变量名 // 读取数据
sessionStorage.clear() // 清除所有sessionStorage保存的数据
localStorage.变量名 = 变量值 // 保存数据
localStorage.变量名 // 读取数据
localStorage.clear() // 清除所有localStorage保存的数据
他的前端js 的编写方式:
var vm = new Vue({
...
methods: {
...
on_submit: function(){
axios.post(...)
.then(response => {
// 记录用户的登录状态
sessionStorage.clear();
localStorage.clear();
localStorage.token = response.data.token;
localStorage.username = response.data.username;
localStorage.user_id = response.data.user_id;
location.href = '/index.html';
})
.catch(...)
}
}
})