jwt用法

JWT认证

全称:Json web token (JWT)

主要用于web方向token的使用

JWT由来

https://www.cnblogs.com/liuqingzheng/p/8990027.html

token的签发认证流程

image

JWT的构成

# 分为三部分(头,荷载,签名),以.分割
eg:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ



	 - 头:header
    	声明类型,   这里是jwt
		声明加密的算法 通常直接使用 HMAC SHA256
        以及一些公司信息。。。
    -荷载:payload
    	-存放有效信息的地方
        -过期时间
        -签发时间
        -用户id
        -用户名字。。。
	-签名:signature
    	-第一部分和第二部分通过秘钥+加密方式得到的
        

编码格式base64


import base64
import json
dic = {'id':2,'username':'tank'}
"用json把字典变成字符串"
dic_str = json.dumps(dic)
"b64encode编码需要bytes类型数据使用encode编码"
res = base64.b64encode(dic_str.encode('utf-8'))

print(res)
#  b'eyJpZCI6IDIsICJ1c2VybmFtZSI6ICJ0YW5rIn0='

# 解码
ret=base64.b64decode('eyJpZCI6IDIsICJ1c2VybmFtZSI6ICJ0YW5rIn0=')
print(ret)
# b'{"id": 2, "username": "tank"}'
"注意base64编码长度后一定是4的倍数,如果解码时报错用=补齐"


base64在很多地方都可以使用
# base64 应用场景
'''
1 jwt 使用了base64
2 网络中传输数据,也会经常使用 base64编码  返回用户数据
3 网络传输中,有的图片使用base64编码,eg:12306  ....

'''

JWT开发重点

登录接口-->签发token

认证类 --->jwt认证

drf-jwt简单操作方法

在dango+drf平台开发,使用jwt有两个模块

djangorestframework-jwt     停更了但是还是一直可以用 
djangorestframework-simplejwt 公司用的比较多,需要自己研究一下
或自己封装jwt签发和认证

安装

pip3.8 install djangorestframework-jwt -i https://pypi.douban.com/simple

基于auth用户表已经帮助我们写好了登录接口

签发

配置路由

from rest_framework_jwt.views import obtain_jwt_token
path('login/',obtain_jwt_token)

测试: 创建一个用户 python38 manage.py createsuperuser
使用postman携带数据发送post请求

image

发现返回的格式不符合我们的规范,自定义返回格式

定制返回格式

写个函数

我们在配置文件中配置的时候,发现rest_framework_jwt 的配置文件里已经有了配置导入了一个方法jwt_response_payload_handler 查看该方法 提示了我们应该怎么写和需要什么参数
新建一个utils.py文件
def jwt_response_payload_handler(token, user=None, request=None):
    return {
        'code':100,
        'msg':'登录成功',
        'token':token,
        'username':user.username,

    }

image

配置文件配置


JWT_AUTH = {
    	'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',  
	}

认证类

导入模块
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
"""
	JSONWebTokenAuthentication: 判断带没带token,带了继续走,没带也能走(但是无法登录所以需要下面的权限类配合使用),带错的不行
	IsAuthenticated : 登录才有权限,没登录不行。
   """
class BookView(ModelViewSet):
    # 单独使用这个无法生效,需要配合drf的权限类
    authentication_classes = [JSONWebTokenAuthentication]
    permission_classes = [IsAuthenticated]
    queryset = Book.objects.all()
    serializer_class = BookSerializer    

image

post请求 token必须遵循下列
放在请求头中:
    -请求头中key值叫		Authorization
    -请求头的value值是:  jwt空格有效的token值

jwt的配置文件需要记住的

1.'JWT_RESPONSE_PAYLOAD_HANDLER' # 需要配置在我们自己的项目配置文件中
JWT_AUTH = {
    	'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',  
	}

2.'JWT_SECRET_KEY':settings.SECRET_KEY # 点进入可以看到密钥,可以修改
    SECRET_KEY = 'ba8j$$dpbo8s!zg49j@_nftm&1fs1dc+mv611-@m7fu4!lb8^^'

3.'JWT_EXPIRATION_DELTA':datetime.timedelta(seconds=300) # 过期时间设置
4. 'JWT_AUTH_HEADER_PREFIX':'JWT', # 前端在请求头中 Authorization: jwt空格token值  的jwt可以修改

关于密码(身份证)加密

我们可以使用uuid生成一个随机字符串,然后与md5或base64加密的密文拼在一起,存入表中(称为加盐处理),这样可以大大的增加安全性,可以用符号分割,拿出来验证的时候只需要用进行按符号分割。
拿到属于密码的那个密文解码后做比较。
或把用户登录的密码加密后与密文作比较。
或把加盐的部分拿出来与密码再次拼接后作比较。
为了安全性可以添加更多的处理。
查看auth用户表做的处理,
from django.contrib.auth.models import AbstractUser
是这个方法进行了加密处理
make_password
还有一个验证密码的方法
check_password  
"可以研究源码了解如何实现的"

drf-jwt源码执行流程

1.1 签发(登录)

# 登录接口,路由匹配成功,执行obtain_jwt_token---》post请求---》ObtainJSONWebToken的父类的post方法
	path('login/', obtain_jwt_token),
    
    
# ObtainJSONWebToken的post方法 继承APIView
    def post(self, request, *args, **kwargs):
        # 实例化得到序列化类对象
        serializer = self.get_serializer(data=request.data)
        # 做校验:字段自己,局部钩子,全局钩子
        if serializer.is_valid():
            # user:当前登录用户
            user = serializer.object.get('user') or request.user
            # 签发的token
            token = serializer.object.get('token')
            # 构造返回格式,我们自己写的jwt_response_payload_handler得方法,可以定制返回格式
            response_data = jwt_response_payload_handler(token, user, request)
            response = Response(response_data)
            #JWT_AUTH_COOKIE经查看是个常量=False,如果为True那么就会直接往cookie里写,用于前后端混合项目。
            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)
    
    
    
    
 # 如何得到user,如何签发的token----》在jwt序列化类的全局钩子中得到的user和签发的token
	-JSONWebTokenSerializer---全局钩子---validate
    	#前端传入,校验过后的数据---》但是这里只有全局钩子没有其他校验那么就是前端传过来的{"username":"lqz","password":"lqz1e2345"}
        def validate(self, attrs):
        credentials = {
            # self.username_field: attrs.get(self.username_field),
            'username':attrs.get('username')
            'password': attrs.get('password')
        }
			# 判断都要有值才能走
        if all(credentials.values()):
            # auth 模块,authenticate方法(校验)可以传入用户名,密码如果用户存在,就返回用户对象,如果不存就是None
          
            user = authenticate(**credentials)
				# 判断用户是否有存在
            if user:
                # user.is_active(is_active字段)1或0,校验用户是否是活跃用户,如果禁用了,不能登录成功
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)
					# payload就是荷载----》通过user得到荷载{'user_id':1,'name':xxx,}
                payload = jwt_payload_handler(user)
				
                return {
                    # jwt_encode_handler方法通过荷载得到token串
                    '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 通过user得到荷载:payload = jwt_payload_handler(user)
    2 通过荷载签发token:jwt_encode_handler(payload)


## 了解:
	# 翻译函数,只要做了国际化,放的英文,会翻译成该国语言(配置文件配置的)
	from django.utils.translation import ugettext as _
	msg = _('Unable to log in with provided credentials.')


2.2 认证(认证类)

# JSONWebTokenAuthentication---->父类BaseJSONWebTokenAuthentication----》authenticate方法
    def authenticate(self, request):
        # 前端带在请求头中的token 值
        jwt_value = self.get_jwt_value(request)
        # 如果没有携带token,就不校验了
        if jwt_value is None:
            return None

        try:
            # jwt_value就是token
            # 通过token,得到荷载,中途会出错
            # 出错的原因:
            	-篡改token
                -过期了
                -未知错误
            payload = jwt_decode_handler(jwt_value)
        except jwt.ExpiredSignature:
            msg = _('Signature has expired.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = _('Error decoding signature.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()

        # 如果能顺利解开,没有被异常捕获,说明token是可以信任的
        # payload就可以使用,通过payload得到当前登录用户
        user = self.authenticate_credentials(payload)
		# 返回当前登录用户,token
        return (user, jwt_value)
# jwt_value = self.get_jwt_value(request)
    def get_jwt_value(self, request):
        # 拿到了前端请求头中传入的 jwt dasdfasdfasdfa
        # auth=[jwt,asdfasdfasdf]
        auth = get_authorization_header(request).split()
        # 'jwt'
        auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()

        if not auth:
            # 请求头中如果没带,去cookie中取
            if api_settings.JWT_AUTH_COOKIE:
                return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
            return None

        if smart_text(auth[0].lower()) != auth_header_prefix:
            return None

        if len(auth) == 1:
            msg = _('Invalid Authorization header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = _('Invalid Authorization header. Credentials string '
                    'should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        return auth[1]
    
    
    
    
 # 认证类配置了,如果不传jwt,不会校验,一定配合权限类使用

自定义用户表签发和认证

签发


# Create your views here.
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from .models import UserInfo
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER


class UserView(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        user = UserInfo.objects.filter(username=username, password=password).first()
        if user:
            # 签发token,通过user得到payload  jwt写好了导入使用
            payload = jwt_payload_handler(user)
            # 通过payload得到token  jwt写好了导入使用
            token = jwt_encode_handler(payload)
            return Response({'code':100,'msg':'登陆成功','token':token})
        else:
            # 为了安全建议返回用户名或密码错误,这样爬虫用手机号发请求不知道手机号存不存在
            return Response({'code':101,'msg':'用户名或密码错误'})


认证类

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
from django.utils.translation import ugettext as _  # 翻译模块
from rest_framework import exceptions
import jwt
from .models import UserInfo
class JsonWebTokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 取出token
        token = request.META.get('HTTP_TOKEN')
        # 判断有没有值
        if token:
            #通过token拿到payload并解码
            try:
                payload = jwt_decode_handler(token)
                # 得到当前用户
                user = UserInfo.objects.get(pk=payload.get('user_id'))
                # 可以节省数据库查询资源 如果有其他字段
                # user = UserInfo(pk=payload.get('user_id'),username=payload.get('username'))
                # 也可以放个字典  方法中能通过request.user.get('id')拿到后如果需要别的数据再去数据库里查
                # user ={'id':payload.get('user_id')}
                # 返回用户与token
                return user,token
            except jwt.ExpiredSignature:
                msg = _('token过期.')
                raise exceptions.AuthenticationFailed(msg)
            except jwt.DecodeError:
                msg = _('token认证失败.')
                raise exceptions.AuthenticationFailed(msg)
            except jwt.InvalidTokenError:
                raise exceptions.AuthenticationFailed('token无效')
            except Exception:
                raise exceptions.AuthenticationFailed('未知异常')

        raise AuthenticationFailed('token没传认证失败')

views

from .authentication import JsonWebTokenAuthentication
class TestView(ViewSet):
    # 因为自己写了获取token验证不需要再配合权限类使用
    authentication_classes = [JsonWebTokenAuthentication]
    @action(methods=['POST'],detail=False)
    def test(self,request):
        return Response('通过')

urls

from django.contrib import admin
from django.urls import path,include
from rest_framework.routers import SimpleRouter
from app01.views import UserView,TestView
from rest_framework_jwt.views import obtain_jwt_token

router = SimpleRouter()
router.register('user',UserView,'user')
router.register('test',TestView,'test')
访问地址
api/v1/user/login/
api/v1/test/test/
urlpatterns = [
    path('admin/', admin.site.urls),

    # path('login/',obtain_jwt_token),
    path('api/v1/',include(router.urls)),

注意的模块

from django.utils.translation import ugettext as _  # 翻译模块
from rest_framework_jwt.settings import api_settings
# jwt 写好的生成payload
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER 
# 通过payload拿到token并编码
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
# 解码从payload中拿到token
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER

修改token过期时间
import datetime

JWT_AUTH = {
    'JWT_EXPIRATION_DELTA':datetime.timedelta(seconds=300),
}

posted @ 2023-02-09 18:48  李阿鸡  阅读(68)  评论(0编辑  收藏  举报
Title