Django Rest Framework 电商项目 7-1 DRF的token登录和原理-1

为什么这个时候开发登录和注册功能呢?其一,要搞明白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实际上是给我们提供了BasicAuthenticationTokenAuthenticationSessionAuthenticationRemoteUserAuthentication四种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

安装方法:http://www.cnblogs.com/mafly/p/postman.html

发布了101 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/liujh_990807/article/details/86587788