【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

推荐阅读:

django从请求到返回都经历了什么

在这里插入图片描述

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的原理

相关文档

前后端分离之JWT用户认证
GitHub:django-rest-framework-jwt

首先安装

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的源码进行学习

猜你喜欢

转载自blog.csdn.net/Yuyh131/article/details/82927192