token介绍,jwt原理介绍,base64编码和解码,drf-jwt使用
Cookie,session,token介绍
token分为三段式:
- 第一段是
header
:公司信息,加密方式等 - 第二段是
payload
:真正的数据,存放用户信息,如:{'name':'xxx','id'='1'}
- 第三段是
signature
:签名,通过第一段和第二段,通过某种加密方式加密得到的,如:awedsdada
token的使用分为两个阶段
- 登录成功后的
签发token
阶段,生成三段 - 登录成功访问某个接口的
验证阶段
,验证token是否合法
cookie是:存在客户端浏览器的键值对
session是:存在于服务端的键值对
token是:三段式,服务端生成的,存放在客户端(浏览器就放在cookie中,移动端:存在移动端中)
使用token的认证机制,服务端就不需要保存数据了,token是服务端生成的,客户端保存,服务端不存储
jwt原理介绍
jwt认证由来
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不在使用session认证机制,而使用json web token(本质就是token)认证机制。
jwt定义
'Json web token (JWT)', 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
jwt构成
header
jwt的头部承载两部分信息:
- 声明类型,这里是jwt
- 声明加密算法,通常直接使用HMAC SHA256
- [有些公司的项目会把公司信息写进去]
完整的头部就像下面这样的JSON:
{
"typ":"JWT",
"alg":"HS256"
}
然后将头部进行base64编码,构成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload
荷载就是存放有效信息的地方(用户信息),一般存放格式如下:
exp:jwt的过期时间,这个过期时间必须要大于签发时间
iat:jwt的签发时间
用户信息:用户信息
{
"exp":"1233424224",
"name":"xxx",
"userid":1
}
然后对其进行base64编码,构成了第二部分
eyJleHAiOiAiMTIzMzQyNDIyNCIsICJuYW1lIjogInh4eCIsICJ1c2VyaWQiOiAxfQ==
signature
签名:把头和荷载加密后得到的
a = {
"typ": "JWT",
"alg": "HS256"
}
b=base64.b64encode(bytes(json.dumps(a),'utf8'))
c = {
"exp": "1233424224",
"name": "xxx",
"userid": 1
}
d=base64.b64encode(bytes(json.dumps(c),'utf8'))
import hashlib
md5=hashlib.md5()
md5.update(c+d+b'salt') # 加盐
res=md5.hexdigest() # 00a6ce8db09917e3b839c70f934be640
注意
secret是保存在服务端的(加密方式+盐),jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证。所以,它就是服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret,那就意味着客户端是可以自我签发jwt了。
jwt使用流程核心
- 签发:登录接口签发
- 认证:认证类认证
base64编码和解码
编码
import base64
import json
a = {
"exp": "1233424224",
"name": "xxx",
"userid": 1
}
b=base64.b64encode(bytes(json.dumps(a),'utf8'))
解码
c=b'eyJleHAiOiAiMTIzMzQyNDIyNCIsICJuYW1lIjogInh4eCIsICJ1c2VyaWQiOiAxfQ=='
d=base64.b64decode(c)
应用场景
- jwt中使用
- 网络中传输字符串就可以使用base64编码
- 网络中传输图片,也可能使用base64的编码
drf-jwt快速使用
jwt的签发在登录接口,认证在认证类
django中使用jwt
- 自己写
https://github.com/jpadilla/django-rest-framework-jwt
(比较老)https://github.com/jazzband/djangorestframework-simplejwt
(比较新)
安装
pip3.8 install djangorestframework-jwt
快速使用
- 迁移表,因为它默认使用auth的user表签发token
- 创建超级用户(auth的user表中要有记录)
- 不需要再写登录接口了,如果是使用auth的user表作为用户表,它可以快速签发
- 签发(登录):只需要再路由中配置(因为它帮我们写好登录接口了)
# 使用步骤 以上1,2步完成后
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/',obtain_jwt_token),
]
认证(认证类):导入,配置在视图上
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class TestView(APIView):
authentication_classes = [JSONWebTokenAuthentication,]
# 使用jwt提供的认证类,必须配合权限类。否则无法生效
permission_classes = [IsAuthenticated,]
前端访问时,token需要放在请求头中
Authorization:jwt 'token串'
drf-jwt修改返回格式
因为登录成功后,后端看到的格式,太固定了,只有token,我们想做成符合restful规范的响应格式
{
'code':100,
'msg':'登录成功',
'token':'xxxadadsdasqw'
}
使用步骤
自定义函数
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code':100,
'msg':'登录成功',
'username':user.username,
'token':token,
}
把函数配置在配置文件中
JWT_AUTH={
'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.auth.jwt_response_payload_handler'
}
以后登录接口返回的格式就是我们写的函数的返回值,有源码可知
自定义user表,签发token
自定义userinfo表
class UserInfo(models.Model):
username=models.CharField(max_length=32)
password=models.CharField(max_length=32)
自定义认证类,并重写返回response函数
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.utils import jwt_decode_handler
import jwt
from django.utils.translation import gettext_lazy as _
from rest_framework import exceptions
class LoginAuthentication(JSONWebTokenAuthentication):
def jwt_get_use_id_from_payload(self,payload):
return payload.get('user_id')
def authenticate_credentials(self, payload):
pk = self.jwt_get_use_id_from_payload(payload)
if not pk:
raise exceptions.AuthenticationFailed('不合法的荷载')
return UserInfo.objects.filter(pk=pk).first()
def authenticate(self, request):
jwt_value = self.get_jwt_value(request)
if jwt_value is None:
raise exceptions.AuthenticationFailed('请先登录再来')
try:
payload = jwt_decode_handler(jwt_value)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed('token解析错误')
user = self.authenticate_credentials(payload)
return user, jwt_value
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code':100,
'msg':'登录成功',
'username':user.username,
'token':token,
}
写一个登录接口,签发token
from .models import UserInfo
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from app01.auth import jwt_response_payload_handler
from rest_framework.exceptions import APIException
from datetime import datetime
class UserView(APIView):
def post(self,request):
username=request.data.get('username')
password=request.data.get('password')
user=UserInfo.objects.filter(username=username,password=password).first()
if user:
# 通过user对象生成荷载
payload = jwt_payload_handler(user)
# 通过荷载生成token
token = jwt_encode_handler(payload)
# 利用重写方法修改返回格式
response_data = jwt_response_payload_handler(token, user,request)
# 生成response对象,后,要通过response对象发送token给浏览器,后续就不需要校验
response=Response(response_data)
# JWT_AUTH_COOKIE是浏览器存放在cookie的键,配置文件默认为None,自己定义一个
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
token,
expires=expiration,
httponly=True)
return response
else:
raise APIException('用户名或密码错误')
这样,在token未过期时,再访问服务端,就不需要再响应头配置token参数了,我们自定义的认证类会通过
jwt_value = self.get_jwt_value(request)
方法,从请求中拿出token。