标签:ken from pac ota 负载 序列化 enc ade 记录
官网:https://github.com/jpadilla/django-rest-framework-jwt
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
1 token: - 加密的串:有三段 Header.Payload.Signature ? - 头.载荷(数据).签名 JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。 就像这样: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ - 使用bsse64转码 - 第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
JWT如何保证安全
""" 1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中 ? 2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以
请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户 ? 注:登录接口需要做 认证 + 权限 两个局部禁用 """
简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快。 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库。 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何违背形式都支持。 不需要在服务端保存会话信息,特别适用于分布式微服务。
1,头部(Header)
2,载荷(Payload)
3,签名(Signature)
因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz Header.Payload.Signature
一般存放如何处理token的方式:加密的算法、是否有签名等
标头通常由两部分组成:
声明类型,这里是JWT
声明加密的算法 通常直接使用 ,例如:HMAC SHA256和RSA。
它会使用Base64编码组成JWT结构的第一部分。 注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
完整的头部就像下面这样的JSON:
{ “typ”:“JWT” "alg":“HS256”, }
数据的主体部分:用户信息、发行者、过期时间等
令牌的第二部分是有效载荷,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64编码组成JWT结构的第二部分
{ "sub":“1234567890”, “name”:“John Doe”, “admin”:true }
将header、payload再结合密码盐整体处理一下
前面两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息。Signature需要使用编码后的header和payload以及我们提供的一个密钥,然后使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过
如: HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret);
最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形式的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
加码和解码
base64转码跟语言无关,任何语言都能编码,解码base64
bas64长度必须是4的倍数,如果不足,要用=补齐,最多使用===等号
# 导入python内置模块base64 import base64 import json dic = {‘id‘: 1, ‘name‘: ‘lqz‘} ? str_dic=json.dumps(dic) print(str_dic) ? # base64加码 res=base64.b64encode(str_dic.encode(‘utf-8‘)) print(res) ? ? ## base64解码 # res=base64.b64decode(b‘eyJpZCI6IDEsICJuYW1lIjogImxxeiJ9‘) # res=base64.b64decode(b‘eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9‘) # bas64长度必须是4的倍数,如果不足,要用=补齐 res=base64.b64decode(b‘eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImxxeiIsImV4cCI6MTYxOTY2OTA1Mn0=‘) # print(len(‘eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImxxeiIsImV4cCI6MTYxOTY2OTA1Mn0=‘)) ? print(res)
1 借助于djangorestframework-jwt实现 # 安装 pip3 install djangorestframework-jwt 2 默认用的是auth的user表,可以在models.py 扩写auth的user表 from django.db import models from django.contrib.auth.models import AbstractUser ? class UserInfo(AbstractUser): phone = models.BigIntegerField(null=True) # 扩写auth的user表需要在settings.py 配置中配置 AUTH_USER_MODEL = "app01.UserInfo" 3 创建超级用户 python3 manage.py createsuperuser 4 不需要写登录功能(按照官方文档要求设置路由) 路由 urls.py(登录功能,djangorestframework-jwt已经写好了登录): ? from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path(‘login/‘, obtain_jwt_token), ] 5 也不需要写认证类,用postman测试 接口:http://127.0.0.1:8000/login/ 向后端接口发送post请求,携带用户名密码,即可看到生成的token postman发送访问请求(必须带jwt空格) 6 djangorestframework-jwt也有配置文件,也有默认配置,默认过期时间是5分钟 # 在settings.py配置中配置 import datetime JWT_AUTH = { # 过期时间1天 ‘JWT_EXPIRATION_DELTA‘: datetime.timedelta(days=1), # 自定义认证结果:见下方自定义response(自定义响应格式) # 如果不自定义,返回的格式是固定的,只有token字段 ‘JWT_RESPONSE_PAYLOAD_HANDLER‘: ‘app01.response.response_user_login‘, }
### 限制某个接口必须登录才能用(加载views.py视图类中) from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.permissions import IsAuthenticated # 如果使用jwt内置的认证类,需要配合一个权限类(局部使用) authentication_classes = [JSONWebTokenAuthentication,] permission_classes = [IsAuthenticated,] ? # 如果只加了JSONWebTokenAuthentication,登录可以用,不登录也可以用 # 请求路径(需要在请求头中带) # Authorization: jwt asdfasdfasfasdfasdtoken串 http://127.0.0.1:8000/api/books/ get请求 # 全局使用 在setting.py配置文件中配置(需要配合一个权限类) REST_FRAMEWORK = { # 认证模块 ‘DEFAULT_AUTHENTICATION_CLASSES‘: ( ‘rest_framework_jwt.authentication.JSONWebTokenAuthentication‘, ), # 权限模块 "DEFAULT_PERMISSION_CLASSES":( ‘rest_framework.permissions.IsAuthenticated‘, ), }
# 自定义一个response.py文件,写一个函数 def response_user_login(token, user=None, request=None): print(request.method) return { ‘status‘:100, ‘msg‘:‘登录成功‘, ‘username‘:user.username, ‘token‘:token } #在settings.py 配置文件中配置 JWT_AUTH = { ‘JWT_RESPONSE_PAYLOAD_HANDLER‘: ‘app01.response.response_user_login‘, }
自己定义的user表,手写登录功能
from rest_framework.response import Response from rest_framework.views import APIView from rest_framework_jwt.settings import api_settings ? jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER ? class UserLoginViwe(APIView): ? def post(self, request, *args, **kwargs): username = request.data.get(‘username‘) password = request.data.get(‘password‘) ? user = models.User.objects.filter(username=username, password=password).first() if user: # 签发token payload = jwt_payload_handler(user) # 先拿到载荷 payload token=jwt_encode_handler(payload) # 通过payload得到token return Response({‘status‘:100,‘msg‘:‘登录成功‘,‘token‘:token}) else: return Response({‘status‘:101,‘msg‘:‘用户名或密码错误‘})
import jwt from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework_jwt.settings import api_settings jwt_decode_handler = api_settings.JWT_DECODE_HANDLER ? from app01 import models class JsonAuthentication(JSONWebTokenAuthentication): def authenticate(self, request): jwt_value=self.get_jwt_value(request) # jwt_value=request.GET.get(‘token‘) # 验证签名,验证是否过期 try: payload = jwt_decode_handler(jwt_value) # 得到载荷 # 取当前用户,拿到user对象,每登录一个人就要去数据库查一次,效率不高 # user=models.User.objects.filter(id=payload[‘user_id‘]).first() # 效率更高一写,不需要查数据库了 user=models.User(id=payload[‘user_id‘],username=payload[‘username‘]) # user={‘id‘:payload[‘user_id‘],‘username‘:payload[‘username‘]} except jwt.ExpiredSignature: msg = ‘token过期‘ raise exceptions.AuthenticationFailed(msg) except jwt.DecodeError: msg = ‘签名错误‘ raise exceptions.AuthenticationFailed(msg) except jwt.InvalidTokenError: raise exceptions.AuthenticationFailed(‘反正就不对‘) # 这个不能用,因为它找的是auth的user表,我们是去自己表中查,这个需要自己写 # user = self.authenticate_credentials(payload) ? return (user, jwt_value)
1 登录功能,继承CreateAPIView,
重写create方法,认证用户名密码是否正确,签发token,在序列化类中写
2 多方式登录
输入用户名,输入手机号,输入邮箱加密码都能登录成功
视图 views.py
from rest_framework.generics import CreateAPIView from app01 import models from app01 import serializer from rest_framework.response import Response ? from rest_framework.viewsets import ViewSetMixin # class UserLogin(CreateAPIView): # 需要自己配路径 class UserLogin(ViewSetMixin,CreateAPIView): # 自动生成路由 queryset = models.UserInfo.objects.all() serializer_class = serializer.UserInfoModelSerializer ? def create(self, request, *args, **kwargs): ? # context是上下文,是视图类和序列化类沟通的桥梁 ser = self.get_serializer(data=request.data) ? if ser.is_valid(): token = ser.context[‘token‘] username = ser.context[‘username‘] return Response({‘status‘: 100, ‘msg‘: ‘登录成功‘, ‘token‘: token, ‘username‘: username}) else: print(ser.errors) return Response({‘status‘: 100, ‘msg‘: ‘用户名或密码错误‘})
序列化类 serializer.py
from app01 import models from rest_framework import serializers import re from rest_framework.exceptions import ValidationError from rest_framework_jwt.settings import api_settings ? jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER ? ? class UserInfoModelSerializer(serializers.ModelSerializer): ? # 这么写,字段没有自己的校验规则了(针对于扩写的auth_user表) username = serializers.CharField() ? class Meta: model = models.UserInfo fields = [‘username‘, ‘password‘] ? def validate(self, attrs): ? username = attrs.get(‘username‘) password = attrs.get(‘password‘) ? # 多方式登录,username有可能是手机号,邮箱,用户名 if re.match(‘^1[3-9][0-9]{9}$‘, username): # 用手机号登录 user = models.UserInfo.objects.filter(phone=username).first() elif re.match(r‘^.+@.+$‘, username): # 以邮箱登录 user = models.UserInfo.objects.filter(email=username).first() else: # 以用户名登录 user = models.UserInfo.objects.filter(username=username).first() ? if user and user.check_password(password): # 登录成,签发token payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) self.context[‘token‘] = token self.context[‘username‘] = user.username return attrs else: # 校验不通过 raise ValidationError(‘用户名或密码错误‘)
路由 urls.py
from django.contrib import admin from django.urls import path,include from rest_framework.routers import SimpleRouter from app01 import views ? router = SimpleRouter() router.register(‘login‘, views.UserLogin) ? urlpatterns = [ path(‘admin/‘, admin.site.urls), path(‘‘, include(router.urls)), # path(‘login1/‘, views.UserLogin.as_view()) ] ?
标签:ken from pac ota 负载 序列化 enc ade 记录
原文地址:https://www.cnblogs.com/gfeng/p/14695287.html