• django实现jwt身份认证


    官网:Django REST framework JWT

    文章中使用版本信息:python3.8,django2.2

    闲暇之余研究了下jwt,没想到过程中遇到各种各样问题,网上乱七八糟搜了一堆,都不能串起来,最后理了理跑起来了,就记录一下,希望以后用到或对大家有帮助,个人理解,可能看到冰山一角,只是能用起来,可互相探讨。

    下面来说两种实现

    1. pyjwt

    2. djangorestframework-jwt

    一、理论

    平时做状态保持登录验证时,一直是用session、cookie、redis等,看了个文章说是这种方式,用户量过大,频繁去读写redis,影响内存,还有多服务器时session共享问题,容易csrf攻击等等,然后就推荐jwt。

    1. JWT(json  web token)
        jwt的令牌存在于客户端,流程:
        (1)用户输入用户名密码请求服务器
        (2)服务器验证用户信息
        (3)服务器通过验证后,发送给用户一个token
        (4)客户端存储token,并在每次请求时携带上这个token
        (5)服务器每次验证token,并返回数据
          说明:前端请求时,token在请求头里,另外服务器要支持cors(跨域资源共享)策略,一般在服务端可以Access-Control-Allow-Origin: *

    2. jwt由三部分组成,点分割,第一部分,头部(header),第二部分,载荷,第三部分,签名,需要验证第三部分(asdasdcxzcxz.sadsafdasgfdsfdasf.fdasfdsafdas)。

       (1)头部有两部分信息,base64编码,{“typ”: "JWT", "alg": "HS256"},作用,声明类型,声明加密方式
       (2)载荷,base64编码,作用,放一些非敏感数据
       (3)签名,将编码后的头部、载荷相加,使用header中声明的加密方式进行加盐secret组合进行加密

        注意:secret是保存在服务器端,jwt签名也是在服务器端,secret是用来进行签名与验证的,相当于私钥,不能泄露。eg: HMACSHA256(string, "secret")

    3. jwt优点:
        jwt支持所有平台
        payload中可以带些非敏感数据
        不需要在服务端保存会话信息,容易扩展

    二、djangorestframework-jwt

    pip install djangorestframework-jwt

    坑一:使用drf-jwt时需要注意,默认的验证时去auth_user表去校验,想使用drf自带的登录接口,通过自定义建的用户表,或者想通过邮箱等字段校验,需要自定义验证方式,内部使用的django的认证类,重写ModelBackend类的authenticate方法,然后在settings中配置成我们的,上代码。

    尽量使用auth_user表,省去很多麻烦,我使用自己新建的用户表

    按照顺序一步步来的

    1. 登录分发签名步骤

    1. # urls.py
    2. from django.contrib import admin
    3. from django.urls import path, include
    4. from rest_framework_jwt.views import obtain_jwt_token
    5. urlpatterns = [
    6. path('admin/', admin.site.urls),
    7. path('library/', include('apps.library.urls')),
    8. path('jwt/login/', obtain_jwt_token), # url地址随意定义,drf登录接口
    9. ]
    10. print(urlpatterns)

    不用写登录接口,直接重写自定义校验就行,在utils文件夹下新建py

    1. # jwt_custom.py
    2. from django.contrib.auth.backends import ModelBackend
    3. # 使用jwt的登录接口,自定义方式认证,request一直是空,取不到内容,如果有验证码等其他要校验的呢?
    4. # 为啥要自定义认证类,因为jwt默认是校验auth_user表的用户名密码
    5. class UserAuthBackend(ModelBackend):
    6. def authenticate(self, request, username=None, password=None, **kwargs):
    7. try:
    8. # LbUserInfo为自定义的用户表
    9. user = LbUserInfo.objects.get(username=username)
    10. # 密码使用base64解码后使用
    11. password = base64.b64decode(password)
    12. if check_password(password, user.password):
    13. return user
    14. except:
    15. raise AuthenticationFailed("没有查找到用户")

    重写后,在settings中指向我们定义的

    1. # settings.py
    2. # jwt认证属性
    3. JWT_AUTH = {
    4. 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), # token过期时间
    5. }
    6. # 自定义jwt验证方式
    7. AUTHENTICATION_BACKENDS = (
    8. 'apps.library.utils.jwt_custom.UserAuthBackend',
    9. )

    至此,drf的登录接口就好了,但此时响应只会返回一个token字段,要想多返回,还得再自定义返回内容

    1. # jwt_custom.py
    2. # 响应内容
    3. {
    4. "token": "asdasdasfadasdasd"
    5. }
    6. # 重写drf-jwt登录视图,构造响应内容
    7. def jwt_response_payload_handler(token, user=None, request=None):
    8. return {
    9. "token": token,
    10. "user_id": user.id,
    11. "username": user.username,
    12. "status": 200
    13. }

    还要在settings.py中指向这个自定义的

    1. # settings.py
    2. # jwt认证属性(默认值都在rest_framework_jwt的settings文件中)
    3. JWT_AUTH = {
    4. 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), # token过期时间
    5. 'JWT_RESPONSE_PAYLOAD_HANDLER': 'apps.library.utils.jwt_custom.jwt_response_payload_handler',
    6. # 登陆失败时自定义的返回结构
    7. # 'JWT_RESPONSE_PAYLOAD_ERROR_HANDLER': 'rest_framework.jwt_response_payload_error_handler',
    8. }

    登录接口结束,校验、响应都自定义完成,下一步就是签名在view视图中怎么使用验证,并获取签名信息了。

    2. 接口校验签名步骤

    1. # settings.py
    2. REST_FRAMEWORK = {
    3. # 这儿配置了就是全局使用,单个视图使用,可以加在视图里,下面有示例
    4. 'DEFAULT_AUTHENTICATION_CLASSES': (
    5. # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 内部认证类
    6. 'apps.library.utils.jwt_custom.JsonAuthentication', # 自定义jwt认证类
    7. 'rest_framework.authentication.SessionAuthentication',
    8. 'rest_framework.authentication.BasicAuthentication',
    9. ),
    10. # 使用drf的权限验证
    11. 'DEFAULT_PERMISSION_CLASSES': [
    12. 'rest_framework.permissions.IsAuthenticated',
    13. ]
    14. }

    自定义认证类

    1. # jwt_custom.py
    2. import jwt
    3. from rest_framework import exceptions
    4. from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    5. from rest_framework_jwt.settings import api_settings
    6. jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
    7. class JsonAuthentication(JSONWebTokenAuthentication):
    8. def authenticate_credentials(self, payload):
    9. username = payload['username']
    10. if not username:
    11. msg = 'Invalid payload.'
    12. raise exceptions.AuthenticationFailed(msg)
    13. try:
    14. user = LbUserInfo.objects.get(username=username)
    15. except Exception:
    16. msg = 'Invalid signature.'
    17. raise exceptions.AuthenticationFailed(msg)
    18. if not user.is_active:
    19. msg = 'User account is disabled.'
    20. raise exceptions.AuthenticationFailed(msg)
    21. return user
    22. def authenticate(self, request):
    23. jwt_value=self.get_jwt_value(request) # jwt_value=request.GET.get('token')
    24. # # 验证签名,验证是否过期
    25. try:
    26. payload = jwt_decode_handler(jwt_value) # 得到载荷
    27. # 取当前用户,拿到user对象,每登录一个人就要去数据库查一次
    28. # 这个也要重写,因为它找的是auth的user表,我们是去自己表中查
    29. user = self.authenticate_credentials(payload)
    30. # 效率更高一写,不需要查数据库了
    31. # user=LbUserInfo(id=payload['user_id'],username=payload['username']) # user={'id':payload['user_id'],'username':payload['username']}
    32. except jwt.ExpiredSignature:
    33. msg='token过期'
    34. raise exceptions.AuthenticationFailed(msg)
    35. except jwt.DecodeError:
    36. msg='签名错误'
    37. raise exceptions.AuthenticationFailed(msg)
    38. except jwt.InvalidTokenError:
    39. raise exceptions.AuthenticationFailed('错误')
    40. return (user, jwt_value)

    看看视图中怎么单个接口使用

    1. # jwt_view.py
    2. from rest_framework import status, viewsets
    3. from rest_framework.decorators import action
    4. from rest_framework.permissions import IsAuthenticated
    5. from rest_framework.response import Response
    6. from apps.library.utils.jwt_custom import JsonAuthentication
    7. class TestViewSets(viewsets.ViewSet):
    8. authentication_classes=[JsonAuthentication, ] # 单个使用
    9. permission_classes = [IsAuthenticated, ]
    10. @action(methods=["GET"], detail=False, url_path="test") # false代表不是路径传参,url后缀
    11. def test(self, request):
    12. print(11111)
    13. print(request.user)
    14. return Response("测试成功", status=status.HTTP_200_OK)
    15. def list(self, request):
    16. return Response("测试成功1", status=status.HTTP_200_OK)
    17. @action(methods=["GET"], detail=False, url_path="check") # false代表不是路径传参
    18. def check(self, request):
    19. return Response("测试成功2", status=status.HTTP_200_OK)

    注意:使用自定义用户表时,如果报错,object has no attribute 'is_authenticated',需要在models中定义方法

    1. # models.py
    2. class LbUserInfo(models.Model):
    3. username = models.CharField(max_length=100)
    4. fullname = models.CharField(max_length=100)
    5. password = models.CharField(max_length=100)
    6. telphone = models.IntegerField(blank=True, null=True)
    7. create_time = models.IntegerField(default=int(time.time()))
    8. email = models.CharField(max_length=100, blank=True, null=True)
    9. is_active = models.IntegerField(default=0)
    10. class Meta:
    11. managed = False
    12. db_table = 'lb_user_info'
    13. ordering = ("-create_time", )
    14. @property
    15. def is_authenticated(self):
    16. """
    17. Always return True. This is a way to tell if the user has been
    18. authenticated in templates.
    19. """
    20. return True
    21. def __str__(self):
    22. return f"id:{self.id},username:{self.username}"

    3. 不影响上面的扩展,不想使用drf的登录接口,可以自己写登录视图,然后随意写逻辑,最后手动生成签名并返回

    1. def get_token():
    2. from rest_framework_jwt.settings import api_settings
    3. jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
    4. jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
    5. user = {
    6. "user_id": 1,
    7. "username": "zhangsan"
    8. }
    9. payload = jwt_payload_handler(user)
    10. token = jwt_encode_handler(payload)
    11. print(token)

    三、pyjwt

    不想过多写了,这个好理解,网上多的是,原理是,封装一个生成token方法,登录接口返回,视图怎么校验使用呢,一是写个装饰器,装饰器中校验签名,二是继承BaseAuthentication,重写authenticate方法,跟上面使用一样,authentication_classes = [重写的类 ]

    pip install pyjwt

  • 相关阅读:
    《向量数据库指南》——向量数据库与 ANN 算法库的区别
    畅捷通T+ v17任意文件上传漏洞复现
    台式电脑怎么格式化重装系统
    Redis中有大量未设置过期时间的缓存应该如何处理?
    Lock接口
    C# 将PDF文档转换为Word文档
    Git入门
    Java释疑
    菜鸟Linux(3):环境变量
    spring boot +vue 博客系统,开源的资源网站
  • 原文地址:https://blog.csdn.net/a961634066/article/details/126303784