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

解决了跨域资源共享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 "[email protected]" -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 "[email protected]" -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认证前后端分离项目)