Loading

JWT

JWT介绍

​ 在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。

image-20240418171423016

构成和工作原理

JWT的构成

​ JWT就是一长串字符串,被.分成三段,分别是头部,载荷,签名

​ jwt的头部承载两部分的信息,头部会进行base64的转码

  1. 声名类型,标识这里是jwt
  2. 声名加密的算法 通常是HMAC SHA256
# header
{
  'typ': 'JWT',
  'alg': 'HS256'
}

# 转码后
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

payload

​ 载荷就是存放有效信息的地方,它也会被进行base64转码

# payload
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

# 转码后
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

​ 签名是用来验证前两个部分有没有被篡改过,它由三部分组成,这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分

  1. header (base64过后)
  2. payload(base64过后)
  3. secret(盐)

本质原理

jwt认证算法:签发与验证

"""
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
  "company": "公司信息",
  ...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
  "user_id": 1,
 ...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
 "head": "头的加密字符串",
  "payload": "体的加密字符串",
 "secret_key": "安全码"
}
"""

签发

# 当用户登录校验通过后
1-将基本信息字典转成json字符串,再进行base64转码,得到头字符串
2-将用户信息字典转成json字符串,再进行base64转码,得到载荷字符串
3-将头和荷载字符串打包成json字典字符串,再按头字符串的加密方式加密,得到签名字符串

校验

# 根据客户端带token的请求反解析出user对象
1-将token分成三段
2-将第二段的用户信息解析出来,从用户表得到登录用户
3-将第一段和第二段加密 再与第三段比较,一致则校验通过,否则校验不通过

drf项目的jwt认证开发流程

1-浏览器发请求带账号密码,登录接口通过校验,签发token给浏览器,浏览器保存到自己的cookies里面
2-浏览器带token发请求,drf认证组件反解出用户对象,

注意:灯笼裤几口需要做 认证 + 权限 两个局部禁用

base64编码解码

import base64
import json
dic_info={
  "sub": "1234567890",
  "name": "lqz",
  "admin": True
}
byte_info=json.dumps(dic_info).encode('utf-8')
# base64编码
base64_str=base64.b64encode(byte_info)
print(base64_str)
# base64解码
base64_str='eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9'
str_url = base64.b64decode(base64_str).decode("utf-8")
print(str_url)

drf-jwt安装和简单使用

安装

pip install djangorestframework-jwt

使用

自定义返回内容

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

# 序列化类 继承TokenObtainPairSerialize
class AutoLoginSerializer(TokenObtainPairSerializer):
    # 重写父类的全局钩子
    def validate(self, attrs):
        dic = super().validate(attrs)
        data = {
            'code': 100,
            'message': '登陆成功',
            'username': self.user.username,
            'refresh': dic.get('refresh'),
            'access': dic.get('access')
        }
        return data

# 视图类
class AutoUserView(GenericViewSet):
    serializer_class = AutoLoginSerializer

    @action(methods=['POST'], detail=False)
    def login(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            return Response(serializer.validated_data)
        
        
# 注册路由
router.register(prefix='auto', viewset=AutoUserView, basename='auto')

# settings配置
# JWT配置 里面具体配置可以参考文档
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # 配置过期时间
    'REFRESH_TOKEN_LIFETIME': timedelta(days=3),
    # 用于生成access和刷refresh的序列化器。
    "TOKEN_OBTAIN_SERIALIZER": "app02.serializer.AutoLoginSerializer",
}


INSTALLED_APPS = [
    # 不要忘记注册app
    'rest_framework_simplejwt',
]

定制payload内容

​ 只需要重写get_token方法,该方法传入user对象,返回token

class AutoLoginSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        # 往第二段数据里面加东西 这里加一个名字
        token['name'] = user.username
        return token

    # 重写全局钩子
    def validate(self, attrs):
        dic = super().validate(attrs)
        data = {
            'code': 100,
            'message': '登陆成功',
            'username': self.user.username,
            'refresh': dic.get('refresh'),
            'access': dic.get('access')
        }

        return data

多方式登录

​ 可以通过多种方式登录,如用户名/手机号/邮箱

视图类

class UserView(GenericViewSet, CreateModelMixin):
    serializer_class = None

    def get_serializer_class(self):
        if self.action == 'login':
            return UserLSerializer
        else:
            return UserRSerializer

    @action(methods=['POST'], detail=False)
    def register(self, request):
        res = super().create(request)
        return Response({'code': 200, 'message': '注册成功', 'result': res.data})

    @action(methods=['POST'], detail=False)
    def login(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            refresh = serializer.context.get('refresh')
            access = serializer.context.get('access')
            return Response({'code': 200, 'message': '登录成功', 'refresh': refresh, 'access': access})
        return Response(serializer.errors)

序列化类

class UserLSerializer(serializers.Serializer):
    # 因为登录的时候只输入用户名密码
    # 所以只需要序列化两个字段
    username = serializers.CharField()
    password = serializers.CharField()

    # 这个方法用来拿到登录对象
    def _get_user(self, attrs):
        # 手机号正则表达式
        phone_regex = r'^1[3-9]\d{9}$'
        # 邮箱正则表达式
        email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
        # 拿到 手机号/邮箱/用户名
        username = attrs.get('username')
        # 拿到密码
        password = attrs.get('password')
        # 下面分别做判断 看用户名是以什么方式登录
        if re.match(phone_regex, username):
            user = User.objects.filter(mobile=username).first()
        elif re.match(email_regex, username):
            user = User.objects.filter(email=username).first()
        else:
            user = User.objects.filter(username=username).first()
        if user and user.check_password(password):
            return user


    def validate(self, attrs):
        user = self._get_user(attrs)
        # 如果上面验证失败user就为None,就抛异常
        if not user:
            raise ValidationError('用户名或密码错误')
        refresh = RefreshToken.for_user(user)
        self.context['refresh'] = str(refresh)
        self.context['access'] = str(refresh.access_token)
        return attrs

自定义用户表,手动签发和认证

# 自定义用户表,不是扩写auth的user表
# 自己写登陆签发token

视图类

class NormaUserView(GenericViewSet, CreateModelMixin):
    serializer_class = None

    def get_serializer_class(self):
        if self.action == 'login':
            return NormalUserLSerializer
        else:
            return NormalUserRSerializer

    @action(methods=['POST'], detail=False)
    def register(self, request):
        res = super().create(request)
        return Response({'code': 200, 'message': '注册成功', 'result': res.data})

    @action(methods=['POST'], detail=False)
    def login(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            refresh = serializer.context.get('refresh')
            access = serializer.context.get('access')
            return Response({'code': 200, 'message': '登录成功', 'refresh': refresh, 'access': access})
        return Response(serializer.errors)

序列化类

class NormalUserRSerializer(ModelSerializer):
    class Meta:
        model = NormalUser
        fields = ['username', 'password']

    def validate(self, attrs):
        username = attrs.get('username')
        user = NormalUser.objects.filter(username=username).first()
        if user:
            raise ValidationError(f'用户{user.username}已存在')
        return attrs


class NormalUserLSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def _get_user(self, attrs):
        # 拿到 手机号/邮箱/用户名
        username = attrs.get('username')
        # 拿到密码
        password = attrs.get('password')
        user = NormalUser.objects.filter(username=username, password=password).first()
        print(user)
        return user

    def validate(self, attrs):
        user = self._get_user(attrs)
        if not user:
            raise ValidationError('用户名或密码错误')
        token = RefreshToken.for_user(user)
        access = str(token.access_token)
        refresh = str(token)
        self.context['access'] = access
        self.context['refresh'] = refresh
        return attrs

自定义用户表,手动签发和认证

​ 现在我定义一个图书表,只有登录之后的用户才能调用这张表的接口

图书视图类 序列化类

class BookView(GenericViewSet, ListModelMixin, RetrieveModelMixin):
    serializer_class = BookSerializer
    queryset = Book.objects.all()
    authentication_classes = [CommonAuthentication]
    
    
class BookSerializer(ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

认证类

from rest_framework.exceptions import AuthenticationFailed

# 继承JWTAuthentication
class CommonAuthentication(JWTAuthentication):
    # 重写authenticate
    def authenticate(self, request):
        # 从请求头中取出token
        token = request.META.get('HTTP_AUTHORIZATION')
        # 如果没有token就说明没有登录,所以抛出异常
        if not token:
            raise AuthenticationFailed('请先登录再操作')
        # 调用父类get_validated_token以此校验token,返回值是payload段,包含用户id
        validated_token = self.get_validated_token(token)
        # 取到id,从而得到用户对象
        user_id = validated_token.get('user_id')
        user = NormalUser.objects.filter(pk=user_id).first()
        return user, token
posted @ 2024-04-21 22:03  HuangQiaoqi  阅读(24)  评论(0编辑  收藏  举报