写在前面
接 Django REST framework 登录之JWT
上文提到JWT的认证方式,配置实现起来很方便;但是公司项目中的登录认证是多种多样的,比如:内部系统的SSO,Oauth等认证方式;
那今天就聊聊在DRF的框架下如何自定义登录认证。
本文的代码是以 Django REST framework (三)为基础开发的
正文开始
1. sso接口准备
新增 demo/mock_view.py 该文件主要是用于模拟 sso 登录、token验证的接口
- 登录 支持用户密码登录 (只为逻辑完整性)
- token验证 不实现具体逻辑只为演示整体逻辑
注意:在set cookie 的时候会直接影响到浏览器的默认行为,如果公司期望是一个token访问所有的服务,那么这里一定要配置顶级域名;类似 baidu.com 而不是 baike.baidu.com;
# 1. 新增文件
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from django.conf.urls import url
class AnyAuthentication(BaseAuthentication):
def authenticate(self, request):
return
@api_view(['POST'])
@authentication_classes((AnyAuthentication,))
@permission_classes((AllowAny, ))
def login(request):
token = request.COOKIES.get('auth', "auth")
password = request.data.get('password', '')
username = request.data.get('username', '')
# user center check username password
response = Response({'user': "user_info", 'token': token})
response.set_cookie('auth', token, domain="0.0.0.0", expires=30 * 24 * 60 * 60)
return response
@api_view(['GET'])
@authentication_classes((AnyAuthentication,))
@permission_classes((AllowAny,))
def check_token(request, token):
token = request.COOKIES.get('auth')
# user center check token ...
data = {
"user_info": {
"username": "admin",
"user_id": 1
},
"token": token
}
return Response(data)
mock_urls = [
url('^login/', login),
url(r"^check_token/(?P<token>[A-Za-z0-9]+)/$", check_token)
]
2. urls.py 新增路由
from .mock_view import mock_urls
...
urlpatterns += mock_urls
2. 安装依赖
由于会远程调用 mock的服务需要引入 网络请求的包
pipenv install requests
3. 实现逻辑
新增文件 utils/authentication.py
# 1. 新增 utils/authentication.py
import requests
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from django.utils.translation import ugettext_lazy
from django.contrib.auth.models import User
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.COOKIES.get('auth')
if token is not None:
username = self.check_token(token)
if username is not None:
return User.objects.get(username=username), token
else:
raise exceptions.AuthenticationFailed(ugettext_lazy('Invalid token.'))
@staticmethod
def check_token(token):
# 模拟请求token的验证
response = requests.get("http://localhost:8000/check_token/" + token)
if response.status_code == 200:
return response.json().get("user_info", {}).get("username")
2. settings.py 中新增认证的配置
'DEFAULT_AUTHENTICATION_CLASSES': (
'utils.authentication.MyAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
4. 验证结果
# 默认的 basci 认证方式
$ curl -H 'Accept: application/json; indent=4' -u admin:admin http://127.0.0.1:8000/api/article/1/
{
"id": 1,
"creator": "admin",
"tag": "现代诗",
"title": "如果",
"content": "今生今世 永不再将你想起\n除了\n除了在有些个\n因落泪而湿润的夜里 如果\n如果你愿意"
}
# 自定义token认证方式
$ curl -H 'Accept: application/json; indent=4' --cookie "auth=123" http://127.0.0.1:8000/api/article/1/
{
"id": 1,
"creator": "admin",
"tag": "现代诗",
"title": "如果",
"content": "今生今世 永不再将你想起\n除了\n除了在有些个\n因落泪而湿润的夜里 如果\n如果你愿意"
}
总结
至此 自定义认证的逻辑已经完成,关于认证的几个问题总结一下:
- 自定义认证与默认的认证执行顺序是什么?
答:在settings文件中我们配置了 DEFAULT_AUTHENTICATION_CLASSES 元祖,默认的执行顺序是自上而下的验证,如果都验证失败则抛出失败的异常,这里的异常最终会被DRF补获,返回给前端。
- 实现 MyAuthentication 的时候需要注意的地方? 答:下面是 BaseAuthentication 的源码,可以看到的 DRF所有的认证都需要继承BaseAuthentication且必须实现authenticate,需要注意的是返回值为一个 元组 (user, token);当然这就是在django中使用 request.user 获取当前用户的底层逻辑。
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
- 这样我们的系统就支持多种认证方式,无论是何种登录也都可以快速支持;人生苦短,我用python。
下图为django中request类的部分代码截取,就可以解释 执行顺序以及user对象的获取
最后多说一句无论是自己实现的认证还是 DRF的认证都是继承 BaseAuthentication 并实现authenticate;其实源码也不难,只怕你用心。