美文网首页Metis
Django rest framework使用jwt认证前后端分

Django rest framework使用jwt认证前后端分

作者: 万州客 | 来源:发表于2020-11-13 16:42 被阅读0次

    解决了跨域资源共享CORS,自定义认证方式,自定义登陆返回及自定义错误返回,返回中包含token过期时间。不更改源代码,第一次优雅实现,值得记录。

    截屏2020-11-13下午4.11.28.png

    一,Django Rest Framework中的跨域

    1,安装django-cors-headers

    pip install django-cors-headers

    2,settings.py文件配置更新

    ...
    INSTALLED_APPS = [
        ...
        'rest_framework',
        'corsheaders',
    ]
    MIDDLEWARE = [
        ...
        'django.contrib.sessions.middleware.SessionMiddleware',
        'corsheaders.middleware.CorsMiddleware',
        'django.middleware.common.CommonMiddleware',
        ...
    ]
    ...
    CORS_ALLOW_CREDENTIALS = True
    CORS_ORIGIN_ALLOW_ALL = True
    
    CORS_ORIGIN_WHITELIST = [
        "https://example.com",
        "https://sub.example.com",
        "http://localhost:8080",
        "http://localhost:8000",
        "http://127.0.0.1:8000"
    ]
    

    二,Django Rest Framework中的jwt认证

    1,安装djangorestframework-jwt

    pip install djangorestframework-jwt

    2,settings.py文件配置更新

    ...
    REST_FRAMEWORK = {
        # Use Django's standard `django.contrib.auth` permissions,
        # or allow read-only access for unauthenticated users.
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
        ],
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.BasicAuthentication',
        ),
    }
    
    # 自定义obtain_jwt_token登录参数验证
    AUTHENTICATION_BACKENDS = (
        'custom_jwt.views.CustomJwtBackend',
    )
    JWT_AUTH = {
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=365),
        'JWT_AUTH_HEADER_PREFIX': 'JWT',
        'JWT_ALLOW_REFRESH': True,
    }
    ...
    
    • custom_jwt目录名如果要分隔,一定是下划线,短横杠符号不识别,切记!!!*

    3,urls.py文档示例

    from django.contrib import admin
    from django.urls import path, include
    from django.contrib.auth.models import User
    from rest_framework import routers, serializers, viewsets
    # from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token
    from custom_jwt import views as jwt_views
    
    
    # Serializers define the API representation.
    class UserSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = User
            fields = ['url', 'username', 'email', 'is_staff']
    
    
    # ViewSets define the view behavior.
    class UserViewSet(viewsets.ModelViewSet):
        queryset = User.objects.all()
        serializer_class = UserSerializer
    
    
    # Routers provide an easy way of automatically determining the URL conf.
    router = routers.DefaultRouter()
    router.register(r'users', UserViewSet)
    
    
    urlpatterns = [
        path('', include(router.urls)),
        path('admin/', admin.site.urls),
        path('api-auth/', include('rest_framework.urls')),
        path('jwt_auth/', jwt_views.obtain_jwt_token),
        path('refresh_jwt_auth/', jwt_views.refresh_jwt_token),
        path('verify_jwt_auth/', jwt_views.verity_jwt_token),
    ]
    

    obtain_jwt_token, refresh_jwt_token这些函数都是自定义的,实现了自定义的登陆成功和失败的返回。这是不改源码的关键思路,切记切记!!!

    4,custom_jwt目录下的views.py内容

    
    from datetime import datetime
    from django.contrib.auth.backends import ModelBackend
    from django.contrib.auth import get_user_model
    from django.db.models import Q
    from rest_framework import status
    from rest_framework.response import Response
    from rest_framework_jwt.settings import api_settings
    from rest_framework_jwt.views import JSONWebTokenAPIView
    from rest_framework_jwt.views import ObtainJSONWebToken
    from rest_framework_jwt.views import RefreshJSONWebToken
    from rest_framework_jwt.views import VerifyJSONWebToken
    
    User = get_user_model()
    
    
    class CustomJwtBackend(ModelBackend):
        """
        自定义用户验证,定义完之后还需要在settings中进行配置
        """
        def authenticate(self, request, username=None, password=None, **kwargs):
            try:
                user = User.objects.get(Q(username=username) | Q(email=username))
                # django里面的password是加密的,前端传过来的password是明文,
                # 调用check_password就会对明文进行加密,比较两者是否相同
                if user.check_password(password):
                    return user
            except Exception as e:
                return None
    
    
    # 不改rest_framework_jwt源码的情况下,自定义登陆后成功和错误的返回,最优雅
    def jwt_response_payload_handler(token, user=None, expiration=None):
        """
        自定义jwt认证成功返回数据
        """
        data = {
            'token': token,
            'expireAt': expiration,
            'user_id': user.id,
            'user': user.username,
            'is_superuser': user.is_superuser,
            'permissions': 'admin',
            'roles': ['admin']
        }
        return {'code': 0, 'data': data}
    
    
    def jwt_response_payload_error_handler(serializer, requst=None):
        """
        自定义jwt认证错误返回数据
        """
        data = {
            'message': "用户名或者密码错误",
            'status': 400,
            'detail': serializer.errors,
        }
        return {'code': -1, 'data': data}
    
    
    # jwt的返回,由JSONWebTokenAPIView,自定义它的调用和返回即可
    class CustomWebTokenAPIView(JSONWebTokenAPIView):
        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')
                expiration = (datetime.utcnow() +
                              api_settings.JWT_EXPIRATION_DELTA)
                response_data = jwt_response_payload_handler(token, user, expiration)
                response = Response(response_data)
                if api_settings.JWT_AUTH_COOKIE:
                    response.set_cookie(api_settings.JWT_AUTH_COOKIE,
                                        token,
                                        expiration=expiration,
                                        httponly=True)
                return response
            error_data = jwt_response_payload_error_handler(serializer, request)
            return Response(error_data, status=status.HTTP_200_OK)
    
    
    class CustomObtainJSONWebToken(ObtainJSONWebToken, CustomWebTokenAPIView):
        pass
    
    
    class CustomRefreshJSONWebToken(RefreshJSONWebToken, CustomWebTokenAPIView):
        pass
    
    
    class CustomVerifyJSONWebToken(VerifyJSONWebToken, CustomWebTokenAPIView):
        pass
    
    
    obtain_jwt_token = CustomObtainJSONWebToken.as_view()
    refresh_jwt_token = CustomRefreshJSONWebToken.as_view()
    verity_jwt_token = CustomVerifyJSONWebToken.as_view()
    

    用户认证时,新增了邮件方式认证,jwt_response_payload_handler默认第3个参数是request,我在返回里没有用上,使用token过期时间来代替换,使前端能获取到后端token的过期时间。这个正确和错误的返回json,是按vue-antd-admin项目的要求返回的。

    三,curl测试用户及邮件认证,正确和错误的返回

    1,用户错误

    curl -X POST -d "username=sky" -d "password=passwor" http://localhost:8000/jwt_auth/
    {"code":-1,"data":{"message":"用户名或者密码错误","status":400,"detail":{"non_field_errors":["Unable to log in with provided credentials."]}}}
    

    2,用户正常

    curl -X POST -d "username=sky" -d "password=password" http://localhost:8000/jwt_auth/
    {"code":0,"data":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InNreSIsImV4cCI6MTYzNjc5MjE0NSwiZW1haWwiOiJza3lAZGVtby5jb20iLCJvcmlnX2lhdCI6MTYwNTI1NjE0NX0.-6R-oya1XTWSwht-6Wi2bcER__44H_9psK-2x1Ni1D4","expireAt":"2021-11-13T08:29:05.448980","user_id":2,"user":"sky","is_superuser":false,"permissions":"admin","roles":["admin"]}}
    

    3,邮箱错误

    curl -X POST -d "username=sky@demo.com" -d "password=passwor" http://localhost:8000/jwt_auth/
    {"code":-1,"data":{"message":"用户名或者密码错误","status":400,"detail":{"non_field_errors":["Unable to log in with provided credentials."]}}}
    

    4,邮箱正确

    curl -X POST -d "username=sky@demo.com" -d "password=password" http://localhost:8000/jwt_auth/
    {"code":0,"data":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InNreSIsImV4cCI6MTYzNjc5MjA5NiwiZW1haWwiOiJza3lAZGVtby5jb20iLCJvcmlnX2lhdCI6MTYwNTI1NjA5Nn0.kZNZA4w2U5H5yb7G27KOu97KkISeooR_6wWd0V5xpFQ","expireAt":"2021-11-13T08:28:16.798464","user_id":2,"user":"sky","is_superuser":false,"permissions":"admin","roles":["admin"]}}
    

    四,vue-antd-admin登陆后的小更改

    好像vue-antd-admin在登陆错误时,信息提示和loading状态不友好,我所更改一下。
    Login.vue

    onSubmit (e) {
          e.preventDefault()
          this.form.validateFields((err) => {
            if (!err) {
              this.logging = true
              const name = this.form.getFieldValue('name')
              const password = this.form.getFieldValue('password')
              // 这里仍然是name,但在传递给后台的service那里,要更新成username参数
              login(name, password).then(this.afterLogin)
            }
          })
        },
        afterLogin(res) {
          this.logging = false
          const loginRes = res.data
          if (loginRes.code >= 0) {
            const {user, permissions, roles} = loginRes.data
                    const expireAt = new Date(loginRes.data.expireAt)
            this.setUser(user)
            this.setPermissions(permissions)
            this.setRoles(roles)
            // 增加debug数据透明度
            console.log(user, permissions, roles, loginRes.data.token, expireAt)
            setAuthorization({token: loginRes.data.token, expireAt: expireAt})
            // 获取路由配置
            getRoutesConfig().then(result => {
              const routesConfig = result.data.data
              loadRoutes(routesConfig)
            // 首页没作好,先显示次级页面
              this.$router.push('/release/list')
              this.$message.success(loginRes.message, 3)
            })
          } else {
                    // 原来的代码已注释,新加了logging状态终止,跳出弹窗3秒提示错误。
                    // this.error = loginRes.message
                    this.logging = false
                    this.$message.error(loginRes.data.message, 3)
          }
        }
    

    参考URL:

    https://blog.csdn.net/python_anning/article/details/109120654
    https://blog.csdn.net/qq_42327755/article/details/108486382
    https://blog.csdn.net/qq_35753140/article/details/90266283
    https://www.jianshu.com/p/a399b98ab05b

    相关文章

      网友评论

        本文标题:Django rest framework使用jwt认证前后端分

        本文链接:https://www.haomeiwen.com/subject/flxbbktx.html