返回

DRF Token认证:使用HttpOnly Cookie保障Token安全

python

DRF Token认证:将Token设置为HttpOnly Cookie

在使用Django REST Framework (DRF) 进行API开发时,常常会涉及到用户认证的问题。常见方式是使用Token认证,Djoser库为此提供便利。不过,将Token直接返回给前端并保存在例如localStorage的地方,会面临XSS攻击的安全隐患,从而泄露Token,这带来较大的安全风险。为了规避此类问题,可以考虑将access_token存放于httponly cookie中。本文将深入探讨如何操作,并分析此种方法的利弊。

问题根源

直接将access token 储存于前端,通过js代码即可轻易获取。如果出现跨站脚本漏洞,攻击者就可轻易窃取Token并冒充用户进行恶意操作。因此需要寻找更安全的方法。

HttpOnly cookie 的优势在于浏览器将阻止Javascript访问此类Cookie。这意味即便遭遇XSS攻击,攻击者也无法直接获取Cookie中的token,从而增加安全性。

解决方案:设置HttpOnly Cookie

以下给出实现过程,主要分成两部分:首先修改token的生成过程,将其放到httpOnly的cookie中;接着编写一个中间件,负责将httpOnly Cookie的Token取回,并放置在request请求头中,以此达到认证的目的。

  1. 修改Token生成过程

首先,覆盖djoserTokenCreateView视图,修改其中的_action方法,不再返回包含token的数据,而是将其写入httponly cookie:

from djoser.views import TokenCreateView
from rest_framework.response import Response
from django.conf import settings
from djoser import utils

class TokenCreateView(TokenCreateView):

    def _action(self, serializer):
        token = utils.login_user(self.request, serializer.user)
        token_serializer_class = settings.SERIALIZERS.token
        
        response = Response() 
        data = token_serializer_class(token).data

        response.set_cookie(
            key = 'access_token', 
            value = data['auth_token'],
            secure = True if settings.DEBUG is False else False,  # 生产环境应设置为 True
            httponly = True,
            samesite = 'Lax'
        )

        response.data = {"Success" : "Login successfully"} # 这里返回成功的消息,不返回auth_token
        return response

此代码段的主要功能是将 auth_token 设置为一个名为 access_tokenhttponly Cookie,同时设置 secure 属性(根据 DEBUG 模式调整),并使用 Lax 策略处理同源策略。设置了 httponly 后,浏览器端的js无法读取该cookie的值。为了兼容性以及增强安全,生产环境中,secure 应设为True ,只允许通过https传输该cookie。这里response不应该再包含auth_token相关数据。

  1. 编写中间件

之后,需要一个中间件来读取请求中的 access_token Cookie值,并将其加入请求头Authorization,供DRF后续认证使用。

import logging

logger = logging.getLogger(__name__)

def auth_token_middleware(get_response):
    def middleware(request):
       
        if 'HTTP_AUTHORIZATION' not in request.META:

            token = request.COOKIES.get('access_token')
            if token:

                request.META['HTTP_AUTHORIZATION'] = f'Token {token}'

        return get_response(request)

    return middleware

# 在settings.py的middleware列表中启用该中间件
MIDDLEWARE = [
    ...
    'your_app.middlewares.auth_token_middleware', # 将 your_app 改为你的实际app
    ...
]

此段代码创建一个中间件,它检查是否存在 HTTP_AUTHORIZATION 头。若不存在,则会检查请求中的 access_token Cookie,并将其添加为 Authorization 头,其格式为 Token xxx,DRF会自动识别该头部进行认证。这个中间件需要在MIDDLEWARE 中被正确注册才能工作。注意更改your_app为实际的APP名称。

代码执行步骤

  1. 在项目中创建一个中间件文件 your_app/middlewares.py(或放在合适的位置)。
  2. 将上面中间件代码粘贴到该文件中。
  3. 修改 settings.py 中的 MIDDLEWARE 列表,将你的中间件路径添加进去。
  4. 在对应的 urls.py 文件中注册使用自己实现的 TokenCreateView
  5. 访问你的登录接口,你会在浏览器请求中看到包含Token的 httponly Cookie。

此方案的适用性与分析

上述方案的主要优势是提高了token存储的安全性,通过HttpOnly防止前端js代码的直接读取。在多数场景下,这可以有效防范XSS攻击带来的token泄露风险。

但是此方法也有它的局限性。由于跨域资源共享(CORS)策略的限制,使用这种方式将token通过cookie传递时,需要正确配置跨域资源共享。而且客户端(比如APP)在处理这类httponly Cookie会面临一些技术难点,并非所有的客户端都可以有效处理HttpOnly类型的Cookie,尤其是一些小程序或者APP内嵌webview环境,开发者应该根据自身实际环境进行选型。

安全建议

  • 确保生产环境 settings.py 中的 DEBUG = False 且开启 HTTPS ,否则 Cookie 的 secure 属性会失效。
  • samesite 属性值应谨慎设置,需要考虑跨站点请求的场景,Lax 是一个相对安全的选项。

此方法主要作用是提高前端 token 安全,服务端仍应定期更新或使其过期。正确使用该技术结合适当的安全实践能够更好地保障应用程序的安全性。