如何让 Python 写的 API 接口同时支持 Session 和 Token 认证?

2021-06-21 20:05:10 浏览数 (1)

Django 是 Python 语言中最受欢迎的 Web 框架之一。其开箱即用的特性,使得我们可以利用它快速搭建一个传统的 Web 应用。

在如今多端横行的互联网,单纯的传统 Web 应用开发已经越来越式微,更多的应用采用了前后端分离的 Web 开发模式,后端只是单纯地提供 API 给前端各个终端(Web、APP、小程序等)调用。

借助于 Django REST Framework 这个第三方库,Django 也能快速生成 RESTful 风格的 API 接口。

通常情况下,需要用户进行登录的 API,我们都统一使用 Token 来进行认证,这样可以确保接口对多端的支持。但是 Django 在 Web 网页端的功能实在是太好用了,以至于很多人舍不得放弃 Django 自带的认证功能。

如果让 Django 写的接口既支持 Token 认证,也能兼容 Django 自带的 Session 认证呢?DRF 框架本身就提供了支持。

DRF 支持的认证模式

REST framework 提供了许多开箱即用的身份认证方案,还允许自定义认证方案。

它一共提供了如下几种认证方案:

  • BasicAuthentication(HTTP Basic 认证):用于根据用户名和密码进行 HTTP 基础身份认证。
  • TokenAuthentication(Token 认证):用于简单的基于 Token 的认证方案,这种方案适合于 CS 模式的应用。
  • SessionAuthentication(Session 认证):使用 Django 的默认会话后端进行身份验证。会话身份验证适用于与网站在相同的会话中运行的 AJAX 客户端。
  • RemoteUserAuthentication(远程用户分组):这种身份认证允许将身份认证交给另一个 Web 服务器(通过设置REMOTE_USER变量指定认证服务器地址)

除此之外,我们还能自定义身份认证,只需要继承BaseAuthentication类进行扩展即可。

在 DRF 中使用认证

在 DRF 框架中,可以通过 2 种方式配置认证方式。

一种是在 Django 的配置文件中通过 REST_FRAMEWORK变量全局设置认证模式,例如:

代码语言:javascript复制
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ]
}

另一种则是在视图中通过authentication_classes属性单独指定每一个视图的认证模式,例如:

代码语言:javascript复制
from rest_framework.authentication import SessionAuthentication
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
    authentication_classes = [SessionAuthentication]

    def get(self, request, format=None):
        content = {
            'user': str(request.user),  # `django.contrib.auth.User` instance.
            'auth': str(request.auth),  # None
        }
        return Response(content)

使用多种认证模式

在上面的示例中,我们可以看到,DRF 的认证模式配置接收的是一个列表,其实我们可以在里面添加多种认证模式。例如:

代码语言:javascript复制
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
class ExampleView(APIView):
    authentication_classes = [SessionAuthentication, BasicAuthentication]

这样,这个接口就可以通过两种方式进行身份认证。

需要特别注意的一点是,如果使用 Session 认证,那么在登录页面的时候,需要使用 Django 默认的登录视图进行登录操作。

同时,在 Web 页面进行接口请求的时候,需要在 headers 头里面带上X-CSRFToken参数,其值为 Django 的 csrf_token,例如:

代码语言:javascript复制
headers: {"X-CSRFToken":'{{ csrf_token }}'},

多认证方式接口示例

「觅道文档」中,我们就采用了这样的双认证方式来处理接口的认证。

例如,在用户列表接口中(/Mrdoc/app_admin/views.py 文件 283 行附近),我们是这样定义接口的:

代码语言:javascript复制
# 后台管理 - 用户列表接口
class AdminUserList(APIView):
    authentication_classes = [SessionAuthentication,AppMustAuth]
    permission_classes = [SuperUserPermission]
    # 获取用户列表
    def get(self, request):
        username = request.query_params.get('username', '')
        page_num = request.query_params.get('page', 1)
        limit = request.query_params.get('limit', 10)
        if username == '':
            user_data = User.objects.all().values(
                'id', 'last_login', 'is_superuser', 'username', 'email', 'date_joined', 'is_active', 'first_name'
            )
        else:
            user_data = User.objects.filter(username__icontains=username).values(
                'id', 'last_login', 'is_superuser', 'username', 'email', 'date_joined', 'is_active', 'first_name'
            )
        page = PageNumberPagination()  # 实例化一个分页器
        page.page_size = limit
        page_users = page.paginate_queryset(user_data, request, view=self)  # 进行分页查询
        serializer = UserSerializer(page_users, many=True)  # 对分页后的结果进行序列化处理
        resp = {
            'code': 0,
            'data': serializer.data,
            'count': user_data.count()
        }
        return Response(resp)

这里面我们使用了SessionAuthenticationAppMustAuth这两个认证方式,其中AppMustAuth是我们自定义的 Token 认证方式,其代码如下所示:

代码语言:javascript复制
class AppMustAuth(BaseAuthentication):
    '''自定义认证类'''
    def authenticate(self, request):
        token = request.query_params.get('token')
        # print(token)
        if token:
            # 如果请求url中携带有token参数
            user_obj = AppUserToken.objects.filter(token=token).first()
            if user_obj:
                # print("ok")
                # token 是有效的,返回一个元组
                return user_obj.user, token  # request.user, request.auth
            else:
                raise AuthenticationFailed(_('无效的token'))
        else:
            raise AuthenticationFailed(_('请求的URL中必须携带token参数'))

如果我们在未登录或不带 Token 的情况下访问接口,会直接响应 403 Forbidden:

如果浏览器未登录状态下访问接口,会直接响应 403 Forbidden:

如果我们在浏览器登录状态下访问接口,会响应成功:

如果我们在接口中携带 Token 参数,也会响应成功:

有兴趣的小伙伴下载源码看一看、试一试。


?分享、点赞、在看,给个三连击呗!?

0 人点赞