Django REST framework API认证(包括JWT认证)

 

Django REST framework API认证(包含JWT认证) + 权限

Django REST framework API认证(包含JWT认证)

一. 背景

在咱们学习Django Rest Framework(简称DRF)时,其很是友好地给咱们提供了一个可浏览API的界面。不少测试工做均可以在可浏览API界面完成测试。要使用可浏览API界面很简单,只须要在urls.py文件中添加以下部分便可。html

1
2
3
4
from django.conf.urls import include
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

 

其中,r'^api-auth/'部分实际上能够用任何你想使用URL替代,惟一的限制是所包含的URL必须使用'rest_framework'命名空间。在Django 1.9+中,REST framework将自动设置,因此你也无须关心。
配置完成后,若是再次打开浏览器API界面并刷新页面,你将在页面右上角看到一个”Log in”连接。这就是DRF提供的登陆和登出入口,能够用来完成认证。
而后进入到’rest_framework.urls’源码,是能够看到提供了’login’和’logout’两个接口,分别用来登入和登陆的。代码以下:前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if django.VERSION < (1, 11):
login = views.login
login_kwargs = {'template_name': 'rest_framework/login.html'}
logout = views.logout
else:
login = views.LoginView.as_view(template_name='rest_framework/login.html')
login_kwargs = {}
logout = views.LogoutView.as_view()


app_name = 'rest_framework'
urlpatterns = [
url(r'^login/$', login, login_kwargs, name='login'),
url(r'^logout/$', logout, name='logout'),
]

 

其中login接口调用LoginView视图,logout接口调用LogoutView视图。这两个视图都是django.contrib.auth应用提供的。在LogoutView视图中,有这么一个装饰器@method_decorator(csrf_protect),是用来作CSRF code验证的,就是作表单安全验证的,防止跨站攻击。而这个CSRF code是在返回HTML页面的时候Django会自动注册这么一个CSRF code方法,而在template中会自动调用这个方法生成code值。在前端页面元素form部分,能够查看到name=”csrfmiddlewaretoken”标识,且在Django返回的 HTTP 响应的 cookie 里,Django 会为你添加一个csrftoken 字段,其值为一个自动生成的token。这就是用来作表单安全验证的,具体关于CSRF原理见Django章节。python

这里要说明一个问题就是这个LoginView咱们是没法直接拿来用的,由于它须要作CSRF验证,而在先后端分离系统中不须要作CSRF验证,这里不存在站内站外的问题,自己就是跨站访问的。那么在咱们先后端分离项目中,如何作API接口的验证呢?其实framework也已经提供了多种验证方式。mysql

二. 身份验证

REST framework提供了许多开箱即用的身份验证方案,同时也容许你实施自定义方案。这里须要明确一下用户认证(Authentication)和用户受权(Authorization)是两个不一样的概念,认证解决的是“有没有”的问题,而受权解决的是“能不能”的问题。git

BasicAuthentication
该认证方案使用 HTTP Basic Authentication,并根据用户的用户名和密码进行签名。Basic Authentication 一般只适用于测试。github

SessionAuthentication
此认证方案使用 Django 的默认 session 后端进行认证。Session 身份验证适用于与您的网站在同一会话环境中运行的 AJAX 客户端。算法

TokenAuthentication
此认证方案使用简单的基于令牌的 HTTP 认证方案。令牌身份验证适用于 client-server 架构,例如本机桌面和移动客户端。sql

RemoteUserAuthentication
这种身份验证方案容许您将身份验证委托给您的 Web 服务器,该服务器设置 REMOTE_USER 环境变量。数据库

默认的认证方案可使用DEFAULT_AUTHENTICATION_CLASSES全局设置,在settings.py文件配置。在默认状况下,DRF开启了 BasicAuthentication 与 SessionAuthentication 的认证。django

1
2
3
4
5
6
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}

 

关于DRF,几乎全部的配置都定义在MREST_FRAMEWORK变量中。另外,关于认证方式DRF默认会检测配置在DEFAULT_AUTHENTICATION_CLASSES变量中的全部认证方式,只要有一个认证方式经过便可登陆成功。这里的DEFAULT_AUTHENTICATION_CLASSES与Django中的MIDDLEWARE相似,在将request经过url映射到views以前,Django和DRF都会调用定义在MREST_FRAMEWORK变量中的类的一些方法。
另外,你还可使用基于APIView类的视图,在每一个视图或每一个视图集的基础上设置身份验证方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)

def get(self, request, format=None):
content = {
'user': unicode(request.user), # `django.contrib.auth.User` instance.
'auth': unicode(request.auth), # None
}
return Response(content)

 

须要明白的一点是,DRF的认证是在定义有权限类(permission_classes)的视图下才有做用,且权限类(permission_classes)必需要求认证用户才能访问此视图。若是没有定义权限类(permission_classes),那么也就意味着容许匿名用户的访问,天然牵涉不到认证相关的限制了。因此,通常在项目中的使用方式是在全局配置DEFAULT_AUTHENTICATION_CLASSES认证,而后会定义多个base views,根据不一样的访问需求来继承不一样的base views便可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from rest_framework.permissions import (
IsAuthenticated,
IsAdminUser,
IsAuthenticatedOrReadOnly
)

class BaseView(APIView):
'''普通用户'''
permission_classes = (
IsOwnerOrReadOnly,
IsAuthenticated
)


class SuperUserpermissions(APIView):
'''超级用户'''
permission_classes = (IsAdminUser,)


class NotLogin(APIView):
'''匿名用户'''
pass

 

另外,在先后端分离项目中通常不会使用 BasicAuthentication 与 SessionAuthentication 的认证方式。因此,咱们只须要关心 TokenAuthentication 认证方式便可。

三.TokenAuthentication

要使用TokenAuthentication方案,你须要将认证类配置为包含TokenAuthentication

1
2
3
4
5
6
7
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
)
}

并在INSTALLED_APPS设置中另外包含 rest_framework.authtoken:

1
2
3
4
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)

 

注意: rest_framework.authtoken应用必定要放到INSTALLED_APPS,而且确保在更改设置后运行python manage.py migrate。 rest_framework.authtoken应用须要建立一张表用来存储用户与Token的对应关系。
数据库迁移完成后,能够看到多了一个authtoken_token表,表结构以下:

1
2
3
4
5
6
7
8
9
10
11
12
mysql> show create table authtoken_token\G
*************************** 1. row ***************************
Table: authtoken_token
Create Table: CREATE TABLE `authtoken_token` (
`key` varchar(40) NOT NULL,
`created` datetime(6) NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`key`),
UNIQUE KEY `user_id` (`user_id`),
CONSTRAINT `authtoken_token_user_id_35299eff_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)

 

其中“user_id”字段关联到了用户表。

  • 配置URLconf
    使用TokenAuthentication时,你可能但愿为客户提供一种机制,以获取给定用户名和密码的令牌。 REST framework 提供了一个内置的视图来支持这种行为。要使用它,请将obtain_auth_token视图添加到您的 URLconf 中:
    1
    2
    3
    4
    from rest_framework.authtoken import views
    urlpatterns += [
    url(r'^api-token-auth/', views.obtain_auth_token)
    ]

其中,r'^api-token-auth/'部分实际上能够用任何你想使用URL替代。

  • 建立Token
    你还须要为用户建立令牌,用户令牌与用户是一一对应的。若是你已经建立了一些用户,则能够为全部现有用户生成令牌,例如
    1
    2
    3
    4
    5
    from django.contrib.auth.models import User
    from rest_framework.authtoken.models import Token

    for user in User.objects.all():
    Token.objects.get_or_create(user=user)

你也能够为某个已经存在的用户建立Token:

1
2
for user in User.objects.filter(username='admin'):
Token.objects.get_or_create(user=user)

 

建立成功后,会在Token表中生成对应的Token信息。

若是你但愿每一个用户都拥有一个自动生成的令牌,则只需捕捉用户的post_save信号便可。

1
2
3
4
5
6
7
8
9
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)

 

请注意,你须要确保将此代码片断放置在已安装的models.py模块或 Django 启动时将导入的其余某个位置。

  • 获取Token
    上面虽然介绍了多种建立Token的方式,其实咱们最简单的就是只须要配置一下urls.py,而后就能够经过暴露的API来获取Token了。当使用表单数据或 JSON 将有效的username和password字段发布到视图时,obtain_auth_token视图将返回 JSON 响应:
    1
    2
    $ curl -d "username=admin&password=admin123456" http://127.0.0.1:8000/api-token-auth/
    {"token":"684b41712e8e38549504776613bd5612ba997616"}

请注意,缺省的obtain_auth_token视图显式使用 JSON 请求和响应,而不是使用你设置的默认的渲染器和解析器类。

当咱们正常获取到Token后,obtain_auth_token视图会自动帮咱们在Token表中建立对应的Token。源码以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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})


obtain_auth_token = ObtainAuthToken.as_view()

 

默认状况下,没有权限或限制应用于obtain_auth_token视图。 若是您但愿应用throttling,则须要重写视图类,并使用throttle_classes属性包含它们。

若是你须要自定义obtain_auth_token视图,你能够经过继承ObtainAuthToken视图类来实现,并在你的urls.py中使用它。例如,你可能会返回超出token值的其余用户信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response

class CustomAuthToken(ObtainAuthToken):

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,
'user_id': user.pk,
'email': user.email
})

 

还有urls.py:

1
2
3
urlpatterns += [
url(r'^api-token-auth/', CustomAuthToken.as_view())
]

 

  • 认证Token
    当咱们获取到Token后,就能够拿着这个Token来认证其余API了。对于客户端进行身份验证,令牌密钥应包含在 Authorization HTTP header 中。关键字应以字符串文字 “Token” 为前缀,用空格分隔两个字符串。例如:
    1
    Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

注意: 若是你想在 header 中使用不一样的关键字(例如Bearer),只需子类化TokenAuthentication并设置keyword类变量。
若是成功经过身份验证,TokenAuthentication将提供如下凭据。

request.user是一个User实例,包含了用户名及相关信息。
request.auth是一个rest_framework.authtoken.models.Token实例。
未经身份验证的响应被拒绝将致使HTTP 401 Unauthorized的响应和相应的 WWW-Authenticate header。例如:

1
WWW-Authenticate: Token

 

测试令牌认证的API,例如:

1
$ curl -X GET -H 'Authorization: Token 684b41712e8e38549504776613bd5612ba997616' http://127.0.0.1:8000/virtual/

 

注意: 若是您在生产中使用TokenAuthentication,则必须确保您的 API 只能经过https访问。

四. 认证源码

使用 TokenAuthentication 认证方式,当认证成功后,在 request 中将提供了 request.user 和 request.auth 实例。其中 request.user 实例中有用户信息,好比用户名及用户ID,而 request.auth 实例中有Token信息。那么DRF是如何把 Token 转换为用户信息呢?经过下面的源码部分就能够看到它们是如何转换的。

基于 DRF 的请求处理,与常规的 url 配置不一样,一般一个 Django 的 url 请求对应一个视图函数,在使用 DRF 时,咱们要基于视图对象,而后调用视图对象的 as_view 函数,as_view 函数中会调用 rest_framework/views.py 中的 dispatch 函数,这个函数会根据 request 请求方法,去调用咱们在 view 对象中定义的对应的方法,就像这样:

1
2
3
4
from rest_framework.authtoken import views
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token)
]

 

这里虽然直接调用 views.obtain_auth_token 方法,但进入到 views.obtain_auth_token 方法后仍是 DRF 模式,源码以下:

1
obtain_auth_token = ObtainAuthToken.as_view()

 

ObtainAuthToken 方法是继承 DRF 中的 APIView 的 View 类:

1
2
3
4
5
6
7
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
....

 

若是你是用 POST 方法请求 ObtainAuthToken,那么 as_view() 函数会调用 dispatch 函数,dispatch 根据 request.METHOD,这里是 POST,去调用 ObtainAuthToken 类的 POST 方法,这就跟一般的 url->view 的流程同样了。

这里须要注意的一点就是,DRF 中的 APIVIEW 是继承 Django View 的,重写了部分 as_view 方法,而调用 dispatch 函数是在 Django View 的 as_view 方法中作的事情,源码部分以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class APIView(View):
....
@classmethod
def as_view(cls, **initkwargs):
"""
Store the original class on the view function.

This allows us to discover information about the view when we do URL
reverse lookups. Used for breadcrumb generation.
"""
if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
def force_evaluation():
raise RuntimeError(
'Do not evaluate the `.queryset` attribute directly, '
'as the result will be cached and reused between requests. '
'Use `.all()` or call `.get_queryset()` instead.'
)
cls.queryset._fetch_all = force_evaluation

view = super(APIView, cls).as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs

 

可是用户认证是在执行请求 View 以前作的,因此其实就是在 dispatch 函数之中作的,具体见源码 rest-framework/views.py 中 APIView 类中的 dispatch 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class APIView(View):
// 从settings文件中获取认证类、限流类、权限类
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
....

def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs

// 封装request,对原始request对象增长一些功能,好比认证类,都是在initialize_request方法中完成
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?

try:
// 调用self.initial进行用户认证
self.initial(request, *args, **kwargs)

# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed

response = handler(request, *args, **kwargs)

except Exception as exc:
response = self.handle_exception(exc)

self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response

 

这里的 self.initialize_request 也能够关注一下,由于这里的 request 对象,后面也会有调用的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class APIView(View):
....
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)

return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(), // 这里把认证类封装进行了request里面
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)

 

其中 self.get_authenticators() 方法就是用来取 self.authentication_classes 变量。

1
2
3
4
5
6
7
class APIView(View):
....
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
return [auth() for auth in self.authentication_classes]

 

关于 authentication_classes 变量,上面已经给出了,就在 APIView 里面 authentication_classes 字段。

而后就到了认证,重点在于 self.initial(request, *args, **kwargs) 函数,对于这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class APIView(View):
....
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)

# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg

# Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme

# Ensure that the incoming request is permitted
self.perform_authentication(request) // 用户认证
self.check_permissions(request) // 权限检查
self.check_throttles(request) // 限流检查

 

这里关注 self.perform_authentication(request) 验证某个用户,其实能够看到权限检查及限流也是在这里作的。

1
2
3
4
5
6
7
8
9
10
11
class APIView(View):
....
def perform_authentication(self, request):
"""
Perform authentication on the incoming request.

Note that if you override this and simply 'pass', then authentication
will instead be performed lazily, the first time either
`request.user` or `request.auth` is accessed.
"""
request.user

 

这里 request.user 实际上是一个 @property 的函数,加 @property 表示调用 user 方法的时候不须要加括号“user()”,能够直接调用 request.user 。而这里的 request 对象就是上面 initialize_request 方法返回的,其中还返回了 DRF 定义的 request 对象,在 request 对象中有被 @property 装饰的 user 方法。

1
2
3
4
5
6
7
8
9
10
11
12
class Request(object):
....
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user

 

重点来了,到了真正认证的方法了,关注 self._authenticate()函数便可。此方法会循环尝试每一个 DRF 认证方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Request(object):
....
def _authenticate(self):
"""
尝试使用每一个身份验证明例验证请求
self.authenticators = [BasicAuthentication, SessionAuthentication, TokenAuthentication]
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
// 若是authenticate方法抛出异常,则执行self._not_authenticated方法,至关于匿名用户,没有经过认证
self._not_authenticated()
raise

if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return

// 若是没有设置认证类的话,也至关于匿名用户,没有经过认证
self._not_authenticated()

 

那么 self.authenticators 从哪儿来的呢?就是上面展现的,在 APIVIEW 类中的 authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES 获得的。咱们上面在介绍 DRF 身份验证时也说了,能够把认证类定义在全局 settings 文件中,你还可使用基于 APIView 类的视图,在每一个视图或每一个视图集的基础上设置身份验证方案。以下方式:

1
2
3
class ExampleView(APIView):
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)

 

当基于 APIView 类的视图定义验证或权限类时,至关于覆盖了原生 APIVIEW 中的相关变量,天然就使用覆盖后的变量了。authentication_classes 里面放的就是能够用来验证一个用户的类,他是一个元组,验证用户时,按照这个元组顺序,直到验证经过或者遍历整个元组尚未经过。同理 self.check_permissions(request) 是验证该用户是否具备API的使用权限。关于对view控制的其余类都在rest-framework/views.py的APIView类中定义了。

因为咱们这里只是拿 TokenAuthentication 认证说明,因此忽略 BasicAuthentication 和 SessionAuthentication 这两种认证,其原理与TokenAuthentication 同样。这样,就进入到了 TokenAuthentication 认证,其源码部分以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// 获取header部分 Authorization 标识的信息
def get_authorization_header(request):
"""
Return request's 'Authorization:' header, as a bytestring.

Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(auth, text_type):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth


// 解析并认证 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

def get_model(self):
if self.model is not None:
return self.model
from rest_framework.authtoken.models import Token
return Token

"""
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):
// 经过上面的get_authorization_header方法获得Token信息
// auth = [b'Token', b'684b41712e8e38549504776613bd5612ba997616']
auth = get_authorization_header(request).split()

// 经过获取Token关键字,并与keyword变量比对,来判断是不是Token方式认证
if not auth or auth[0].lower() != self.keyword.lower().encode():
return None

// auth长度等于2时才是合法值,继续往下进行
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)

// 进行token解码,从bytes编码格式转为字符串
try:
token = auth[1].decode()
except UnicodeError:
msg = _('Invalid token header. Token string should not contain invalid characters.')
raise exceptions.AuthenticationFailed(msg)

// 把转换过的token传给认证凭证方法进行验证
return self.authenticate_credentials(token)

// 验证凭证方法进行接收token并进行验证
def authenticate_credentials(self, key):
// 获取Token模型实例
model = self.get_model()
try:
// 使用select_related方法获取相应外键对应的对象(就是两表Join),而后进行Token过滤查询
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.'))

// 返回user实例,及token实例
return (token.user, token)

 

PS:DRF自带的TokenAuthentication认证方式也很是简单,同时弊端也很大,真正项目中用的较少。因为须要存储在数据库表中,它在分布式系统中用起来较为麻烦,而且每次都须要查询数据库,增长数据库压力;同时它不支持Token的过时设置,这是一个很大的问题。在实际先后端分离项目中使用JWT(Json Web Token)标准的认证方式较多,每一个语言都有各自实现JWT的方式,Python也不例外。

五. JWT认证

了解完DRF自带的TokenAuthentication认证方式的弊端以后,再来看JWT(Json Web Token)认证方式。它们两个的原理是同样的,就是认证用户Token,而后取出对应的用户。但JWT解决了两个较大的问题。

第一,是不须要把Token存储到数据库表中了,而是根据必定的算法来算出用户Token,而后每次用户来验证时再以一样的方式生成对应的Token进行校验。固然,实际JWT生成Token的方式仍是较为复杂的,具体能够看JWT协议相关文章。

第二,JWT对于生成的Token能够设置过时时间,从而在必定程度提升了Token的安全性。

JWT的原理仍是稍稍有点麻烦的,里面涉及了一些对称加密和非对称加密的算法。可是JWT使用起来确是很是简单,Python中有PyJWT库,而在DRF中也有对应的开源项目django-rest-framework-jwt

  • 安装
    直接使用pip安装便可,目前支持Python、Django、DRF主流版本:

    1
    $ pip install djangorestframework-jwt
  • 使用
    在settings.py文件中,将JSONWebTokenAuthentication 添加到REST framework框架的DEFAULT_AUTHENTICATION_CLASSES

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
    'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    'rest_framework.authentication.SessionAuthentication',
    'rest_framework.authentication.BasicAuthentication',
    ),
    }

一样,你还可使用基于APIView类的视图,在每一个视图或每一个视图集的基础上设置身份验证方案。与上面演示的 Token 认证同样,这里就不贴代码了,尽量使用基于APIView类的视图认证方式。
但使用基于APIView类的视图认证方式时,不要忘记导入类。

1
from rest_framework_jwt.authentication import JSONWebTokenAuthentication

 

在你的urls.py文件中添加如下URL路由,以便经过POST包含用户名和密码的令牌获取。

1
2
3
4
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns += [
url(r'^api-token-auth/', obtain_jwt_token)
]

 

若是你使用用户名admin和密码admin123456建立了用户,则能够经过在终端中执行如下操做来测试JWT是否正常工做。

1
$ curl -X POST -d "username=admin&password=admin123456" http://127.0.0.1:8000/api-token-auth/

 

或者,你可使用Django REST framework支持的全部内容类型来获取身份验证令牌。例如:

1
$ curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"admin123456"}' http://127.0.0.1:8000/api-token-auth/

 

如今访问须要认证的API时,就必需要包含Authorization: JWT <your_token>头信息了:

1
$ curl -H "Authorization: JWT <your_token>" http://127.0.0.1:8000/virtual/

 

  • 刷新Token
    若是JWT_ALLOW_REFRESH为True,能够“刷新”未过时的令牌以得到具备更新到期时间的全新令牌。像以下这样添加一个URL模式:
    1
    2
    3
    4
    from rest_framework_jwt.views import refresh_jwt_token
    urlpatterns += [
    url(r'^api-token-refresh/', refresh_jwt_token)
    ]

使用方式就是将现有令牌传递到刷新API,以下所示: {"token": EXISTING_TOKEN}。请注意,只有非过时的令牌才有效。另外,响应JSON看起来与正常获取令牌端点{"token": NEW_TOKEN}相同。

1
$ curl -X POST -H "Content-Type: application/json" -d '{"token":"<EXISTING_TOKEN>"}' http://localhost:8000/api-token-refresh/

 

能够重复使用令牌刷新(token1 -> token2 -> token3),但此令牌链存储原始令牌(使用用户名/密码凭据获取)的时间。做为orig_iat,你只能将刷新令牌保留至JWT_REFRESH_EXPIRATION_DELTA。
刷新token以得到新的token的做用在于,持续保持活跃用户登陆状态。好比经过用户密码得到的token有效时间为1小时,那么也就意味着1小时后此token失效,用户必须得从新登陆,这对于活跃用户来讲实际上是多余的。若是这个用户在这1小时内都在浏览网站,咱们不该该让用户从新登陆,就是在token没有失效以前调用刷新接口为用户得到新的token。

  • 认证Token
    在一些微服务架构中,身份验证由单个服务处理。此服务负责其余服务委派确认用户已登陆此身份验证服务的责任。这一般意味着其余服务将从用户接收JWT传递给身份验证服务,并在将受保护资源返回给用户以前等待JWT有效的确认。添加如下URL模式:
    1
    2
    3
    4
    from rest_framework_jwt.views import verify_jwt_token
    urlpatterns += [
    url(r'^api-token-verify/', verify_jwt_token)
    ]

将Token传递给验证API,若是令牌有效,则返回令牌,返回状态码为200。不然,它将返回400 Bad Request以及识别令牌无效的错误。

1
$ curl -X POST -H "Content-Type: application/json" -d '{"token":"<EXISTING_TOKEN>"}' http://localhost:8000/api-token-verify/

 

  • 手动建立Token
    有时候你可能但愿手动生成令牌,例如在建立账户后当即将令牌返回给用户。或者,你须要返回的信息不止是Token,可能还有用户权限相关值。你能够这样作:

    1
    2
    3
    4
    5
    6
    7
    from rest_framework_jwt.settings import api_settings

    jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
    jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

    payload = jwt_payload_handler(user)
    token = jwt_encode_handler(payload)
  • 其余设置
    你能够覆盖一些其余设置,好比变动Token过时时间,如下是全部可用设置的默认值。在settings.py文件中设置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    JWT_AUTH = {
    'JWT_ENCODE_HANDLER':
    'rest_framework_jwt.utils.jwt_encode_handler',

    'JWT_DECODE_HANDLER':
    'rest_framework_jwt.utils.jwt_decode_handler',

    'JWT_PAYLOAD_HANDLER':
    'rest_framework_jwt.utils.jwt_payload_handler',

    'JWT_PAYLOAD_GET_USER_ID_HANDLER':
    'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler',

    'JWT_RESPONSE_PAYLOAD_HANDLER':
    'rest_framework_jwt.utils.jwt_response_payload_handler',

    // 这是用于签署JWT的密钥,确保这是安全的,不共享不公开的
    'JWT_SECRET_KEY': settings.SECRET_KEY,
    'JWT_GET_USER_SECRET_KEY': None,
    'JWT_PUBLIC_KEY': None,
    'JWT_PRIVATE_KEY': None,
    'JWT_ALGORITHM': 'HS256',
    // 若是秘钥是错误的,它会引起一个jwt.DecodeError
    'JWT_VERIFY': True,
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_LEEWAY': 0,
    // Token过时时间设置
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
    'JWT_AUDIENCE': None,
    'JWT_ISSUER': None,

    // 是否开启容许Token刷新服务,及限制Token刷新间隔时间,从原始Token获取开始计算
    'JWT_ALLOW_REFRESH': False,
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),

    // 定义与令牌一块儿发送的Authorization标头值前缀
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
    'JWT_AUTH_COOKIE': None,
    }
相关文章
相关标签/搜索