drf-day6
1. 请求安全认证
传统安全认证

前后端分离

Token认证方式

1 2 3 4
| # 服务端验证的优点: 1. 服务端不需要储存token,token交给每一个客户端储存,服务器压力小 2. 服务器做的是 签发token与校验token的两段算法,签发认证的效率高 3. 算法在集群同步的成本低
|
2. JWT(Json web token)
github文档:https://github.com/jpadilla/django-rest-framework-jwt
JWT构成
JWT是由三段信息构成的,将这三段信息文本用.
链接一起就构成了Jwt字符串
例如:abc.admin.123
:头部.载荷.签名
1 2 3 4 5 6 7 8 9 10 11
| # jwt构成介绍 第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature). # jwt每一部分都是由一个json字典加密后形成的字符串
# 头部和载荷采用的是base64可逆加密(前后台都可以解密)
# 签名采用的是sha256不可逆加密
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
|
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
1 2 3 4 5 6
| { 'typ': 'JWT', 'alg': 'HS256' }
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
|
载荷就是存放有效信息的地方。 可以保存用户信息,方便在服务端进行反解析
- header (base64后的)
- payload (base64后的)
- secret
由以上三个字段串拼接后再使用不可逆的加密算法sha256进行加密
1
| # 注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
|
JWT的使用
pip install djangorestframework-jwt
源码剖析
JSONWebTokenAPIView
中的post方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data)
if serializer.is_valid(): user = serializer.object.get('user') or request.user token = serializer.object.get('token') response_data = jwt_response_payload_handler(token, user, request) response = Response(response_data) 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
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
JSONWebTokenAPIView
并没有实现token签发的逻辑,而是在子类ObtainJSONWebToken
的序列化器中的全局钩子中来处理了token的签发
JSONWebTokenSerializer
中的validate
函数完成了token的签发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| def validate(self, attrs): credentials = { self.username_field: attrs.get(self.username_field), 'password': attrs.get('password') }
if all(credentials.values()): user = authenticate(**credentials)
if user: if not user.is_active: msg = _('User account is disabled.') raise serializers.ValidationError(msg)
payload = jwt_payload_handler(user)
return { 'token': jwt_encode_handler(payload), 'user': user } else: msg = _('Unable to log in with provided credentials.') raise serializers.ValidationError(msg) else: msg = _('Must include "{username_field}" and "password".') msg = msg.format(username_field=self.username_field) raise serializers.ValidationError(msg)
|
1 2 3 4 5 6 7 8 9 10 11
| 1. 通过username password获取到用户对象,对user进行校验
2. 通过user对象来生成载荷:`jwt_payload_handler(user)` 'rest_framework_jwt.utils.jwt_payload_handler', 3. 通过载荷payload来签发token: `jwt_encode_handler(payload)` 'rest_framework_jwt.utils.jwt_payload_handler', # 总结: 1. 签发token走的是`ObtainJSONWebToken`父类中的post方法 2. user 与 token是在ObtainJSONWebToken自己的序列化器中的全局钩子中来完成获取的
|
3. 多方式登陆 签发token
签发token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import re
from rest_framework.serializers import ModelSerializer from rest_framework import serializers from rest_framework_jwt.settings import api_settings
from api.models import User
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class UserModelSerializer(ModelSerializer): """ 前端发送请求 传递参数 但数据不需要保存至数据 反序列化的过程中,有些字段只参与反序列化的业务,并不会保存到数据中,模型也没有对应的字段 """ account = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True)
class Meta: model = User fields = ["account", "password", "username", "phone", "email"]
extra_kwargs = { "username": { "read_only": True }, "phone": { "read_only": True }, "email": { "read_only": True }, }
def validate(self, attrs): account = attrs.get("account") password = attrs.get("password")
if re.match(r'1[3-9][0-9]{9}', account): user_obj = User.objects.filter(phone=account).first() elif re.match(r'.+@.+', account): user_obj = User.objects.filter(email=account).first() else: user_obj = User.objects.filter(username=account).first()
if user_obj and user_obj.check_password(password): """ 根据用户信息生成载荷 根据载荷生成token """ payload = jwt_payload_handler(user_obj) token = jwt_encode_handler(payload) self.obj = user_obj self.token = token
return attrs
|
自定义校验token
继承BaseJSONWebTokenAuthentication
,重写authenticate
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import jwt from rest_framework import exceptions from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication, jwt_decode_handler
class JWTAuthentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request): jwt_token = request.META.get("HTTP_AUTHORIZATION", None) print(jwt_token) token = self.parse_jwt_token(jwt_token)
if token is None: return None
try: payload = jwt_decode_handler(token) except jwt.ExpiredSignature: raise exceptions.AuthenticationFailed("签名已过期") except: raise exceptions.AuthenticationFailed('非法用户')
user = self.authenticate_credentials(payload)
return user, token
def parse_jwt_token(self, jwt_token): tokens = jwt_token.split()
if len(tokens) != 3 or tokens[0].lower() != "auth" or tokens[2].lower() != "jwt": return None
return tokens[1]
|
4. 搜索与排序组件
1 2 3 4 5 6 7 8 9 10
| class ComputerListAPIView(ListAPIView): queryset = Computer.objects.all() serializer_class = ComputerModelSerializer
filter_backends = [SearchFilter, OrderingFilter] search_fields = ['name', "price"] ordering = ['price']
|
5. 分页组件
基础分页器
1 2 3 4 5 6 7 8 9
| class MyPageNumberPagination(PageNumberPagination): page_size = 2 max_page_size = 5 page_size_query_param = "page_size" page_query_param = "page"
|
偏移分页器
1 2 3 4 5 6 7 8 9
| class MyLimitOffsetPagination(LimitOffsetPagination): default_limit = 3 limit_query_param = "limit" offset_query_param = "offset" max_limit = 5
|
游标分页器(了解)
1 2 3 4 5 6
| class MyCursorPagination(CursorPagination): cursor_query_param = "course" page_size = 3 page_size_query_param = "page_size" max_page_size = 5 ordering = "-price"
|
django-filter
pip install django-filter==2.2.0
1 2 3 4 5 6 7 8 9 10 11 12 13
| from django_filters.rest_framework import FilterSet
from api.models import Computer
class ComputerFilterSet(FilterSet): from django_filters import filters min_price = filters.NumberFilter(field_name="price", lookup_expr="gte") max_price = filters.NumberFilter(field_name="price", lookup_expr="lte")
class Meta: model = Computer fields = ["brand", "min_price", "max_price"]
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class ComputerListAPIView(ListAPIView): queryset = Computer.objects.all() serializer_class = ComputerModelSerializer
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend] search_fields = ['name', "price"] ordering = ['price']
filter_class = ComputerFilterSet
|
作业
1 2 3
| 1. 掌握JWT签发与校验token的方式,多方式签发token 2. 掌握过滤组件以及django-filter插件的使用 3. 总结知识图谱
|