为什么这个时候开发登录和注册功能呢?其一,要搞明白DRF的登录和注册,我们首先需要有一定的DRF基础。其二,之后的许多功能的实现,都以登录为前提。
首先要说的是,前后端分离系统下的登录开发与基于浏览器的登录开发(cookie和session)是不大一样的。
类似于下图这样的页面的登录(不涉及前后端分离,django自带的登录系统)是如何实现的呢?
是因为在urls.py中,我们配置了:
url(r'^api-auth/', include('rest_framework.urls')),
include方法将这个认证功能分发给了rest_framework的urls.py进行处理,点进去看,里面又有:
url(r'^login/$', views.LoginView.as_view(template_name='rest_framework/login.html'), name='login'),
url(r'^logout/$', views.LogoutView.as_view(), name='logout'),
再点进LoginView中,发现该类是django自带的,这是处理上图形式的登录(不涉及前后端分离的登录,django自带的登录模式)的后端逻辑所在的类,它里面有一句:
@method_decorator(csrf_protect)
也就是说,像上图中那样的登录(不涉及前后端分离的登录,django自带的登录模式)是会验证csrf_protect的。我们在上图的登录页面审查元素时,能够看到:
<input type="hidden" name="csrfmiddlewaretoken" value="vRaIDAPsUUTtuQBC0BYPpE47F04kIRxS8Xn1e8tgJBdZeUn0ApGU5mzMr5RlPbfv">
这是在做表单的安全验证,是为了防止跨站攻击。
这个LoginView我们不能直接拿来用。因为它要我们对csrf_code做验证,csrf_code实际上是在返回html页面的时候django自动注册的一个方法,我们在template中调用这个方法,就会生成上面的input标签。而在前后端分离的系统中,我们是不需要做csrf的验证的,这个时候已经不存在站内站外的问题了,本身就是跨站的。即在使用DRF开发的时候,是不需要关心csrf的问题的。
我们要舍弃这个方法,并找寻新的适用于前后端分离系统的验证方法。
看官方文档中API Guide中的Authentication(认证)部分,这是一个核心而又不复杂的部分,毕竟很多接口都需要用到用户认证。文档中关于Authentication的介绍的第一句很精辟:
Authentication is the mechanism of associating an incoming request with a set of identifying credentials.
身份验证是将传入请求与一组标识凭据关联的机制。(也就是在request到达我们的view业务逻辑前,将user放进request中)
下面的Setting the authentication scheme给我们列举了两个默认配置:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
REST_FRAMEWORK是整个DRF配置的变量。
既然是默认配置,那我们不作配置。
其中 SessionAuthentication 的原理非常简单,它实际上还是应用了django中的 SessionMiddleware ,加上django的 AuthenticationMiddleware (这两者均配置在了settings.py中的 MIDDLEWARE 变量中)。每当一个request进来的时候,这两个middleware会将request中cookie中的 session id 转换成 user,也就是 request.user 。也就完成了非前后端分离系统中的用户认证(将request与user相关联)。
大家可以自行去搜索一些有关django从请求到响应流程的博文。
我们可以看一下SessionAuthentication的源码。上面的配置已经表明了SessionAuthentication这个类所在的位置。它继承了BaseAuthentication,可以看到,BaseAuthentication只是定义了两个方法,并没有具体的逻辑:
class BaseAuthentication(object):
"""
All authentication classes should extend BaseAuthentication.
"""
def authenticate(self, request):
"""
Authenticate the request and return a two-tuple of (user, token).
"""
raise NotImplementedError(".authenticate() must be overridden.")
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
pass
再看SessionAuthentication中它所重载的authenticate方法:
def authenticate(self, request):
"""
Returns a `User` if the request session currently has a logged in user.
Otherwise returns `None`.
"""
# Get the session-based user from the underlying HttpRequest object
user = getattr(request._request, 'user', None)
# Unauthenticated, CSRF validation not required
if not user or not user.is_active:
return None
self.enforce_csrf(request)
# CSRF passed with authenticated user
return (user, None)
它取出了request中的user,而没有操作cookie。而它的认证机制又是通过浏览器的cookie和session来获取用户,所以,它最终依赖的还是Django的SessionMiddleware和AuthenticationMiddleware来完成对cookie、session的操作。
我们客户端的用户登录不使用这个,但因为后端项目中有的地方(文档、接口、后台)是使用这个机制来登陆的,所以我们先配置在这个地方(实际上是默认配置,也不用配置)。
再看官方文档中,DRF实际上是给我们提供了BasicAuthentication,TokenAuthentication,SessionAuthentication,RemoteUserAuthentication四种Authentication的。前面已经说了,SessionAuthentication这种机制在浏览器中比较常见,因为浏览器会自动设置cookie,并将session和cookie带到服务器。但在前后端分离的系统中,这种机制是非常少见的。
我们重点关注TokenAuthentication。
首先是将:
'rest_framework.authtoken',
加入到INSTALL_APPS中。
为什么像SessionAuthentication之类的不用加进去呢?这是因为用TokenAuthentication做验证是会为我们建立一张数据表的,凡是有表的APP我们一定要加入到INSTALLED_APPS中。否则在执行makemigrations和migrate时,它是不会给我们生成数据表的。
加到INSTALLED_APPS中后,我们打开manage.py工具,执行:
makemigrations
migratate
这样之后,我们使用Navicat查看我们的数据库,发现多了一张表——authtoken-token,这张表是Token机制的基础。这张表有三个字段——key(Token值)、created(创建时间)、uesr_id(外键,指向user_userprofile这张表的id)。
接着,文档中又说:You'll also need to create tokens for your users.(token是需要我们自己来生成的)
我们看到我们的authtoken-token表,它是空的。但我们之前是已经创建过一个超级用户了的,原本应该是有一个user就有一个token。即,token应该在我们的用户创立之初的时候就建立,token和user是一一对应的。
而,我们之前、包括现在,都没有引入token,所以我们之前创建超级用户的时候,并没有为我们创建token。我们现在可以再创建一个超级用户,会发现,它仍然不会为我们在authtoken-token表中创建数据。执行:
createsuperuser
然后按步骤创建超级用户。再看看authtoken-token表,会发现仍然没有数据。
所以,我们需要引入token,并自己创建token:
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=...)
print(token.key)
文档还告诉了我们如何来使用token,需要将:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
放到http请求中的header当中。
实际上,我们不用自己写获取(包括create和get)token的业务逻辑。我们可以配置一个url,用来获取token。我们需要向这个url来post我们的username和password,然后它会返回token。
在根urls.py中,引入:
from rest_framework.authtoken import views
在urlpatterns中新增:
url(r'^api-token-auth/', views.obtain_auth_token)
可以一步一步跟进去看看逻辑,发现这个view(ObtainAuthToken)的post方法为:
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})
即它帮我们完成了token的创建(create),并且若已经存在token,则执行获取(get)操作。
我们希望测试这个url,此时可以使用Chrome浏览器的一个插件——postman。