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修改之后签名也需要修改,所以需要重新生成一个JWTJWT令牌一般会比较长,如果是性能极度敏感的话需要在意这一点
组成
JWT生成的Token由三部分组成:header.payload.signature,通俗地说,JWT的本质是一个字符串,将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输
headeralg:指定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进行加密payloadjson{ "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/CORShttps://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORSpython实现
依赖项目pyjwt
https://github.com/jpadilla/pyjwthttps://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'}