0%

Django REST framework的token原理和验证登录

drf的登陆验证功能

drf自带的登陆功能(drf Api Root中使用),通过定义路径访问 rest_framework.urls 中设置的路由进行访问

1
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),

从子路径的实现

1
2
url(r'^login/$', views.LoginView.as_view(template_name='rest_framework/login.html'), name='login'),
url(r'^logout/$', views.LogoutView.as_view(), name='logout'),

可以发现,内部实现实际上是调用了Django的原生类 LoginViewLogoutViewLoginView 中添加了 @method_decorator(csrf_protect) 装饰器,所以在drf的API Root页面使用了csrf_token 标签,该标签会在渲染模版的时候替换为 <input type="hidden" name="csrfmiddlewaretoken" value="服务器随机生成的token值"> ,配合 django.middleware.csrf.CsrfViewMiddleware 中间件防止csrf(跨站请求伪造),但是目前是一个前后端分离的系统,就不需要做csrf的验证(因为前后端分离的系统中,已经产生了跨站的问题,app和网站肯定不是一个站点,已经是跨站了),所以在做drf开发的时候,不需要关系csrf验证的问题。
综上,直接调用 LoginView 进行登录认证是不适合在前后端分离的系统中使用的。

前后端分离的验证方式

drf中的Authentication提供了一种 request 和 用户身份 绑定的机制。
使用方式:

  1. 设置setting,设置认证方式,作用方式和Django中的中间件类似。
1
2
3
4
5
6
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [ # 设置权限验证的类,默认为以下两个类
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}

rest_framework.authentication.SessionAuthentication 内部的实现很简单,只是从request中取出了user,实际还是依赖django的django.contrib.sessions.middleware.SessionMiddleware和django.contrib.auth.middleware.AuthenticationMiddleware这两个中间件。

  1. 添加drf自带的Token认证类 rest_framework.authentication.TokenAuthentication,使用该类时,需要在 INSTALLED_APPS 中进行注册'rest_framework.authtoken',因为需要使用数据表,生成authtoken_token表(所以在INSTALLED_APPS注册完之后,还需要使用 migrate 命令生成数据表),该表还用三个字段(key、created、user_id),通过 user_id 自动和用户表进行关联,保证一个用户对应着一个token值。
    在创建用户的时候,不会自动生成token的key值,需要手动调用方法进行生成,手动生成的方式有几种:

    • 调用提供的 Token 类生成

      1
      2
      3
      4
      from rest_framework.authtoken.models import Token

      token = Token.objects.create(user=...)
      print(token.key)
    • 使用官方提供的url,url(r'^api-token-auth/', views.obtain_auth_token),发送post请求,会返回相应的token,若没有则生成后返回,并自动和相应的用户进行关联(需要注意的该url请求是post请求,需要传递用户名和密码进行验证)。

  2. 认证
    需要手动在header中传递 Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b 。通过DEFAULT_AUTHENTICATION_CLASSES 的作用之后,就会获取到用户,然后将用户存储在request中的user属性中,token值存储在auth属性中。

token的实际应用

问题:有这样的使用场景,若访问一个公共数据页面,一般的,这种公共页面即使是在用户未登录(没有传递token),或者用户传递的token产生错误的情况下也是可以访问的,该如何解决呢?

可以通过前端,也可以在后端解决。

后端的方式:
其实可以不需要采用在 settings.py 中设置全局的 DEFAULT_AUTHENTICATION_CLASSES 的方式,而是在需要进行token验证的view中使用 authentication_classes 属性指定是否需要使用token验证(该属性在基础类APIView中设置了,authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES)。

drf的配置,多数都可以选择是在settings.py中配置还是在view中进行配置。settings.py中的是全局的配置,view中的设置是局部的,同时设置时,view中的设置优先于全局的设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
from rest_framework.authentication import TokenAuthentication

class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
"""
商品列表页, 分页, 搜索, 过滤, 排序
"""
...
# 注意是classes,可接受多个值,所以采用元组的数据格式,另外,当元组只有一个数据的时候,需要使用逗号
# view中的局部设置会覆盖全局的设置
authentication_classes = (TokenAuthentication, )
permission_classes = (IsAuthenticated,) # 只有设置了IsAuthenticated,才会进行身份验证
...

问题

  1. 目前,使用的token验证方式,token值是存储在生成的 authtoken_token 表中,是放在一台服务器上的,若采用分布式的话,还需要做数据的同步操作。
  2. 相比于 django_session 的设置,没有expire_date属性(过期时间),将是永久有效的,这样的话,一旦泄露将造成很大的安全问题。
  3. 和 django_session 存储同样的问题,随着用户的增多,token值会占用服务器大量空间,同时也会加大数据库的查询压力,导致性能下降。

解决措施:使用JWT用户认证