简介
JWT是一个含签名并携带用户相关信息的加密串,页面请求校验登录接口时,
请求头中携带JWT串到后端服务,后端通过签名加密串匹配校验,保证信息未
被篡改。校验通过则认为是可靠的请求,将正常返回数据。
构成
JWT由三部分组成,分别是头信息header、有效载荷payload、签名signature,中间以(.)分隔
1 | xxx.yyy.zzz |
header
由两部分组成,令牌类型(即:JWT)、散列算法(HMAC、RSASSA、RSASSA-PSS等),例如:
1 | { |
然后,这个JSON被编码为Base64Url,形成JWT的第一部分。
payload
JWT的第二部分是payload,其中包含claims。claims是关于实体(常用的是用户信息)
和其他数据的声明,claims有三种类型: registered, public, and private claims。
1.Registered claims: 这些是一组预定义的claims,非强制性的,但是推荐使用,
iss(发行人), exp(到期时间), sub(主题), aud(观众)等;2.Public claims: 自定义claims,注意不要和JWT注册表中属性冲突,这里可以查看JWT注册表;
3.Private claims: 这些是自定义的claims,用于在同意使用这些claims的各方之间共享信息,
它们既不是Registered claims,也不是Public claims。以下是payload示例:
1
2
3
4
5{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}然后,再经过Base64Url编码,形成JWT的第二部分。
注意
对于签名令牌,此信息虽然可以防止篡改,但任何人都可以读取。除非加密,
否则不要将敏感信息放入到Payload或Header元素中。
Signature
要创建签名部分,必须采用编码的Header,编码的Payload,秘钥,Header中指定的算法,并对其进行签名。
例如,如果要使用HMAC SHA256算法,将按以下方式创建签名:
1 | HMACSHA256( |
签名用于验证消息在此过程中未被篡改,并且,在使用私钥签名令牌的情况下,
它还可以验证JWT的请求方是否是它所声明的请求方。输出是三个由点分隔的
Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,与SAML等基于XML
的标准相比更加紧凑。
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. |
jwt存储方式
localStorage/sessionStorage存储
响应正文将包含JWT作为一个访问令牌:
后台api
1
2
3
4
5
6HTTP/1.1 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB",
"expires_in":3600
}在客户端,你可以将这个令牌存储在HTML5 Web存储中(假设我们有一个成功的回调函数):
1
2
3
4
5
6function tokenSuccess(err, response) {
if(err){
throw err;
}
$window.sessionStorage.accessToken = response.body.access_token;
}回传访问令牌到你受保护的API,你将使用HTTP Authorization Header和Bearer组合。请求你的SPA将会像:
1
2
3
4
5
6HTTP/1.1
GET /xxx
Host: xxx.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsI.
eyJpc3MiOiJodHRwczotcGxlL.
mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB优点
如果在Authorization header中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie,
这样可以用jwt做简单的单点登录,分离出登录系统做sso。
缺点
- xss攻击
Web存储(localStorage/sessionStorage)可以通过同一域上JavaScript访问。
这意味着任何在你的网站上运行的JavaScript都可以访问Web存储,因为这样容易
受到跨站点脚本(XSS)攻击。(最好确定用https发送jwt)
cookie存储
- 后端通过response.set_cookie进行设置
- 回传访问令牌到你同一领域受保护的API,浏览器将自动包括cookie的值。请求你受保护的API将类似于:
1
2
3
4
5GET /xxx
Host: xxx.com
Cookie: access_token=eyJhbGciOiJIUzI1NiIsI.
eyJpc3MiOiJodHRwczotcGxlL.
mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB;优点
Cookies,当使用带有HttpOnly的cookie标志时,通过
JavaScript是无法访问的,并且对XSS是免疫的。你还可
以设置安全的cookie标志来保证cokie仅通过HTTPS发送。
这是过去利用cookie存储令牌或会话数据的主要原因之一。
现代开发人员不愿使用cookie,因为它们通常要求状态被存
储在服务器上,从而打破RESTful的最佳实践。如果你在cookie
上存储JWT,cookie作为存储机制不用将状态存储在服务器上。
这是因为JWT封装了所有服务器需要服务的请求。
缺点
- csrf攻击
cookies容易受到不同类型的攻击:跨站点请求伪造(CSRF)。 - 解决设置cookie同源、同域、path等
jwt的缺点
无法满足注销场景
传统注销清空session即可,因为服务端保存状态。而jwt是无状态的,一旦被生成
在过期时间没到之前都是可用的,虽然注销后可以删除cookie中的jwt,但不代表这个
jwt不可用。
无法满足修改密码场景
修改密码后,盗号者在原jwt有效期内还是可以使用,如果你没有把password加入jwt(应该都不会把)。
无法满足token续签场景
jwt严格定义过期时间,一旦生成,就无法续签延时。
django/flask实操jwt登录
以django为例的jwt使用
大致流程概述
这里其实可以自己手动定制一个简单的jwt,并不复杂,但没必要-vvvvvv-嘿嘿,但还是可以,但……。
- 1.生成payload里面带有user_id,exp(过期时间),username,email,aud,iat(issued at)甚至可以带权限
- 2.调准过期时间,payload用json.dumps编码为字符,并encode为二进制。
- 3.分别对payload,header(也需要dumps、encode一下下)(在setting中配置)进行base64编码
- 4.通过header中的algorithm拿到算法,获取secretkey(alg.prepare_key(key))
再获取signature:alg.sign(signing_input, secretkey) - 5.对signature进行base64,然后把三个段结合在一起。
1
2
3
4
5#默认自带算法
'none': NoneAlgorithm(),
'HS256': HMACAlgorithm(HMACAlgorithm.SHA256),
'HS384': HMACAlgorithm(HMACAlgorithm.SHA384),
'HS512': HMACAlgorithm(HMACAlgorithm.SHA512)
安装和简单使用
1 | pip install djangorestframework-jwt |
jwt_payload_handler生成payload
1 | 注意这里payload的信息 |
jwt_encode_handler获取jwt_token
1 | def jwt_encode_handler(payload): |