Django 中认证和权限
在没有使用 drf 之前,如何判断用户是否登录,一般是给前端提供一个获取用户信息的接口,如果未登录返回未授权等信息,权限的话一般是在 model 层通过字段来设置,这样只能完成简单的权限限制。
@require_http_methods(['GET'])
@ensure_csrf_cookie
def api_userinfo(request):
"""
获取当前用户信息
"""
if not request.user.is_authenticated:
return api.not_authorized()
return JsonResponse()
认证
认证一般分三种:
- session认证,内部通过Django 的 auth 系统,cookie中要携带 sessionid、csrftoken,请求头中要携带 x-csrftoken
- token 认证,基于令牌的 HTTP 认证方案,请求头中要携带 authorization,值为 jwt空格token,但有弊端,下方会讲
- 自定义认证,一般是继承BaseAuthentication(或其子类),重写authenticate
自定义认证类
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request._request.GET.get("token")
try:
token_obj = ReviewUserToken.objects.get(token=token)
except ReviewUserToken.DoesNotExist:
raise exceptions.AuthenticationFailed("认证失败")
return (token_obj.user, token_obj)
def authenticate_header(self, request):
pass
Django REST Framework 中认证流程
当用户进行登录的时候,运行了登录类的as_view()方法,进入了APIView类的dispatch方法,在原始的 django 中 dispatch 只做方法的分发与执行,并不做认证、权限、节流等操作。
原始 dispatch :
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
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
return handler(request, *args, **kwargs)
APIView 类也是继承自 View 并重写了 dispatch 方法,加上了认证等信息。
下面一步一步进行解析:
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不再是django的request 而是 drf 里面的
request = self.initialize_request(request, *args, **kwargs)
# 重新复制给request,记住,这是封装之后的
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 执行认证、权限、节流相关逻辑,认证也在里面,先看认证。
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
# 以下和 django View 里面内容一样
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
initial 方法:
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
# 认证、权限、节流,依次,
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
perform_authentication 方法:
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.
"""
# 执行了 user 这个属性,找到这个 user 属性,只不过是方法加了 property 装饰器
request.user
@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():
# 执行了_authenticate() 方法。
self._authenticate()
return self._user
_authenticate 方法:
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
# MyAuthentication 自定义的认证类
# APIView 中指定的 authentication_classes = [MyAuthentication, ]
# authenticators 就是封装 request 的时候已经赋值给 authenticators
"""
def initialize_request(self, request, *args, **kwargs):
parser_context = self.get_parser_context(request)
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(), # 将认证类赋值给 authenticators
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
def get_authenticators(self):
# 返回一个实例化好的列表
return [auth() for auth in self.authentication_classes]
"""
# 循环每一个认证类,这个时候 self.authenticators
# 内容就是 get_authenticators 里面实例化好的对象了,在这循环遍历认证。
for authenticator in self.authenticators:
try:
# 返回一个二元组,分别是认证成功的 user 实例和 auth
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
# 如果认证失败,会往上抛出异常,将会被 dispatch 捕获并返回
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
# 赋值给 user 和 auth 对象
self.user, self.auth = user_auth_tuple
return
# 认证失败
self._not_authenticated()
def _not_authenticated(self):
"""
Set authenticator, user & authtoken representing an unauthenticated request.
Defaults are None, AnonymousUser & None.
"""
self._authenticator = None
# 默认配置文件中配置的,default_sttings.py
if api_settings.UNAUTHENTICATED_USER:
# 也可以在配置文件中配置成自己想要的
self.user = api_settings.UNAUTHENTICATED_USER() # AnonymousUser
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN() # None
else:
self.auth = None
配置文件中配置认证类:
默认会去配置文件读,如果 APIView 中写了就用 APIView 中的。
key 的名称叫 REST_FRAMEWORK。
配置如下:
REST_FRAMEWORK = {
# 自定义认证类的路径,可以多个
"DEFAULT_AUTHENTICATION_CLASSES": ["api.utils.auth.MyAuthentication"],
}
默认的认证类:
为了规范,自定义的认证类都要继承:BaseAuthentication
class BaseAuthentication:
"""
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
**BasicAuthentication:**基于用户名密码的身份验证。
class BasicAuthentication(BaseAuthentication):
"""
HTTP Basic authentication against username/password.
"""
pass
**SessionAuthentication:**使用 Django 的会话框架进行身份验证。
class SessionAuthentication(BaseAuthentication):
"""
Use Django's session framework for authentication.
"""
pass
**TokenAuthentication: ** 基于 token 令牌的方式
如:Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
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
"""
pass
不建议使用 drf 自带的 token 认证,因为 token 是保存在后端,
每次都需要查询数据库,增加数据库的压力,不利于分布式系统中使用,
在实际项目中会使用 JWT 标准的认证方式,python中可以使用 pyjwt
一般配置文件中这样设置:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
Django REST Framework 中权限模块和执行流程
权限模块也是在 initial 中:check_permissions
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
# 这次读取的是 APIView 中的 permission_classes
# 设置如:permission_classes = [IsAuthenticated]
# 需要在类中实现一个 has_permission 方法。如下:
"""
class IsAuthenticated(BasePermission):
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
"""
# 循环的是 get_permissions 实例化之后的权限类
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
# message 可以自定义,默认是异常里面抛出的
request, message=getattr(permission, 'message', None)
)
同样也有一个获取所有权限类的方法,并且实例化之后返回一个列表:
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]
没有权限直接抛出异常:
def permission_denied(self, request, message=None):
"""
If request is not permitted, determine what kind of exception to raise.
"""
if request.authenticators and not request.successful_authenticator:
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied(detail=message)
同样也可以配置文件中配置:
REST_FRAMEWORK = {
# 自定义认证类路径
"DEFAULT_AUTHENTICATION_CLASSES": ["rest_framework.authentication.SessionAuthentication"],
# 自定义权限类路径,这里是使用的默认的,自定义的可以把路径写在这里
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
}
总结
源码内容:
- self.initialize_request这个方法,里面封装了request和认证对象列表等其他参数
- 执行self.initial方法中的self.perform_authentication,里面运行了user方法
- 再执行了user方法里面的self._authenticate()方法
- self._authenticate() 循环遍历每个认证类,将得到的user和auth赋值给user和auth方法
- 这两个方法把user和auth的值分别赋值给request.user:是登录用户的对象;request.auth:是认证的信息字典
- 注意:自定义的类中没有 authenticate 会报错
- 没有携带认证信息,直接返回None => 游客
- 有认证信息,校验成功,返回一个元组,第一个参数赋值给request.user,第二个赋值给request.auth
- 有认证信息,校验失败,抛异常 => 非法用户
参考文献
drf 中文网:https://q1mi.github.io/Django-REST-framework-documentation/api-guide/authentication_zh/