前言
最近在研究学习django rest framework中,对于运用过的一些技术点进行总结,因此设置相关技术专题。
基础
在官网中对Authentication解释如下:
Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The permission and throttling policies can then use those credentials to determine if the request should be permitted.
大概意思就是说Authentication是一种把request信息和用户身份信息绑定起来的机制,也就用来验证登录用户。
对于本篇内容主要讲解的TokenAuthentication实际上是Authentication验证方式的一种,主要是用于数据库中通过用户登录时创建一个Token存储在数据库中,每次都会从http header中取出来于用户客户端的token进行比对。TokenAuthentication主要是运用于移动端和服务端进行交互验证。
用法
首先先把下面的代码放进settings.py中,用于调用各个类中的验证方法,把user放进request中。request请求的请求,Token信息是放在auth字段中
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
....
'rest_framework.authentication.TokenAuthentication',
)
}
然后,在setting中加入authtoken用于创建Token表存储用户登陆时的token
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
接着在urls.py中增加:
from rest_framework.authtoken import views
urlpatterns = [
path('api-token-auth/', views.obtain_auth_token),
]
原理
第一步:
在settings.py添加AUTHENTICATION类TokenAuthentication
,在执行请求的时候,会调用authenticate
方法。调用之前,先通过get_authorization_header
把Token取出来,注意,在用户做登录请求的时候,注意Token是放在http header中,参数格式:Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b,接着split 拿到Token信息
源码:
class TokenAuthentication(BaseAuthentication):
"""
Simple token based authentication.
Clients should authenticate by passing the token key in the "Authorization"
HTTP header, prepended with the string "Token ". For example:
Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
"""
keyword = 'Token'
model = None
"""
A custom token model may be used, but must have the following properties.
* key -- The string identifying the token
* user -- The user to which the token belongs
"""
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != self.keyword.lower().encode():
return None
if len(auth) == 1:
msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Token string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
token = auth[1].decode()
except UnicodeError:
msg = _('Invalid token header. Token string should not contain invalid characters.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(token)
第二步:
拿到用户request中的Token后,再调用authenticate_credentials
方法进行验证:使用get_model方法取到数据库存储的model,在model中进行查询是否包含Token对应的信息进行判断。
源码:
def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.select_related('user').get(key=key)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed(_('Invalid token.'))
if not token.user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (token.user, token)
def authenticate_header(self, request):
return self.keyword
def get_model(self):
if self.model is not None:
return self.model
from rest_framework.authtoken.models import Token
return Token
第三步
如果用户Token表中没有信息,用户在请求的过程中,会调用试图方法中的get_or_create
进行创建表,并写入用户Token信息。
urlpatterns = [
path('api-token-auth/', views.obtain_auth_token),
]
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
if coreapi is not None and coreschema is not None:
schema = ManualSchema(
fields=[
coreapi.Field(
name="username",
required=True,
location='form',
schema=coreschema.String(
title="Username",
description="Valid username for authentication",
),
),
coreapi.Field(
name="password",
required=True,
location='form',
schema=coreschema.String(
title="Password",
description="Valid password for authentication",
),
),
],
encoding="application/json",
)
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key})
总结
缺点:Django rest framework的Token验证,在数据库中的Token表存储的Token信息缺少一个过期时间来对Token进行管理,导致别人窃取或仿造Token进行攻击,所以这种验证方式并不安全。