JWT
全称JSON Web Token
应用流程
- 客户端使用用户名和密码请求登录,服务端收到请求
- 验证用户名和密码正确后,后端通过
JWT
机制,将用户数据作为JWT
的Payload
,同时在前面拼接上一个JWT Header
之后进行Base64
编码,并进行签名,生成一个token
,格式为header.payload.signature
,返回给客户端 - 客户端后续的每次请求都需要携带
token
,携带在HTTP Header Authorization
中 - 后端拿到客户端传递的
token
后,进行解密验证身份,验证其有效性,检查签名是否正确,是否过期,最后解析出JWT Token
中的用户信息
优点
无状态,
token
机制在服务端不需要存储session
信息,因为token
自身包含了所有登录用户的信息,所以可以减轻服务端压力分布式友好,由于
session
需要固定保存在一个地方,如果保存在本机,分布式系统中认证会失效,如果采用redis
等统一保存session
,系统复杂性会增加支持跨域访问,跨域后不会存在信息丢失问题
CDN
友好,可以通过内容分发网络请求服务端的所有资料移动端友好,当客户端是非浏览器平台时,
cookie
是不被支持的,此时采用token
认证方式会简单很多无需考虑
CSRF
(Cross Site Request Forgery
跨站点请求伪造),token
是开发者为了防范csrf
而特别设计的令牌,浏览器不会自动添加到headers
里,攻击者也无法访问用户的token
,所以攻击者提交的表单无法通过服务器认证,也就无法形成攻击CSRF
简述- 在一个浏览器中打开了两个标签页,其中一个页面通过窃取另一个页面的
cookie
来发送伪造的请求,因为cookie
是随着浏览器请求自动发送到服务端的,这个是CSRF
攻击成功的核心原因 session
认证本质需要依赖Cookie
,如果cookie
被截获,用户很容易受到跨站请求伪造攻击CSRF
无法直接窃取到用户的Cookie,header
,仅仅是冒用Cookie
- 在一个浏览器中打开了两个标签页,其中一个页面通过窃取另一个页面的
缺点
- 不可控,由于
JWT
无状态,想要在JWT
有效期内废弃一个JWT
或者更改它的权限的话,并不会立即生效,通常需要等到有效期过后才可以,如果要避免这个问题,需要把JWT
存入redis
等缓存,JWT
失效的时候就删除这个JWT
,每次验证的时候查询一下JWT
在不在redis
,但是这样就增加了成本和系统复杂度 token
续签不方便,JWT
本身payload
参数当中携带exp
参数表示过期时间,payload
修改之后签名也需要修改,所以需要重新生成一个JWT
JWT
令牌一般会比较长,如果是性能极度敏感的话需要在意这一点
组成
JWT
生成的Token
由三部分组成:header.payload.signature
,通俗地说,JWT
的本质是一个字符串,将用户信息保存到一个Json
字符串中,然后进行编码后得到一个JWT token
,并且这个JWT token
带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json
对象传输
header
alg
:指定signature
采用的加密算法,默认是HS256(HMAC SHA256)
,对称加密(加密和解密的密钥相同)typ
:固定值,通常是JWT
- 通常值是
{"alg": "HS256", "typ": "JWT"}
, 通过base64Url
算法进行编码之后进行拼接
payload
用户
id
和name
默认携带
iat
,令牌签发时间(时间戳)exp
设置令牌过期时间参数一般形式如下,通过
base64Url
算法进行编码与header
进行拼接,默认情况下JWT
是未加密的,因为只是采用base64
算法,拿到JWT
字符串后可以转换回原本的JSON
数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT
中,JWT
只是适合在网络中传输一些非敏感的信息,要传递一些敏感数据的话需要使用一些AES
或者其他类型的算法,尽量加上一些salt
进行加密payload
json{ "sub": "1234567890", "name": "Helen", "admin": true }
{ "sub": "1234567890", "name": "Helen", "admin": true }
signature
- 设置一个
secretKey
,通过将前两个结果合并后进行HS256
算法,signature = HS256(base64Url(header)+'.'+base64Url(payload),secretKey)
secreKey
一定不能暴露,因为可以颁发token
,也可以解密
- 设置一个
跨域资源共享
是一种基于 HTTP
头的机制,该机制主要是为了避免跨站脚本攻击而存在
实际网站开发过程当中需要从A
网站访问到另外一个网站B
,就需要通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源,服务器需要返回一个HTTP Header Access-Control-Allow-Origin: *
表明,该资源可以被任意外源访问,这样浏览器才会正常加载返回的数据
但是当携带cookie
进行访问的时候就不能返回一个Header
头表示允许被任意外源访问
- 服务器不能将
Access-Control-Allow-Origin
的值设为通配符“*
”,而应将其设置为特定的域,如:Access-Control-Allow-Origin: https://example.com
,如果值被设置为*
,请求会失败,如歌设置为具体的域请求成功 - 服务器不能将
Access-Control-Allow-Headers
的值设为通配符“*
”,而应将其设置为标头名称的列表,如:Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
- 服务器不能将
Access-Control-Allow-Methods
的值设为通配符“*
”,而应将其设置为特定请求方法名称的列表,如:Access-Control-Allow-Methods: POST, GET
详情参考Mozilla
对跨域资源共享的定义
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
python
实现
依赖项目pyjwt
https://github.com/jpadilla/pyjwt
https://github.com/jpadilla/pyjwt
安装
$ pip install pyjwt
$ pip install pyjwt
使用
>>> import jwt
>>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
>>> print(encoded)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
>>> jwt.decode(encoded, "secret", algorithms=["HS256"])
{'some': 'payload'}
>>> import jwt
>>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
>>> print(encoded)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
>>> jwt.decode(encoded, "secret", algorithms=["HS256"])
{'some': 'payload'}