JWT认证

JWT认证

一 jwt认证介绍

1 什么是jwt

# Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

1) 不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制,用户登录认证

2) 用户只要登录了,返回用户一个token串(随机字符串),每次用户发请求,需要携带这个串过来,验证通过,我们认为用户登录了

2 构成和工作原理

2.1 JWT的构成

# 3 JWT的构成(字符串)
-三部分(每一部分中间通过.分割):header   payload  signature
1)-header:明类型,这里是jwt,声明加密算法,头里加入公司信息...,base64转码
    {
        'typ': 'JWT',
        'alg': 'HS256'
    }

2)-payload:荷载(有用),当前用户的信息(用户名,id,这个token的过期时间,手机号),base64转码
    {
        "sub": "1234567898",
        "name": "egon",
        "admin": true,
        "userid":1,
        'mobile':123444444
    }

3)-signature:签名
    -把前面两部分的内容通过加密算法+密钥加密后得到的一个字符串        
        
4)-jwt总的构成样子:
 	 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

3 JWT认证原理

3.1 jwt认证算法:签发与校验

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

签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token

# 1) 签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
	-用户携带用户名,密码登录我的系统,校验通过,生成一个token(三部分),返回给用户---》登录功能完成
"""
1)用基本信息存储json字典,采用base64算法加密得到 头字符串
2)用关键信息存储json字典,采用base64算法加密得到 体字符串
3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串

账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
"""

校验:根据客户端带token的请求 反解出 user 对象

# 2) 校验:根据客户端带token的请求 反解出 user 对象
    -访问需要登录的接口(用户中心),必须携带token过来,后端拿到token后,把header和payload截出来,再通过一样的加密方式和密码得到一个signature,和该token的signature比较,如果一样,表示是正常的token,就可以继续往后访问
"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""

3.2 base64介绍和使用**

1) 任何语言都有base64的加码和解码,转码方式(加密方式)

2) python中base64的加密与解密

    import base64

    import json
    dic_info={
      "name": "lqz",
      "age": 18
    }
    # 转成json格式字符串
    dic_str=json.dumps(dic_info)
    print(dic_str)
    #eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=
    #eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=

    # 需要用bytes格式
    # 加密
    base64_str=base64.b64encode(dic_str.encode('utf-8'))
    print(base64_str)

    # 解密
    res_bytes=base64.b64decode('eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=')
    print(res_bytes)

4 drf项目的jwt认证开发流程(重点)

"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""

二 jwt基本使用

1 官网及安装

1) drf中使用jwt,借助第三方https://github.com/jpadilla/django-rest-framework-jwt

2) pip3 install djangorestframework-jwt

2 快速使用(默认使用auth的user表)

# 3 快速使用(默认使用auth的user表 )
1) 在默认auth的user表中创建一个用户

2) 在路由中配置
	path('login/', obtain_jwt_token),

3) 用postman向这个地址发送post请求,携带用户名,密码,登陆成功就会返回token

4) obtain_jwt_token本质也是一个视图类,继承了APIView
	-通过前端传入的用户名密码,校验用户,如果校验通过,生成token,返回
	-如果校验失败,返回错误信息

3 代码示例:控制用户登录/未登录访问内容

3.1 用户登录以后才能访问某个接口

# 4 用户登录以后才能访问某个接口
1)-jwt模块内置了认证类,拿过来局部配置就可以

2)-class OrderView(APIView):
    # 只配它不行,不管是否登录,都能访问,需要搭配一个内置权限类
    authentication_classes = [JSONWebTokenAuthentication, ]  # 仅仅认证是否用户,不涉及权限
    permission_classes = [IsAuthenticated,]  # 增加权限认证
    def get(self, request):
        print(request.user.username)
        return Response('订单的数据')

3.2 用户不登录,也能访问

# 5 用户不登录,也能访问
-class OrderView(APIView):    
    authentication_classes = [JSONWebTokenAuthentication, ]
    def get(self, request):
        print(request.user.username)
        return Response('订单的数据')

3.3 用户类型判断

-如果用户携带了token,并且配置了JSONWebTokenAuthentication,从request.user就能拿到当前登录用户,如果没有携带,当前登录用户就是匿名用户

3.4 前端请求jwt格式要求

# 7 前端要发送请求,携带jwt,格式必须如下
1)-把token放到请求头中,key为:Authorization 

2)-value必须为:jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo1LCJ1c2VybmFtZSI6ImVnb24xIiwiZXhwIjoxNjA1MjQxMDQzLCJlbWFpbCI6IiJ9.7Y3PQM0imuSBc8CUe_h-Oj-2stdyzXb_U-TEw-F82WE

三 控制登录接口返回的数据格式

1) 控制登录接口返回的数据格式如下
    {
    code:100
    msg:登录成功
    token:asdfasfd
    username:egon
    }
    
2) 写一个函数
	from homework.serializer import UserReadOnlyModelSerializer
    def jwt_response_payload_handler(token, user=None, request=None):
        return {'code': 100, 
                'msg': '登录成功',
                'token': token,
                'user': UserReadOnlyModelSerializer(instance=user).data
                }

3) 在setting.py中配置
	import datetime
    JWT_AUTH = {
        'JWT_RESPONSE_PAYLOAD_HANDLER': 'homework.utils.jwt_response_payload_handler',
    }

四 自定义基于jwt的认证类

1) 自己实现基于jwt的认证类,通过认证,才能继续访问,通不过认证就返回错误

2) 代码如下:	
    class JwtAuthentication(BaseJSONWebTokenAuthentication):
        def authenticate(self, request):
            # 认证逻辑()
            # token信息可以放在请求头中,请求地址中
            # key值可以随意叫
            # token=request.GET.get('token')
            token=request.META.get('HTTP_Authorization'.upper())
            # 校验token是否合法
            try:
                payload = jwt_decode_handler(token)
            except jwt.ExpiredSignature:
                raise AuthenticationFailed('过期了')
            except jwt.DecodeError:
                raise AuthenticationFailed('解码错误')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('不合法的token')
            user=self.authenticate_credentials(payload)
            return (user, token)

3) 在视图类中配置
	authentication_classes = [JwtAuthentication, ]

五 基于jwt的多方式登陆

1) 手机号+密码   用户名+密码  邮箱+密码
2) 流程分析(post请求):
	-路由:自动生成
    -视图类:ViewSet(ViewSetMixin, views.APIView)
    -序列化类:重写validate方法,在这里面对用户名和密码进行校验
    
3) 代码实现

1 路由

path('login/', views.LoginViewSet.as_view({'post':'create'})),

2 视图

class LoginViewSet(ViewSet):
    def create(self, request, *args, **kwargs):
        # 实例化得到一个序列化类的对象
        # ser=LoginSerializer(data=request.data,context={'request':request})
        ser = LoginSerializer(data=request.data)
        # 序列化类的对象的校验方法
        ser.is_valid(raise_exception=True)  # 字段自己的校验,局部钩子校验,全局钩子校验
        # 如果通过,表示登录成功,返回手动签发的token
        token = ser.context.get('token')
        username = ser.context.get('username')
        return APIResponse(token=token, username=username)
        # 如果失败,不用管了

3 序列化类

from rest_framework import serializers
from app01.models import UserInfo
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
from rest_framework_jwt.views import obtain_jwt_token

class LoginSerializer(serializers.ModelSerializer):
    # 重写username字段,否则post请求下,执行反序列化校验却不保存,会报错,被认为要进行create操作
    username = serializers.CharField()

    class Meta:
        model = UserInfo
        fields = ['username', 'password']

    def validate(self, attrs):
        # username可能是邮箱,手机号,用户名
        username = attrs.get('username')
        password = attrs.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = UserInfo.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = UserInfo.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = UserInfo.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.check_password(password):
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            # token是要在视图类中使用,现在我们在序列化类中
            # self.context.get('request')
            # 视图类和序列化类之间通过context这个字典来传递数据
            self.context['token'] = token
            self.context['username'] = user.username
            return attrs

        else:
            raise ValidationError('用户名或密码错误')

六 自定义user表, 签发token, 认证类

1 表模型

class MyUser(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    phone = models.CharField(max_length=32)
    email = models.EmailField()

2 路由

path('login2/', views.MyLoginView.as_view()),

3 登陆视图

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from rest_framework_jwt.views import obtain_jwt_token


class MyLoginView(APIView):
    def post(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        # 如果是手机号
        if re.match('^1[3-9]\d{9}$', username):
            # 以手机号登录
            user = MyUser.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):
            # 以邮箱登录
            user = MyUser.objects.filter(email=username).first()
        else:
            # 以用户名登录
            user = MyUser.objects.filter(username=username).first()
        # 如果user有值并且密码正确
        if user and user.password == password:
            # 登录成功,生成token
            # drf-jwt中有通过user对象生成token的方法
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token, username=user.username)
        else:
            return APIResponse(code=101, msg='用户名或密码错误')

4 认证组件

# 详见:自定义基于jwt的认证类
posted @ 2021-06-29 06:37  越关山  阅读(817)  评论(0)    收藏  举报