【Django REST framework电商项目笔记】第07章 手机注册和用户登录(上)
drf 的 token 登录原理
用户的下单,个人中心等功能都是需要用户登录之后才能进行的
drf 的页面中右上角的 login 为什么可以实现登录,是因为我们配置了
# DRF 后台登录 API 接口
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
它里面有login 和 loginout 。里面的 login 调用了loginview,这个 view 是 django自带的 view
@method_decorator(csrf_protect)
会验证我们的csrf。我们打开登录的界面f12可以看到一个隐藏的csrf
注意:
用户的登录在前后端分离的开发中和我们之前基于模板template进行开发的是有一定区别的。
用户登录的session和cookie等在模板中是有用的。
前后端分离的系统,不需要做crsf的验证。app和网站服务端本来就是跨站了
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
SessionAuthentication 实际上是使用了 django 的 sessionMiddleware
每当一个 request 进来的时候,这两个 meddleware 就会将我们的 cookie 里的session_id 转换成 request.user
rest_framework/authentication.py
源码解读:
class SessionAuthentication(BaseAuthentication):
"""
Use Django's session framework for authentication.
"""
def authenticate(self, request):
"""
Returns a `User` if the request session currently has a logged in user.
Otherwise returns `None`.
"""
# Get the session-based user from the underlying HttpRequest object
user = getattr(request._request, 'user', None)
# Unauthenticated, CSRF validation not required
if not user or not user.is_active:
return None
self.enforce_csrf(request)
# CSRF passed with authenticated user
return (user, None)
其中的 authenticate 就是从我们的 request 中取出 user。实际上还依赖的是 django 自带的 session机制
drf 为我们提供了三种不同的 auth
sessionauth 在浏览器中比较常见,它会自动设置 cookie,并将 session 等带到我们的服务器
前后端分离的系统中这种 sessionauth 比较少见
TokenAuth
INSTALLED_APPS = (
...
'rest_framework.authtoken',
)
tokenauth 会为我们建一张表的,凡是会有表产生的。都必须放到 INSTALLED_APPS 中
否则会造成 makemigrate 报错
我们必须为我们的token生成配置相关的url
官方文档代码:
from rest_framework.authtoken import views
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token)
]
django源码中的sessionMiddleware中有一个process_request方法
和一个process_response方法。
django/contrib/sessions/middleware.py:
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
setting 中注册的 middleware 会将用户 request 的数据经过这些 middlware 中有 process_request 方法和 process_response 方法注册进入。
当用户的request进入view之前会将这些process_request通通调用一遍
如果用户post过来的是session_id那么我们的session middleware就会起作用
会执行上面代码从request.cookies中获取到setting中设置的SESSION_COOKIE_NAME
这里仅仅是完成了把session放入request
推荐阅读:
drf在setting中设置的auth和我们前面django自带的middlware是不一样的
django自带的是会对每一个request做一些处理。而drf的auth是来验证用户登录的
rest_framework/authentication.py
中的 TokenAuthentication
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
会调用get_authorization_header获取到request中的HTTP_AUTHORIZATION取到的值就是后面的value
前面的Token的校验是通过转化为小写。和小写的keyword进行校验的。
if not auth or auth[0].lower() != self.keyword.lower().encode():
可以经验证我们使用小写的也不会影响。而且这个关键字可以通过比如Bearer简单地创建子类TokenAuthentication并设置keyword类变量,覆盖该变量进行实现
get_model方法会获取到我们的Token 这个model
def get_model(self):
if self.model is not None:
return self.model
from rest_framework.authtoken.models import Token
return Token
Token这个model就是我们对应的数据库中的那张表
class Token(models.Model):
"""
The default authorization token model.
"""
key = models.CharField(_("Key"), max_length=40, primary_key=True)
user = models.OneToOneField(
settings.AUTH_USER_MODEL, related_name='auth_token',
on_delete=models.CASCADE, verbose_name=_("User")
)
created = models.DateTimeField(_("Created"), auto_now_add=True)
这里我们就可以重写这个model及Token auth为我们定制出token的过期时间等
def authenticate_credentials(self, key):
model = self.get_model()
try:
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.'))
return (token.user, token)
这里是在通过我们的key(token值),取Token model中的关联user中获取token.user
那么问题来了,表里的token值是哪里来的呢?
# token授权登录,获取token需要向该地址post数据
url('api-token-auth/', views.obtain_auth_token)
ObtainAuthTokenview的post方法中的get_or_create会get。没有就create,因为刚才我们登录的时候,它就自动创建了。
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})
drf的token是保存在我们的服务器数据库当中,但是我们如果是一个分布式的系统
两套系统想用一个token的话就会出现问题。分布式就得做用户同步
第二个非常严重的问题,这个token没有过期时间。永久有效
解决办法 : JWT认证机制
viewsets 配置认证类
删除setting中的auth token配置
并在列表页中添加单独的auth 认证
# 设置列表页的单独auth认证
authentication_classes = (TokenAuthentication,)
因为商品列表页是一个公开的。所以不能配置这个。测试完成后,必须注释掉
Json Web Token的原理
相关文档
首先安装
pip install djangorestframework-jwt
需要将jsonWebAuth加入到drf 的default auth class中
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
配置path中的jwt
# jwt的token认证, 验证jwt的token是否匹配
url(r'^login/$', obtain_jwt_token),
jwt的加密认证方式可以参照jwt的源码进行学习