跬步 On Coding

Tastypie实现accesstoken认证

Tastypie提供了几种基本的认证方式比如SessionAuthentication,Django实现的web站点一般都是基于cookie-session的认证方式,在Django中使用中间件的方式处理cookie与session,以及用户认证。使用起来是很方便的。

但是在api中就不能继续使用cookie了,我们会使用accesstoken的方式来实现认证。之前同事使用redis实现了一版accesstoken的认证,但是由于考虑不周全,导致在使用过程中出现了很多bug,然后为了解决bug又写了很多恶心的兼容代码,所以抽空直接使用熟悉的session重构了认证过程。

# coding: utf-8

from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware
from django.utils.functional import SimpleLazyObject
from tastypie.authentication import Authentication

from apps.ucenter.models import UserInfo


SESSION_KEY = '_auth_user_id'


class EhrSessionMiddleware(SessionMiddleware):
    '''
    定制session中间件,继承至Django自己的SessionMiddleware
    '''

    def process_request(self, request):
        session_key = request.META.get('HTTP_ACCESSTOKEN') # 直接在http头里取accesstoken,作为session的key
        request.session = self.SessionStore(session_key) # 获取到request session

    def process_response(self, request, response):
        """
        If request.session was modified, or if the configuration is to save the
        session every time, save the changes and set a session cookie or delete
        the session cookie if the session has been emptied.
        这里就直接抄来,去掉了set_cookie相关的代码
        """
        try:
            modified = request.session.modified
        except AttributeError:
            pass
        else:
            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
                if response.status_code != 500:
                    request.session.save()
        return response


def get_user(request):
    '''
    获取用户信息
    '''
    if not hasattr(request, '_cached_user'):
        user_id = request.session.get(SESSION_KEY)
        user = user_id and UserInfo.objects.filter(pk=user_id).first() or None
        request._cached_user = user
    return request._cached_user


class AuthenticationMiddleware(object):
    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The Django authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE_CLASSES setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'django.contrib.auth.middleware.AuthenticationMiddleware'."
        )
        '''
        认证中间件,也是抄django.contrib.auth.middleware.AuthenticationMiddleware
        '''
        request.user = SimpleLazyObject(lambda: get_user(request))


def login(request, user):
    """
    生成token列表
    """
    if user is None:
        user = request.user

    request.session[SESSION_KEY] = str(user.pk)
    request.session.save()
	return {'accesstoken': request.session.session_key} # 使用session作为token


def logout(request):
    '''
    退出
    '''
    request.session.flush()
    request.user = None


class UserAuthentication(Authentication):
    '''
    认证是否登录,在Tastypie中使用,用于判断request是否已认证
    '''
    def is_authenticated(self, request, **kwargs):
        return isinstance(request.user, UserInfo)

这样使用我们熟悉的session逻辑就实现了我们需要的accesstoken认证,只需要在settings中的中间件配置中注释掉原来的SessionMiddleware,AuthenticationMiddleware中间配置上上面的版本,然后在登录,退出逻辑中使用login,logout函数即可。

MIDDLEWARE_CLASSES = [
    # 'django.contrib.sessions.middleware.SessionMiddleware', 注释掉原来的session中间件
    'core.authentication.EhrSessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    # 'django.contrib.auth.middleware.AuthenticationMiddleware', 注释掉原来的认证中间件
    'core.authentication.AuthenticationMiddleware',
    # 'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'core.middleware.XForwardedForMiddleware',
]