Django REST framework API Guide 之 Authentication

由于业务需要,打乱之前的计划,提前梳理 Authentication 这块。

概览

在官网上,DRF 的 API Guide 涉及如下模块:

  • Requests 请求
  • Response 响应
  • Views 视图
  • Generic views 通用视图
  • Viewsets 视图集合类
  • Routers 路由器
  • Parsers 解释器
  • Renderers 渲染器
  • Serializers 序列化器
  • Serializer fields 序列化器字段
  • Serializer relations 序列化器之间的关系
  • Validators 验证器
  • Authentication 认证
  • Permissions 权限
  • Caching 缓存
  • throttling 限流
  • Filtering 过滤
  • Pagination 分页
  • Versioning 版本
  • Content negotiation 内容协商
  • Metadata 元数据
  • Schemas 架构
  • Format suffixes 格式后缀
  • Returning URLS 返回url
  • Exceptions 异常
  • Status codes 状态码
  • Testing 测试
  • Settings 设置

Authentication

“Auth needs to be pluggable.”

  • Jacob Kaplan-Moss, “REST worst practices”

Authentication 是一种将到来的请求附带上一系列身份信息,比如请求是从哪个用户过来的,或者后端所签发的 token。
依据这些身份信息或者token,后续的权限(permission)或者限流(throttling)策略才能实施。

REST framework 提供了一些开箱即用的 authentication schemes,同样也允许你采用自定义的 schemes。

在每个view 非常早的时候都会进行身份验证(Authentication ),在权限(permission)和限流(throttling)检测前进行,也发生在其他代码被允许执行前。

一般而言,request.user 会被设置为一个 contrib.auth 包中的 User 类的实例。
request.auth 用于任何额外的认证信息,比如,它可能代表 request 被签发的 authentication token。

注意:
不要忘记 身份认证(authentication)本身不会允许或者不允许一个到来的请求(request),它只是简单的对请求(request)发起者的身份进行验证。对于如何为api设置权限策略,可参看 permission documentation (https://www.django-rest-framework.org/api-guide/permissions/)

How authentication is determined

authentication schemes 通常被定义为一个类的列表。REST framework 会尝试用列表中的每个类对请求进行验证,将首个验证成功的类的返回值来对 request.user 和 request.auth 进行赋值。

如果没有一个类有成功验证的结果,request.user 会被设定为一个 django.contrib.auth.models.AnonymousUser 的一个实例,而 request.auth 会被设定为 None。

未被身份认证成功 request 的 request.user 和 request.auth的默认值可以通过 settings 中的 UNAUTHENTICATED_USER 和 UNAUTHENTICATED_TOKEN 来进行修改。

Setting the authentication scheme

默认的 authentication schemes 可以进行全局设置,用 DEFAULT_AUTHENTICATION_CLASSES,比如:

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

你也可以针对每一个基于类(继承APIView)的 view 或者 viewset 单独设置 authentication scheme。

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)

或者如果你使用 @api_view 装饰器:

1
2
3
4
5
6
7
8
9
@api_view(['GET'])
@authentication_classes([SessionAuthentication, BasicAuthentication])
@permission_classes([IsAuthenticated])
def example_view(request, format=None):
content = {
'user': unicode(request.user), # `django.contrib.auth.User` instance.
'auth': unicode(request.auth), # None
}
return Response(content)

Unauthorized and Forbidden responses

当一个未被认证的request的权限被拒绝,这会有两个不同的错误码:

  • HTTP 401 Unauthorized 总是会包含一个 www-Authenticate header,指导客户端如何认证。
  • HTTP 403 Permission Denied 不会包含 www-Authenticate header。

response 的种类取决于 authentication scheme。尽管多个 authentication scheme 可能被用到,但是只有一个scheme 可能用于去决定 response 的种类。view 中设定的第一个 authentication 类被用于去决定response 的类型。

注意到一个request 可能成功验证,但是仍然有可能被决绝去执行该请求,在这种情况下, 403 Permission Denied 会被经常用到,而不管 authentication scheme 是哪个。

Apache mod_wsgi specific configuration

Note that if deploying to Apache using mod_wsgi, the authorization header is not passed through to a WSGI application by default, as it is assumed that authentication will be handled by Apache, rather than at an application level.

If you are deploying to Apache, and using any non-session based authentication, you will need to explicitly configure mod_wsgi to pass the required headers through to the application. This can be done by specifying the WSGIPassAuthorization directive in the appropriate context and setting it to ‘On’.

1
2
# this can go in either server config, virtual host, directory or .htaccess
WSGIPassAuthorization On

API Reference

BasicAuthentication

这个 authentication scheme 使用 HTTP Basic Authentication,针对 用户的 username 和 password。Basic authentication 一般主要适用于测试。

如果身份验证成功,BasicAuthentication 提供如下证明信息:

  • request.user 会被设置成一个 User 实例。
  • request.auth 会是 None。

未被验证、被拒绝授权的 responses 会导致一个 HTTP 401 Unauthorized response,同时附带一个合适的 WWW-Authenticate header。比如:

WWW-Authenticate: Basic realm=”api”

注意:
如果你在生产环境中使用 BasicAuthentication,你必须确保你的API 只对 https 适用。你同样需要确保使用你 API 的客户端每次登录都会请求 username 和 password,并且不会永久存储这些细节。(这一段没怎么懂)

TokenAuthentication

这种 authentication scheme 使用一个简单的基于 tokn 的 HTTP Authentication cheme。Token 验证适用于 Client-Server 模式,比如桌面客户端以及手机客户端。

为了使用 TokenAuthentication scheme,你需要在 authentication classes中配置 TokenAuthentication,并且在你的 INSTALLED_APPS 中添加 rest_framework.authentoken:

1
2
3
4
INSTALLED_APPS = [
...
'rest_framework.authtoken'
]

注意:
确保在你更改 settings 后运行 manage.py migrate。rest_framework.authentoken app 提供了 Django database migrations。

你同样需要问你的用户创建 token:

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

token = Token.objects.create(user=...)
print(token.key)

如果客户端需要验证,Authorization HTTP header 中应该包含 token key。这个key 应该在前面添加一个字符串 “Token”,两个字符串间用空格连接:

1
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

注意:
如果你想在 header 中使用其他的关键字,比如 Bearer,只需简单的继承 TokenAuthentication 并设置设置类 keyword。

如果验证成功,TokenAuthentication 提供如下认证信息:

  • request.user 会被设置成一个 Django User 实例。
  • request.auth 会被设置为 rest_framework.authtoken.models.Token 实例。

未被验证、被拒绝授权的 responses 会导致一个 HTTP 401 Unauthorized response,同时附带一个合适的 WWW-Authenticate header。比如:

WWW-Authenticate: Token

curl 命令工具对于测试 token authentication API 非常有用。比如给:

1
curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'

注意,如果在生产环境中使用 TokenAuthentication,你需要你的API 只适用于 https。

Generating Tokens

通过使用信号量 signals

如果你期望每个用户自动地创建 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)

注意,你需要确保把这段代码放在一个已经安装了的app的model中,或者放在Django在启动时会加载的其他地方。

如果你已经创建了一些用户,你可以为已经存在的用户创建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)
通过暴露一个 api 接口创建

在使用 TokenAuthentication,你可能希望提供这样一种机制,即让客户端通过提供用户名和密码来获取一个token。REST framework 提供了一个内置的view 来实现这一行为。为了使用它,将 obtain_auth_token 添加到你的 URLconf:

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

当username 和 password 以表单或者JSON 格式,通过 POST 发送到 obtain_auth_token时,obtain_auth_token 会返回一个 JSON response:

1
{ 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }

注意默认的 obtain_auth_token view 会显式地使用 JSON requests 和 JSON response,而不会使用在settings中设定的默认 renderer(渲染器) 和 paser(解释器)。

默认情况下,obtain_auth_token 是没有 permissions(权限审查) 和 throttling(限流)。如果你想应用 throttling,你需要重写这个view 类,用 throttle_classes 来设置。

如果你需要一个定制化的 obtain_auth_token view,你可以通过继承 ObtainAuthToken 类来实现,同时需要在你的url 配置里改为你写的类。

比如,你可以在返回的response 中,除了 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())
]
通过 django admin

也可以通过 admin 交互界面手动创建 Token。假使你的用户表数量超大,我们建议你对 TokenAdmin 打猴子补丁,以满足你的需求,贴别的是将user声明为 raw_field。
your_app/admin.py:

1
2
3
from rest_framework.authtoken.admin import TokenAdmin

TokenAdmin.raw_id_fields = ['user']
通过 Django manage.py 命令

3.6.4 版本以后,可以用如下命令创建用户 token:

1
./manage.py drf_create_token <username>

该命令会对针对给出的用户返回 token,如果不存在的话会创建:

1
Generated token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b for user user1

如果你想重新生成token,你可以加上额外的参数 -r :

1
./manage.py drf_create_token -r <username>

SessionAuthentication

这个 authentication scheme 使用 Django 默认的 session backend。Session authentication 适用于 AJAX 客户端,如同你的 website 运行相同的 session 背景。

如果认证成功,SessionAuthentication 提供如下认证信息:

  • request.user Django User 对象
  • request.auth 值是 None

如果未被认证成功,会返回 HTTP 403 Forbidden response。

如果你使用一个 AJAX 风格的API(用SessionAuthentication),你需要确保任何一个不安全的HTTP方法(PUT,PATCH,POST,DELETE)包含一个有效的 CSRF token,参看 Django CSRF 文档了解更多。

警告: 在创建 login 页面时,请用 Django 标准的 login view。这会确保你的login views会受到很好的保护。

REST framework 对于 CSRF 的验证,同Django 的标准方式有些许不同,这是由于前者需要在相同的views同时支持基于 session 和非session 的 authentication。这意味着,只有经过认证 authenticated 的请求需要 CSRF token。而匿名请求请求可能不会携带 CSRF token。这一行为对 login view 并不适用,login view 总是需要 CSRF validation。

RemoteUserAuthentication

这个 authentication scheme 允许你将认证流程授权给你的 web 服务,该服务设置 REMOTE_USER 环境变量。

为了使用它,你必须在你的 AUTHENTICATION_BACKENDS setting 里添加 django.contrib.auth.backends.RemoteUserBackend(或者其子类)。默认情况下,RemoteUserBackend 会为 尚未存在的 username 创建User 对象。如果要改变这种行为,请参考 Django documentation

如果认证成功,RemoteUserAuthentication 提供如下认证信息:

  • request.user Django User 实例
  • request.auth 会被置为 None

有关配置认证方法的信息,参考如下:

用户自定义 Custom authentication

使用用户自定义的 authentication scheme,需要继承 BaseAuthentication 以及重写 .authenticate(self, request)方法。当验证成功,该方法应该返回一个包含两个元素的元组(user, auth),否则返回 None。在某些情形下,当验证失败,你可能不会返回 None,而是从 .authenticate() 方法中抛出一个 AuthenticationFailed 异常。

一般而言,你应该采取如下行为:

  • 如果验证没有被尝试(为理解),返回 None。而其他的 authentication scheme 还会继续 check。
  • 如果认证被尝试,但是失败了,抛出 AuthenticationFailed 异常,一个错误 reponse 会被立即返回,而不会管任何的权限审核,也不会进行其他的身份认证。

你可能也会重写 .authenticate_header(self, request) method. 如果那样,你需要返回一个字符串,该字符串会被作为 HTTP 401 Unauthorized response 的 WWW-Authenticate header 的值。

如果 .authenticate_header() 方法未被重写,当一个未被认证 request 被拒绝访问时,authentication scheme 会返回 HTTP 403 Forbidden 响应。

注意: 当你自定义的 authenticator 被 request 对象的 .user 或者 .auth 属性唤醒时,你可能会看到一个 AttributeError 作为一个 WrappedAttributeError 重新抛出。这可以避免初始异常被外部属性访问封禁掉。Python 不会识别从用户自定义的 authenticator 中抛出的 AttributeError,而是会认为 request 对象没有 .user 和 .auth 属性值。这些错误应该在你自己定义的 authenticator 中进行处理。

例子

The following example will authenticate any incoming request as the user given by the username in a custom request header named ‘X-USERNAME’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions

class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
username = request.META.get('HTTP_X_USERNAME')
if not username:
return None

try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')

return (user, None)

第三方包 Third party packages

Django Oauth Toolkit

The Django OAuth Toolkit package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by Evonove and uses the excellent OAuthLib. The package is well documented, and well supported and is currently our recommended package for OAuth 2.0 support.

Installation & configuration

Install using pip.

1
pip install django-oauth-toolkit

Add the package to your INSTALLED_APPS and modify your REST framework settings.

1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = [
...
'oauth2_provider',
]

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
]
}

For more details see the Django REST framework - Getting started documentation.

Django REST framework OAuth

The Django REST framework OAuth package provides both OAuth1 and OAuth2 support for REST framework.

This package was previously included directly in REST framework but is now supported and maintained as a third party package.

Installation & configuration
Install the package using pip.

pip install djangorestframework-oauth
For details on configuration and usage see the Django REST framework OAuth documentation for authentication and permissions.

JSON Web Token Authentication

JSON Web Token is a fairly new standard which can be used for token-based authentication. Unlike the built-in TokenAuthentication scheme, JWT Authentication doesn’t need to use a database to validate a token. A package for JWT authentication is djangorestframework-simplejwt which provides some features as well as a pluggable token blacklist app.

Hawk HTTP Authentication

The HawkREST library builds on the Mohawk library to let you work with Hawk signed requests and responses in your API. Hawk lets two parties securely communicate with each other using messages signed by a shared key. It is based on HTTP MAC access authentication (which was based on parts of OAuth 1.0).

HTTP Signature Authentication

HTTP Signature (currently a IETF draft) provides a way to achieve origin authentication and message integrity for HTTP messages. Similar to Amazon’s HTTP Signature scheme, used by many of its services, it permits stateless, per-request authentication. Elvio Toccalino maintains the djangorestframework-httpsignature (outdated) package which provides an easy to use HTTP Signature Authentication mechanism. You can use the updated fork version of djangorestframework-httpsignature, which is drf-httpsig.

Djoser

Djoser library provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. The package works with a custom user model and it uses token based authentication. This is a ready to use REST implementation of Django authentication system.

django-rest-auth

Django-rest-auth library provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc. By having these API endpoints, your client apps such as AngularJS, iOS, Android, and others can communicate to your Django backend site independently via REST APIs for user management.

django-rest-framework-social-oauth2

Django-rest-framework-social-oauth2 library provides an easy way to integrate social plugins (facebook, twitter, google, etc.) to your authentication system and an easy oauth2 setup. With this library, you will be able to authenticate users based on external tokens (e.g. facebook access token), convert these tokens to “in-house” oauth2 tokens and use and generate oauth2 tokens to authenticate your users.

django-rest-knox

Django-rest-knox library provides models and views to handle token based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into).

drfpasswordless

drfpasswordless adds (Medium, Square Cash inspired) passwordless support to Django REST Framework’s own TokenAuthentication scheme. Users log in and sign up with a token sent to a contact point like an email address or a mobile number.