前言
目前Web项目多为前后端分离架构。在这种架构下使用token认证优于传统的session/cookie认证。具体表现在:
- 后端无状态,不需要再存储session信息。多个后端(例如微服务/集群状态部署)也不需要去共享session信息,后端架构非常简单。
- 可以规避cookie劫持和CSRF攻击。
- 前后端解耦,前端只需向后端发送的请求头中携带token信息,后端即知晓来意,不再需要管理cookie。
本篇为大家带来Django Rest Framework配置token认证的方式。
Django signing的使用
在网络上传播的数据我们必须有一套自校验的机制来确认数据是否被篡改。Django提供的signing模块正好适合这个用途。可以将用户信息通过signing模块签名加密之后作为token使用。外部系统可以拿到这个token,但无法篡改(篡改后无法通过signing模块的校验),因此可以信任这个token。再配合Django的cache模块,可以做到token的定时过期。
通过signing生成签名信息的方法为signing.dumps(...),传入一个dict类型的参数。如下所示:
from django.core import signing
token = signing.dumps({'username': user.username, 'id': str(user.id), 'email': user.email})
解析token的使用,调用signing.loads()方法。
user = signing.loads(token)
如果token解析正常(没有被篡改,当然还有可能一个原因,后面说),方法返回前面signing.dumps传入的那个dict。如果解析失败,会抛出一个exception。
需要格外注意的是,Django的settings.py中有一个配置项SECRET_KEY,该变量是用来签名校验数据的密钥。Signing模块dumps出一个token之后,必须使用相同的SECRET_KEY才能loads这个token,否则会解析失败。换个角度说,如果``SECRET_KEY`泄露,其他Django项目就能够生成一个在我们项目中完全合法的token,这样signing模块就不再安全了。
在用户登录生成token之后,将该token存放在token_cache中,约定一个过期时间,可以确保token只有在该期间内有效。代码如下所示:
from django.core import cache
token_cache = cache.caches['default']
token_cache.set(token, user, timeout=CONFIG.get_session_timeout())
Django Signing模块参考链接:Cryptographic signing | Django documentation | Django
views使用token认证
Django Rest Framework的views方法使用authentication_classes指定认证类。例如:
class SomeView(APIView):
authentication_classes = [TokenAuth]
这个TokenAuth类需要继承TokenAuthentication,实现authenticate方法。一个示例实现如下:
from rest_framework.authentication import TokenAuthentication
from django.core import signing, cache
from chat_app.utils.logger import get_logger
logger = get_logger(__name__)
token_cache = cache.caches['default']
class TokenAuth(TokenAuthentication):
def authenticate(self, request):
# 获取请求头中Authorization携带的token
token = request.META.get('HTTP_AUTHORIZATION')
logger.info(f"auth: {token}")
# 从本地token cache获取token。该cache在用户登录的时候set
cache_token = token_cache.get(token)
if cache_token is None:
# token不存在,身份验证失败,需要处理异常,具体代码省略
logger.info(f"Authentication failed")
try:
# 从cache中获取到了token,接着需要校验token
# 解码token,检查token是否被篡改
token_details = signing.loads(cache_token)
logger.info(f"token_details: {token_details}")
except Exception as e:
logger.info(f"signing.loads error: {e}")
# token续期
token_cache.touch(token, timeout=CONFIG.get_session_timeout())
# 返回元组的第一个对象为User详情,第二个对象用于request.auth携带信息
return token_details, None
上面方法的逻辑整体为:
- 从
token_cache中读取token,如果能够读取到,说明当前用户的登录状态没有过期。 - 校验token合法性。顺便读取当前登录的用户信息(token中携带的有)。
- Cache续期。该步骤可选,用户有活跃操作可以延长token过期时间,但是会降低安全性。
- 认证成功的话,方法返回用户信息和携带给
request.auth的信息。
request.auth可用于携带一些和认证相关的其他信息,上面例子中没有用到,可以返回None。实际使用时我们可以携带权限相关的信息。关于如何使用request.auth,假如前面的authenticate方法return的不是token_details, None,而是token_details, {"demo": "Hello world"}。我们在views中可以通过:
request.auth.get('demo')
这种方式获取到值Hello World。
以上是Django Rest Framework配置token认证的方式。