本节大纲
1、Validators
2、Authentication
Validators
在REST框架中处理验证的大多数时间,您将仅仅依赖于缺省字段验证,或在序列化器或字段类上编写显式验证方法。但是,有时您需要将验证逻辑放入可重用组件中,以便可以在整个代码库中轻松地重用它。这可以通过使用验证器函数和验证器类来实现。
Validation in REST framework
Django REST framework serializer里面的验证处理有一些不同于Django ModelForm类工作方式。ModelForm的验证可以一部分在form上,一部分在model实例上执行。而REST框架的验证全部在serializer类上执行。它有下面这些好处
1、它引入合适的关注分离,让你的代码变得更清晰 2、在ModelSerializer类和Serializer类切换上变得更简单。用于ModelSerializer任何验证行为都很容易复制。 3、打印serializer实例的repr,讲精确的显示应用的验证规则
当你使用ModelSerializer所有的这些将会自动被处理,如果你放弃并使用Serializer类代替,你需要显式的定义验证规则。
class CustomerReportRecord(models.Model): time_raised = models.DateTimeField(default=timezone.now, editable=False) reference = models.CharField(unique=True, max_length=20) description = models.TextField()
class CustomerReportSerializer(serializers.ModelSerializer): class Meta: model = CustomerReportRecord
当我们使用shell:
>>> from project.example.serializers import CustomerReportSerializer >>> serializer = CustomerReportSerializer() >>> print(repr(serializer)) CustomerReportSerializer(): id = IntegerField(label='ID', read_only=True) time_raised = DateTimeField(read_only=True) reference = CharField(max_length=20, validators=[<UniqueValidator(queryset=CustomerReportRecord.objects.all())>]) description = CharField(style={'type': 'textarea'})
有趣的一点是reference字段。可以看到唯一性约束通过serializer字段上的一个验证器被显式执行。因此,更明确地样式REST框架包换一些Django核心中不可用的验证器类。下面详细介绍这些
Unique Validator
此验证器可以被用来强制unique=True约束在model字段上。它采取了一个单独需要的参数和一个可选择的messages参数
1、queryset # required 2、message # 验证失败的错误信息 3、lookup # 用来查找一个值被验证过的已存在的实例,默认是exact
使用如下:
from rest_framework.validators import UniqueValidator slug = SlugField( max_length=100, validators=[UniqueValidator(queryset=BlogPost.objects.all())] )
UniqueTogetherValidator
此验证器被用来强制unique_together约束在model实例上,他有两个需要的参数可一个可选的messages参数
1、queryset 2、fields # 联合唯一字段组 3、message
from rest_framework.validators import UniqueTogetherValidator class ExampleSerializer(serializers.Serializer): class Meta: # ToDo items belong to a parent list, and have an ordering defined # by the 'position' field. No two items in a given list may share # the same position. validators = [ UniqueTogetherValidator( queryset=ToDoItem.objects.all(), fields=('list', 'position') ) ]
UniqueForDateValidator、UniqueForMonthValidator、UniqueForYearValidator
这些验证器可以被用来强制unique_for_data、unique_for_month和unique_for_year约束在模型实例上,它有以下参数:
1、queryset # 需要的 2、field # 需要的 3、date_field # 需要的 4、message
from rest_framework.validators import UniqueForYearValidator class ExampleSerializer(serializers.Serializer): # ... class Meta: # Blog posts should have a slug that is unique for the current year. validators = [ UniqueForYearValidator( queryset=BlogPostItem.objects.all(), field='slug', date_field='published' ) ]
date字段被验证器使用,总是需要在序列化类上出现。你不能简单地依赖模型类的default,因为用于默认值的值只有在验证运行之后才会生成。如果您使用的是ModelSerializer,可能只需要依赖REST框架为您生成的默认值,但是如果您使用的是Serializer或者只是想要更明确的控制,那么使用下面演示的样式。
Using with a writable date field
如果想要一个日期字段可写唯一值得做的就是确保它在输入数据总是可用的,无论是设置default还是required=True
published = serializers.DateTimeField(required=True)
Using with a read-only field
如果想要日期字段对用户隐藏,可以使用HiddenField。这个字段类型不接受用户输入,但总是返回默认值到serializer的validated_data里
published = serializers.HiddenField(default=timezone.now)
Advanced field defaults
验证器在serializer上被用于多字段上,有时需要一个不是API客户端挺哥的字段输入,但是在验证器中作为可用的输入。
1、HiddenField 2、使用一个标准的字段带read_only=True参数,但这也包含了一个default参数,可以被用在serializer输出展示,但不能被用户直接设置。
CurrentUserDefault
owner = serializers.HiddenField( default=serializers.CurrentUserDefault() )
CreateOnlyDefault
一个默认类,只有在创建操作期间设置一个默认参数时可以被使用,在更新时此字段被忽略。内部参数为默认值
created_at = serializers.DateTimeField( default=serializers.CreateOnlyDefault(timezone.now) )
Limitation of validators
有一些模糊的案例,比起依赖默认的ModelSerializer生成的serializer类,你需要清楚的替代掉处理验证。在这些案例里你可能需要让自动生成的验证器不可用,通在serializer的Meta.validator属性指定一个空的列表。
Optional fields
默认联合唯一的所有字段都是required=True。在一些情况下,你可能想应用required=False到其中的一个字段上。在这种情况下,所需的验证行为是模糊的。
在这种案例里面,你通常需要从serializer类里面排除这个验证器,显式的写下任何验证逻辑来替代,无论是.validate()方法还是在view里面。
class BillingRecordSerializer(serializers.ModelSerializer): def validate(self, data): # Apply custom validation either here, or in the view. class Meta: fields = ('client', 'date', 'amount') extra_kwargs = {'client': {'required': False}} validators = [] # Remove a default "unique together" constraint.
Writing custom validators
可以使用django存在的验证器,或者写你自己的客制化的验证器
Function based
一个验证器可以随时被调用,失败了引发serializers.ValidationError错误
def even_number(value): if value % 2 != 0: raise serializers.ValidationError('This field must be an even number.')
Field-level validaion
你可以制定自定义的字段级别的验证通过添加.validate_<field_name>方法到你的Serializer子类里面。
Class-based
使用__call__方法,来写一个基于类的验证器。允许你参数化重用行为。
class MultipleOf(object): def __init__(self, base): self.base = base def __call__(self, value): if value % self.base != 0: message = 'This field must be a multiple of %d.' % self.base raise serializers.ValidationError(message)
Using set_context()
在某些高级情况下,你可能希望验证器被传递用作附加上下文不能的序列化字段。可以通过在基于类的验证器上申明set_context方法来实现这一点。
def set_context(self, serializer_field): # Determine if this is an update or a create operation. # In `__call__` we can then use that information to modify the validation behavior. self.is_update = serializer_field.parent.instance is not None
Authentication
身份验证是将传入请求与一组标识凭据关联的机制,这些标识凭据例如来自请求的用户或与之签名的令牌。之后,权限和节流策略可以使用这些凭据来确定请求是否应该被允许。
REST framework提供了很多验证方案,也允许你执行自定义的方案。
验证总是在视图的很开始的部分执行,在权限和节流检查之前,在任何其他代码允许被执行之前。
request.user属性将被设置成contrib.auth包的User类的一个实例。
request.auth属性被任何其他的验证信息使用,比如可以用来表示请求签名的身份验证令牌
注意:
不要忘记验证本事不会允许或者不允许一个即将到来的请求,它简单的标识请求的凭据。
How authentication is determined
验证方法总是定义为一组类的列表,REST framework视图验证列表里面的每一个类,使用成功认证的第一个类的返回值设置request.user和request.auth。
如果没有类验证,request.user将会被设置为django.contrib.auth.models.AnonymousUser, request.auth将被设置成None。
没有验证的请求的request.user和request.auth的值可以被修改通过使用UNAUTHENTICATED_USER和UNAUTHENTICATED_TOKEN设置。
Setting the authentication scheme
默认验证方案是通过设置DEFAULT_AUTHENTICATION_CLASSES进行全局设置。
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', ) }
你也可以设置验证方案在每一个视图跟试图借,使用APIView的CBV格式
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)
或者在FBV格式里使用@api_view装饰器
@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
当一个没有验证的请求被拒绝权限,有两种可能合适的不同的错误代码
1、HTTP 401 Unauthorized
2、HTTP 403 Permission Denied
HTTP 401 响应必须总是包含WWW-Authenticate请求头,告诉客户端如何验证. HTTP 403 响应不包含WWW-Authenticate请求头。这种类型响应的使用依赖于验证方案,尽管多个验证方法可能在使用,仅仅只有一个方案可以被使用来决定响应的类型。在确定响应类型时使用视图上设置的第一个验证类。
如果请求验证成功,但仍然被权限拒绝执行请求,不管认证方案如何,此时403 Permission Denied响应将总是被使用.
Apache mod_wsgi specific configuration
注意,如果使用mod_wsgi部署在Apache,默认验证头不会通过WSGI应用,因为它假设验证将由Apache处理,而不是应用程序级别。
如果你部署在Apache,使用任何基于没有session的认证,你讲需要显示地配置mod_wsgi来从应用中通过需要的请求头。可以通过制定WSGIPassAuthorization指令在合适的上下文并设置它为on来完成。
# this can go in either server config, virtual host, directory or .htaccess WSGIPassAuthorization On
API Reference
BasicAuthentication
验证方案使用HTTP基础认证,通过用户名和密码登录,基础认证通常只适用于测试。
如果认证通过,BasicAuthentication提供下面的证书
1、request.user将会是一个Django的User实例
2、request.auth将会是None
认证不通过的响应将会返回HTTP 401 Unauthorized 响应并带有一个合适的WWW-Authenticate头
WWW-Authenticate: Basic realm="api"
如果使用BasicAuthentication在生产上,你必须确保你的API只在https下可用。你也必须保证你的API客户端总是在登录的时候重新发送用户名和密码,并且永远不会讲这些细节存储到持久存储中。
TokenAuthentication
这个认证方案使用基于简单令牌的HTTP认证方案。Token验证适合于客户端-服务器的设置,比如本地桌面和移动客户端。
INSTALLED_APPS = ( ... 'rest_framework.authtoken' )
注意:添加完需要执行 manage.py migrate,rest_framework.authtoken 项目提供Django 数据库的migrations。你也需要为你的用户创建token
from rest_framework.authtoken.models import Token token = Token.objects.create(user=...) print(token.key)
对于客户端验证,token应该被包含在Authorization HTTP请求头。该键应该由字符串文本“Token”前缀,用空白分隔两个字符串。
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
如果你想要使用不能的关键字在请求头里面,比如Bearer,TokenAuthentication的子类设置关键字类变量。
成功的话,TokenAuthentication提供下面的证书
1、request.user # django user实例 2、request.auth # rest_framework.authtoken.models.Token实例
被拒绝权限没有认证的响应,讲导致一个HTTP 401 Unauthorized响应带有一个合适的WWW-Authenticate请求头
WWW-Authenticate: Token
curl命令行工具测试token认证APIs很有用
curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'
Generating Tokens
使用信号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
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)
By exposing an api endpoint
当使用TokenAuthentication,可能想要提供一种机制,让客户端获得用户名和密码的token。REST框架一个内置的视图提供了这个行为。obtain_auth_token视图
from rest_framework.authtoken import views urlpatterns += [ url(r'^api-token-auth/', views.obtain_auth_token) ]
obtain_auth_token视图当合法的用户名和密码字段被通过表单或者JSON发送时,返回一个JSON响应
{ 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }
默认没有权限或者节流应用于obtain_auth_token视图,如果你确实想应用节流,就需要重写视图类,通过throttle_classes属性来包含它。如果你需要一个自定义的obtain_auth_token视图的版本,你可以继承ObtainAuthToken类。
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 })
urlpatterns += [ url(r'^api-token-auth/', CustomAuthToken.as_view()) ]
with Django admin
通过admin接口手动创建Token也可以。如果您使用的是大型用户基础,我们建议您对TokenAdmin类进行修补,以便根据需要对其进行定制,更具体地说,通过将user字段声明为raw_field。
your_app/admin.py
:
from rest_framework.authtoken.admin import TokenAdmin TokenAdmin.raw_id_fields = ('user',)
Using Django manage.py command
由于3.6.4版本之后可以使用下面的命令生成用户的token
./manage.py drf_create_token <username>
这个命令将返回API提供的user的token,创建它如果不存在
Generated token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b for user user1
如果你想重新生成token,你可以执行
./manage.py drf_create_token -r <username>
Custom Authentication
执行自定义认证方案,继承BaseAuthentication重写.authenticate(self, request)方法。验证通过则返回元祖(user, token),否则None。在一些情况下可以在.authenticate()方法内引发AuthenticationFailed错误来代替None。
通常有以下2个分支:
1、如果未尝试进行身份验证,则返回“None”。任何其他认证方案也在使用中仍然会被检查。 2、如果试图进行身份验证但失败,则引发AuthenticationFailed的异常。将立即返回错误响应,而不管任何权限检查,也不检查任何其他身份验证方案。
你也可以重写.authenticate_header(self, request)方法. 如果成功,它应该返回字符串,被用来作为Http请求头内WWW-Authenticate的值在HTTP 401 Unauthorized响应中。
示例:
下面的示例将在名为“X_USERNAME”的自定义请求头中验证任何传入请求作为用户名。
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('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)