十、Django REST framework 使用JWT认证方式

参考 Django REST framework JWT认证方式和BasicAuthentication认证方式

django, django_restful 关于Authentication的学习总结

一、基本使用

1、JWT的流程

在这里插入图片描述

2、安装jwt
pip  install  djangorestframework-jwt
3、在setting.py中设置三种认证方式,权限管理为仅通过认证的用户
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',  # #第一种jwt方式
        'rest_framework.authentication.SessionAuthentication',  # 第二种session方式
        'rest_framework.authentication.BasicAuthentication', #第三种Django的基本方式
    ),

    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

import datetime
JWT_AUTH = {  # 导包: import datetime
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),  # jwt有效时间
}

然后重启项目,这个时候如果再访问接口,会提示没有权限

在这里插入图片描述

4、创建一个user用户及/login路由
# coding=utf-8
import os,django,sys
sys.path.append('../')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "restframeworkdemo.settings")# project_name 项目名称
django.setup()
from django.contrib.auth.models import User

newuser = User.objects.create(username="wangxiaoyu2333")
newuser.set_password("123")
newuser.save()

urls.py中设置一个/login路由

from rest_framework_jwt.views import obtain_jwt_token
re_path(r'^login/$', obtain_jwt_token),
5、将刚才创建的用户名密码作为参数POST请求http://127.0.0.1:8000/login/,得到返回的token

在这里插入图片描述

6、再将这个jwt+“ ”+token作为头部的Authorization参数,请求接口,可以得到返回的信息,使用jwt认证成功

在这里插入图片描述

二、自定义用户类

1、Django认证系统中提供了用户模型类User保存用户的数据,默认的User包含以下常见的基本字段:

可以看之前的博客Django User用户模型

username

必选。 150个字符以内。 用户名可能包含字母数字,_,@,+ . 和-个字符。在Django更改1.10:max_length从30个字符增加到150个字符。

first_name

可选(blank=True)。 少于等于30个字符。

last_name

可选(blank=True)。 少于等于30个字符。

email

可选(blank=True)。 邮箱地址。

password

必选。 密码的哈希及元数据。 (Django 不保存原始密码)。 原始密码可以无限长而且可以包含任意字符。

groups

与Group 之间的多对多关系。

user_permissions

与Permission 之间的多对多关系。

is_staff

布尔值。 指示用户是否可以访问Admin 站点。

is_active

布尔值。 指示用户的账号是否激活。 我们建议您将此标志设置为False而不是删除帐户;这样,如果您的应用程序对用户有任何外键,则外键不会中断。它不是用来控制用户是否能够登录。 在Django更改1.10:在旧版本中,默认is_active为False不能进行登录。

is_superuser

布尔值。 指定这个用户拥有所有的权限而不需要给他们分配明确的权限。

last_login

用户最后一次登录的时间。

date_joined

账户创建的时间。 当账号创建时,默认设置为当前的date/time。
2、常用方法:
set_password(raw_password)

设置用户的密码为给定的原始字符串,并负责密码的。 不会保存User 对象。当None 为raw_password 时,密码将设置为一个不可用的密码。

check_password(raw_password)

如果给定的raw_password是用户的真实密码,则返回True,可以在校验用户密码时使用。

管理器方法:
管理器方法即可以通过User.objects. 进行调用的方法。

create_user(username, email=None, password=None, *\extra_fields*)

创建、保存并返回一个User对象。

create_superuser(username, email, password, *\extra_fields*)

与create_user() 相同,但是设置is_staff 和is_superuser 为True。
3、创建自定义的用户模型类

Django认证系统中提供的用户模型类及方法很方便,我们可以使用这个模型类,但是字段有些无法满足项目需求,如需要保存用户的手机号,需要给模型类添加额外的字段。

Django提供了django.contrib.auth.models.AbstractUser用户抽象模型类允许我们继承,扩展字段来使用Django认证系统的用户模型类。

我们创建Django应用users,并在配置文件中注册users应用。

在创建好的应用models.py中定义用户的用户模型类。

class User(AbstractUser):
    """用户模型类"""
    mobile = models.CharField(max_length=11, verbose_name='手机号')

    class Meta:
        db_table = 'tb_users'
        verbose_name = '用户'
        verbose_name_plural = verbose_name

我们自定义的用户模型类还不能直接被Django的认证系统所识别,需要在配置文件中告知Django认证系统使用我们自定义的模型类。

在配置文件中进行设置

AUTH_USER_MODEL = 'users.User'
AUTH_USER_MODEL 参数的设置以点.来分隔,表示应用名.模型类名。

注意:Django建议我们对于AUTH_USER_MODEL参数的设置一定要在第一次数据库迁移之前就设置好,否则后续使用可能出现未知错误。

执行数据库迁移

python manage.py makemigrations
python manage.py migrate

三、用户类序列号器和视图处理类

1、序列化器serializers.py
from rest_framework import serializers

from .models import User


class UserModelSerializer(serializers.ModelSerializer):


    class Meta:
        model = User

        #在put方法更新的时候,只会更新fields里并且没有设为只读的字段,就算传入了其他的字段也不会生效
        fields = ("id","username","first_name","mobile")  # []

        read_only_fields = ["id"]

        # exclude =
        # read_only_fields = []
        # extra_kwargs = {'bread': {'min_value': 0, }}
2、视图处理类

这里有一部分是我自己的处理逻辑

class UserListAPIView(APIView):
    '''
    信息查询及新增
    '''
    queryset = User.objects.all()
    serializer_class =  UserModelSerializer
    pagination_class = StandardPageNumberPagination

    def get(self, request,):
        """查询所有"""

        keyword=request.query_params.get("keyword")
        if keyword==None:
            keyword=""

        queryset = User.objects.filter(Q(username__icontains=keyword)|Q(first_name__icontains=keyword))

        pagePagination = StandardPageNumberPagination()   # 产生一个自定义分页器对象
        pagedata = pagePagination.paginate_queryset(queryset, request)  # #获取分页的数据,必须传两个参数,第一个queryset对象要分页的所有数量,第二个参数request
        serializer = UserModelSerializer(instance=pagedata, many=True)  #对数据进行序列化
        pageresponse = pagePagination.get_paginated_response(serializer.data)  # 将序列化后的数据再转为分页格式的输出,有带count等参数,如果没有这一步,则还是默认的输出方式
        return Response(pageresponse.data,status=status.HTTP_200_OK)

    def post(self, request):
        """新增"""
        data = request.data
        username = request.data.get("username")
        first_name = request.data.get("first_name")

        if first_name==None:
            first_name = username.split("@")[0]
        elif not first_name.strip():
            first_name = username.split("@")[0]
        else:pass

        data["first_name"]=first_name

        # 创建序列化器进行反序列化
        serializer = UserModelSerializer(data=data)
        # 调用序列化器的is_valid方法进行校验
        serializer.is_valid(raise_exception=True)
        # 调用序列化器的save方法进行执行create方法
        serializer.save()
        # 响应
        return Response(serializer.data, status=status.HTTP_201_CREATED)




class UserDetailAPIView(RetrieveUpdateDestroyAPIView):
    """
    查询详情、修改及删除。直接继承RetrieveUpdateDestroyAPIView的三个方法
    """
    queryset = User.objects.all()
    serializer_class =  UserModelSerializer
3、路由
from . import views
from django.urls import path,re_path,include


app_name="users"

urlpatterns = [
    # 列表视图的路由APIView
    re_path(r'^users/$', views.UserListAPIView.as_view()),
    # 详情视图的路由APIView
    re_path(r'^users/(?P<pk>\d+)/$', views.UserDetailAPIView.as_view()),
]

四、JWT基本介绍

1、什么是JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

2、传统的session认证

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.

3、基于session认证所显露的问题

Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

4、基于token的鉴权机制

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程上是这样的:

用户使用用户名密码来请求服务器
服务器进行验证用户的信息
服务器通过验证发送给用户一个token
客户端存储token,并在每次请求时附送上这个token值
服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *
5、JWT长什么样?

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
6、JWT的构成

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

(1)header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256
    完整的头部就像下面这样的JSON:
{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
(2)payload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

标准中注册的声明

  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
  • 公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

  • 私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行base64加密,得到JWT的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature
(3)JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

7、如何应用

一般是在请求头里加入Authorization,并加上Bearer标注:

fetch('api/user/1', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:

在这里插入图片描述

8、总结
  • 优点
因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
它不需要在服务端保存会话信息, 所以它易于应用的扩展
  • 安全相关
不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
保护好secret私钥,该私钥非常重要。
如果可以,请使用https协议

五、Django REST framework JWT手动签发JWT

1、原因

我们在验证完用户的身份后(检验用户名和密码或者其他条件),需要向用户签发JWT,在需要用到用户身份信息的时候,还需核验用户的JWT。

在最前面使用的默认的jwt方式from rest_framework_jwt.views import obtain_jwt_token re_path(r'^login/$', obtain_jwt_token),,配置后访问登陆函数,在登陆函数中自动为我们签发jwt,但有时候我们的流程和django默认的情况并不一样,这时候就需要自己手动签发JWT。

关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。

文档网站http://getblimp.github.io/django-rest-framework-jwt/

2、安装配置

安装

pip install djangorestframework-jwt

配置认证方式和权限,只有认证过的用户才有访问权限

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
    
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=30),
}
#JWT_EXPIRATION_DELTA 指明token的有效期
3、使用

Django REST framework JWT 扩展的说明文档中提供了手动签发JWT的方法

from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)

在注册成功后,连同返回token,需要在注册视图中创建token。

修改CreateUserSerializer序列化器,在create方法中增加手动创建token的方法

from rest_framework_jwt.settings import api_settings

class CreateUserSerializer(serializers.ModelSerializer):
    """
    创建用户序列化器
    """
    ...
    token = serializers.CharField(label='登录状态token', read_only=True)  # 增加token字段

    class Meta...
        fields = ('id', 'username', 'password', 'password2', 'sms_code', 'mobile', 'allow', 'token')  # 增加token
        ...

    def create(self, validated_data):
        """
        创建用户
        """
        user = super().create(validated_data)

        # 调用django的认证系统加密密码
        user.set_password(validated_data['password'])
        user.save()

        # 补充生成记录登录状态的token
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER # 引用jwt中的叫jwt_payload_handler函数(生成payload)
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER # 函数引用 生成jwt
        payload = jwt_payload_handler(user)   # 根据user生成用户相关的载荷
        token = jwt_encode_handler(payload)  # 传入载荷生成完整的jwt
        user.token = token

        return user

前端保存token
我们可以将JWT保存在cookie中,也可以保存在浏览器的本地存储里,我们保存在浏览器本地存储中

浏览器的本地存储提供了sessionStorage 和 localStorage 两种:

sessionStorage 浏览器关闭即失效
localStorage 长期有效

使用方法

sessionStorage.变量名 = 变量值   // 保存数据
sessionStorage.变量名  // 读取数据
sessionStorage.clear()  // 清除所有sessionStorage保存的数据

localStorage.变量名 = 变量值   // 保存数据
localStorage.变量名  // 读取数据
localStorage.clear()  // 清除所有localStorage保存的数据

在前端js/register.js文件中增加保存token

var vm = new Vue({
    ...
    methods: {
        ...
        on_submit: function(){
            axios.post(...)
                .then(response => {
                    // 记录用户的登录状态
                    sessionStorage.clear();
                    localStorage.clear();
                    localStorage.token = response.data.token;
                    localStorage.username = response.data.username;
                    localStorage.user_id = response.data.id;
                    location.href = '/index.html';
                })
                .catch(...)
        }
    }
})

六、账号登陆

1、业务说明

验证用户名和密码,验证成功后,为用户签发JWT,前端将签发的JWT保存下来。

2、后端接口设计

请求方式: POST /authorizations/

请求参数: JSON 或 表单

参数名类型是否必须说明
usernamestr用户名
passwordstr密码

返回数据: JSON

{
    "username": "python",
    "user_id": 1,
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo5LCJ1c2VybmFtZSI6InB5dGhvbjgiLCJleHAiOjE1MjgxODI2MzQsImVtYWlsIjoiIn0.ejjVvEWxrBvbp18QIjQbL1TFE0c0ejQgizui_AROlAU"
}
返回值类型是否必须说明
usernamestr用户名
user_idint用户id
tokenstr身份认证凭据
3、后端实现

Django REST framework JWT提供了登录签发JWT的视图,可以直接使用

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    url(r'^authorizations/$', obtain_jwt_token),
]

但是默认的返回值仅有token,我们还需在返回值中增加username和user_id。

通过修改该视图的返回值可以完成我们的需求。

在users/utils.py 中,创建

def jwt_response_payload_handler(token, user=None, request=None):
    """
    自定义jwt认证成功返回数据
    """
    return {
        'token': token,
        'user_id': user.id,
        'username': user.username
    }

修改配置文件

# JWT
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=30),
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
}
4、增加支持用户名与手机号均可作为登录账号

JWT扩展的登录视图,在收到用户名与密码时,也是调用Django的认证系统中提供的authenticate()来检查用户名与密码是否正确。

我们可以通过修改Django认证系统的认证后端(主要是authenticate方法)来支持登录账号既可以是用户名也可以是手机号。

修改Django认证系统的认证后端需要继承django.contrib.auth.backends.ModelBackend,并重写authenticate方法。

authenticate(self, request, username=None, password=None, **kwargs)方法的参数说明:

request 本次认证的请求对象
username 本次认证提供的用户账号
password 本次认证提供的密码

我们想要让用户既可以以用户名登录,也可以以手机号登录,那么对于authenticate方法而言,username参数即表示用户名或者手机号。

重写authenticate方法的思路:

根据username参数查找用户User对象,username参数可能是用户名,也可能是手机号
若查找到User对象,调用User对象的check_password方法检查密码是否正确
在users/utils.py中编写:

from django.contrib.auth.backends import ModelBackend
import re

def get_user_by_account(account):
    """
    根据帐号获取user对象
    :param account: 账号,可以是用户名,也可以是手机号
    :return: User对象 或者 None
    """
    try:
        if re.match('^1[3-9]\d{9}$', account):
            # 帐号为手机号
            user = User.objects.get(mobile=account)
        else:
            # 帐号为用户名
            user = User.objects.get(username=account)
    except User.DoesNotExist:
        return None
    else:
        return user

#在这里可以自定义认证后端的方式,也可以使用其他的方式,在authenticate方法里进行定义
class UsernameMobileAuthBackend(ModelBackend):
    """
    自定义用户名或手机号认证,如果认证没通过,默认返回None
    """

    def authenticate(self, request, username=None, password=None, **kwargs):
        user = get_user_by_account(username)
        if user is not None and user.check_password(password):
            return user

在配置文件中告知Django使用我们自定义的认证后端

AUTHENTICATION_BACKENDS = [
    'users.utils.UsernameMobileAuthBackend',
]

注意,如果不调用 django.contrib.auth.authenticate. 那么setting. AUTHENTICATION_BACKENDS根本用不上,就不会被调用。

七、自定义jwt方式login登陆

前面直接使用url(r'^authorizations/$', obtain_jwt_token),这个定义好的方法来处理登陆验证请求,查看obtain_jwt_token源码的序列化器可以发现,这个方法获取了username和password这两个post过去的参数,并且只把这两个参数传递给authenticate()方法

也就是说,authenticate()中其实只能收到username和password这两个这两个参数。但如果有时候我们需要传其他的参数,或者需要使用其他的参数名称的时候,直接使用obtain_jwt_token便不能满足我们的需求了。
在这里插入图片描述

这个时候可以自己定义登陆函数。不过需要自己生成token、定义返回的格式等。例如我这里使用邮箱前缀、后缀、密码这几个参数来进行登陆

class JwtLoginAPIView(APIView):
    '''
    使用JWT的方式登录
    '''
    # 认证方式和权限都设为空,表示不需要认证就可以访问这个接口
    permission_classes = ()
    authentication_classes = ()

    def post(self, request):
        mailprefix = request.data.get("mailprefix")
        mailsuffix = request.data.get("mailsuffix")
        password = request.data.get("password")
        email = mailprefix + mailsuffix
        
        ret = ldap_auth(username=mailprefix, password=password, DC=dc)
        if ret:
            # 如果认证成功,判断user表中是否存在该用户
            user = User.objects.filter(username=email)
            # 如果用户存在,则设置session
            if user.exists():
                # 获取在项目setting中的设置来生成token
                jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
                jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
                # 生成token
                payload = jwt_payload_handler(user[0])
                token = jwt_encode_handler(payload)

                #自定义返回的格式
                return Response({
                    'token': token,
                    'user_id': user[0].id,
                    'username': user[0].username
                },status.HTTP_200_OK)
            # 如果用户不存在,则表示没有权限登陆
            else:
                raise AuthenticationFailed("权限不足")

        # 如果认证失败,返回状态码0
        else:
            # print(request.GET.get('next'))
            raise AuthenticationFailed("账号密码认证失败")

签发和校验token的逻辑可以参考drf中的jwt使用与手动签发token、校验用户

八、drf使用session方式登录

有时候我们仍然需要使用session的方式来进行登陆验证。

在setting.py中设置认证方式,并设置权限为登陆的才能访问。可以看到认证方式我们设置了三种,第一种是jwt方式,第二种是基于django的session进行认证,第三种是基础的认证。如果有一种认证通过即可。

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
}

session登陆方式的login视图函数,认证成功后调用login函数设置sessionid这个cookies,并且返回sessionid和用户信息。

class SessionLoginAPIView(APIView):
    '''
    使用Session的方式登录
    '''

    # 认证方式和权限都设为空,表示不需要认证就可以访问这个接口
    permission_classes = ()
    authentication_classes = ()

    def post(self, request):
        mailprefix = request.data.get("mailprefix")  
        mailsuffix = request.data.get("mailsuffix") 
        password = request.data.get("password")
        email = mailprefix + mailsuffix 
        if ret:
            # 如果认证成功,判断user表中是否存在该用户
            user = User.objects.filter(username=email)
            # 如果用户存在,则设置session
            if user.exists():
                login(request, user[0])
                # 设置为None,则表示使用全局的过期时间
                request.session.set_expiry(None)
                #先保存,不然第一次登录会获取不到sessionid
                request.session.save()
                return Response(
                    {"sessionid": request.session.session_key, 'user_id': user[0].id, 'username': user[0].username},
                    status=status.HTTP_200_OK)
            # 如果用户不存在,则表示没有权限登陆
            else:

                raise AuthenticationFailed("权限不足")
        # 如果认证失败,返回状态码0
        else:
            raise AuthenticationFailed("账号密码认证失败")

urls.py

re_path(r'^sessionlogin/$', views.SessionLoginAPIView.as_view()),

Django Rest Framework关闭CSRF验证

上面使用session的方式,在post的时候,会出现detail": "CSRF Failed: CSRF cookie not set这样的报错,因为SessionAuthenticationDRF使用的默认方案。DRF SessionAuthentication使用Django的会话框架进行身份验证,需要检查CSRF。

方法一

自己写一个中间件,取消django-restful的csrf验证

from django.utils.deprecation import MiddlewareMixin

class DisableCSRFCheck(MiddlewareMixin):
    def process_request(self, request):
        setattr(request, '_dont_enforce_csrf_checks', True)

把这个中间件添加到项目setting.py的中间件配置中

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'testproject.test.DisableCSRFCheck',
)
方法二

继承SessionAuthentication自定义一个认证中间件CsrfExemptSessionAuthentication,重写enforce_csrf方法

from rest_framework.authentication import SessionAuthentication
 
class CsrfExemptSessionAuthentication(SessionAuthentication): 
 
    def enforce_csrf(self, request): 
        return  # To not perform the csrf check previously happening

在视图中使用
authentication_classes = (CsrfExemptSessionAuthentication, BasicAuthentication)

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值