如何在Django实现API配合JWT进行用户验证的方法?

关注者
3
被浏览
1,688

2 个回答

JSON Web Token(JWT)是目前 Token 鉴权机制下最流行的方案,鉴于网上 JWT的介绍众多且不完整,在 Django 项目中如何利用 JWT 实现对 API 的认证有这自己的开发方法。

本实力实现非认证用户不能访问指定内容的功能。

传统的 session 和 JWT 的区别

我们以一个用户,获取用户资料的例子。

session 流程



JWT 流程



发现了吗?好些并没有什么区别,除了session需要服务端存储一份,而 JWT不需要。

  1. session 存储在服务端占用服务器资源,而 JWT 存储在客户端。
  2. session 存储在 Cookie 中,存在伪造跨站请求伪造攻击的风险。
  3. session 只存在一台服务器上,那么下次请求就必须请求这台服务器,不利于分布式应用。
  4. 存储在客户端的 JWT 比存储在服务端的 session 更具有扩展性。

JWT的工作原理



Django服务端配置流程

安装

pip install djangorestframework-jwt

settings.py配置

### 根据实际情况进行选择
# token rest framework 配置实现
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        # 'rest_framework.permissions.IsAuthenticated',            # IsAuthenticated 仅通过认证的用户
        # 'rest_framework.permissions.AllowAny',                   # AllowAny 允许所有用户
        # 'rest_framework.permissions.IsAdminUser',                # IsAdminUser 仅管理员用户
        # 'rest_framework.permissions.IsAuthenticatedOrReadOnly',  # IsAuthenticatedOrReadOnly 认证的用户可以完全操作,否则只能get读取
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # 'rest_framework_simplejwt.authentication.JWTAuthentication',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',  # 在 DRF中配置JWT认证
        'rest_framework.authentication.BasicAuthentication',  # 在 DRF中基础认证信息,特定认证修改使用
        # 'rest_framework.authentication.TokenAuthentication',  # 全局Token认证
        # 'rest_framework.authentication.SessionAuthentication',  # 全局Session认证
        # 'article.auth.MyTokenAuthentication',  # 自定义的带过期的认证
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
    # 新版drf schema_class默认用的是rest_framework.schemas.openapi.AutoSchema
# 配置jwt载荷中的有效期设置
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),  # 设置Token有效期
    'JWT_AUTH_HEADER_PREFIX': 'JWT',  # token前缀:headers中 Authorization 值的前缀
    'JWT_ALLOW_REFRESH': True,  # 刷新token:允许使用旧的token换新token
    # 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(hours=24),# token有效期:token在24小时内过期, 可续期token
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',  # 5.自定义JWT载荷信息:自定义返回格式,需要手工创建 utils.py的路径和其中方法
}

user/urls.py

from django.urls import include, path
from .views import *
from rest_framework.routers import DefaultRouter
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token
# 自动生成路由方法
router = DefaultRouter()
......
urlpatterns = [
    # path('user_token/',CreateUserTokenViewSet.as_view(),name="user_token"),# 用户Token
    path('token_login/', obtain_jwt_token),  # 获取token,登录视图
    path('token_refresh/', refresh_jwt_token),  # 刷新token
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),  # 认证地址
urlpatterns += router.urls  # 模块地址

项目/urls.py

urlpatterns = [
    ......
    # include 各个模块的内容
    path('UserSettings/', include("users.urls")),  # apps.users里面的内容
urlpatterns += router.urls

user/utils.py

# 该代码修改源码里的内容,return中返回的内容可以自行修改
def jwt_response_payload_handler(token, user=None, request=None, role=None):
    自定义jwt认证成功返回数据
    :token 返回的jwt
    :user 当前登录的用户信息[对象]
    :request 当前本次客户端提交过来的数据
    :role 角色
    if user.first_name:
        name = user.first_name
    else:
        name = user.username
        return {
            'authenticated': 'true',
            'id': user.id,
            "role": role,
            'name': name,
            'username': user.username,
            'email': user.email,
            'token': token,
        }

应用中设置验证权限

from rest_framework.permissions import IsAuthenticated, BasePermission
from rest_framework_jwt.authentication import JSONWebTokenAuthentication  # jwt用户认证
# JWT 权限设置
class MyPermission(BasePermission):
    message = '自定义的返回信息'
    def has_permission(self, request, view):  # 列表数据
        # # 这个函数返回True或者False,True表示有权限,False表示没有权限,这个函数同时有三个参数,最后一个是view, 这个是在源码中规定的
        # if request.user.id == 0:
        #     return False
        # else:
        return True
    def has_object_permission(self, request, view, obj):  # 对象数据
        """用户是否有权限访问添加了权限控制类的数据对象"""
        # 需求:用户能够访问id为1,3的对象,其他的不能够访问
        if request.user.is_active == 1:
            return True
        else:
            return False
# 内容详情视图,只有验证通过的才能访问文章正文信息
class ArticleDetailListViewSet(viewsets.ReadOnlyModelViewSet):
    permission_classes = (IsAuthenticated, MyPermission)  # 设置用户认证
    authentication_classes = (JSONWebTokenAuthentication,)  # 只接受 jwt token认证
    ......

测试JWT

获取JWT token



JWT token访问



前端使用JWT信息

这里举例将JWT保存到浏览器本地。

Javascript 访问API数据

const token = localStorage.getItem('jwt_token'); //获取本地存储的token
console.log(token);
const info_slug = location.href.split("=")[1]
console.log(info_slut)
function VisitDetail() {
    $.ajax({
        type: 'get',
        dataType: 'json',
        contentType: 'application/json',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'JWT ' + token,  //这里有个空格
        url: 'http://127.0.0.1:8000/ArticleSettings/ArticleDetail/' + info_slug,
        success: function (result) {
            console.log(result) // 显示返回API数据结果
            $("#article_detail").html(result.detail_content); //在html页面id=test的标签里显示html内容
        error: function (response, ajaxOptions, thrownError) {
            $("#article_detail").html("登陆后方可浏览");

HTML详情页部分

<div class="main" id="article_detail">
     {{ detail_content |safe }}
 </div>