JWT解析
jwt : Json web token,一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
传统session认证
http协议是一种无状态、短连接的协议,而这就意味着如果用户向应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,也就是session。
这份登录信息session会在响应时传递给浏览器,保存在cookie,以便下次请求携带session,这样就能够识别请求来自哪个用户了,这就是传统的基于session认证。
基于session认证暴露的问题
- 每个用户经过认证之后,都需要保存一个session, 如果是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
- 扩展性:如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
- CSRF: cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的认证
不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就有了扩展性、安全性。
流程:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时携带上这个token值
- 服务端验证token值,并返回数据
token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)
策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *
。
JWT:
jwt格式
JWT是由三段信息构成的,将这三段信息文本用.
链接一起就构成了Jwt字符串。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
结构:
header: {"typ":'JWT',"alg":'HS256'}
payload: {"user_id":1, 'username':'xx', 'exp':'超时时间'}
signature: 前两部分加密后拼接,再加密
-
头部header:{'typ':'JWT',"alg":'HS256'}
- 声明类型,jwt
- 声明算法,hs256
然后将头部进行Base64ulr加密,构成第一部分
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
-
载荷payload: {"user_id":1, 'username':'xx', 'exp':'超时时间'} 存放一些有效的信息,不建议有敏感的信息。
-
标准中注册声明(不强制):
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
-
公共声明
一般添加用户的相关信息或其他业务需要的必要信息。
这部分也会进行base64url加密,构成第二部分,例如:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
-
-
签名signature:
这部分会将上面两部分加密后得到的字符串用
.
拼接,然后先进行hs256加密、加盐,再进行base64url加密,组成第三部分,例如:TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
-
然后将上面上部分用
.
连接,就组成了最终的token。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```
当用户认证成功后,就会给浏览器返回一个token字符串,服务端不保存,下次再来请求时,会携带着token:
- 先进性超时验证(ExpriredSignature)
- token合法性校验(通过前两部分加密对比)
优点:
- token只在前端保存,后端只负责校验。
- 内部集成了超时时间,后端可以根据时间进行校验是否超时。
- 由于内部存在hs256加密,所以不可以修改token,只要一修改就认证失败。
- 不需要在服务端保存会话信息, 所以它易于应用的扩展。
缺点:
- token签发后,不能手动使其失效。
jwt在drf中的使用
安装
pip3 install djangorestframework-jwt
setting.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api.apps.ApiConfig',
'rest_framework',
'rest_framework_jwt' # 注册
]
# JWT过期时间设置
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=10), # 10分钟过期
}
用户登录
import uuid
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import URLPathVersioning
from rest_framework import status
from rest_framework_jwt.settings import api_settings
from api import models
class LoginView(APIView):
"""
登录接口
"""
def post(self,request,*args,**kwargs):
# 基于jwt的认证
# 1.去数据库获取用户信息
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
user = models.UserInfo.objects.filter(**request.data).first()
if not user:
return Response({'code':1000,'error':'用户名或密码错误'})
payload = jwt_payload_handler(user) # 产生第二段的字典类型{'user_id':1, 'username':xxx, 'exp':当前时间+过期时间段}
token = jwt_encode_handler(payload) # 加密生成jwt的token(加密、拼接全做)
return Response({'code':1001,'data':token})
用户认证
from rest_framework.views import APIView
from rest_framework.response import Response
# from rest_framework.throttling import AnonRateThrottle,BaseThrottle
import jwt
from rest_framework import exceptions
from rest_framework_jwt.settings import api_settings
class ArticleView(APIView):
# throttle_classes = [AnonRateThrottle,]
def get(self,request,*args,**kwargs):
# 获取用户提交的token,进行一步一步校验
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_value = request.query_params.get('token')
try:
payload = jwt_decode_handler(jwt_value) # 校验
except jwt.ExpiredSignature:
msg = '签名已过期'
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = '认证失败'
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenpythonticationFailed()
print(payload) # 检验后的第二段数据
return Response('文章列表')