源码剖析Django REST framework的认证方式及自定义认证

时间:2018-11-19
本文章向大家介绍django rest_fremework源码之认证流程剖析,需要的朋友可以参考一下

1.绪言

        上一篇中讲了django rest_framework总体流程,整个流程中最关键的一步就是执行dispatch方法。在dispatch方法中,在调用了一个initial方法,所有的认证、权限检查、访问频率控制都是在这个方法中进行的。下面代码为init方法执行这三个操作的源码:

def initial(self, request, *args, **kwargs):
    """
    在调用方法处理程序之前运行需要发生的任何事情(例如:认证、权限、访问频率控制)。
    """
    ……
    self.perform_authentication(request)#执行认证
    self.check_permissions(request)#检查权限
    self.check_throttles(request)#频率控制

        这一篇我们来分析一下django rest_framework认证部分的源码。

2. Request对象

        这里为什么提到Request对象呢?因为接下来的操作跟它息息相关。我们可以把整个浏览器到服务器的请求过程看作快递运输,Request对象就是我们要运送的包裹,所经历的各个方法就犹如快递中转站或者安检口,会检查Request对象是否符合规定,如果符合规范稍作加工就放行,移送下一个中转站,如果不符合规范,就打回原籍。认证(authentication)显然就是一个安检口,而且还是第一道安检。我们先来看一看这一道安检的入口,即perform_authentication方法的源码:

def perform_authentication(self, request):

       request.user

        你没有看错,perform_authentication方法在删除注释之后就只有两行代码。代码中的request就是我们要运送的主角(Request对象),是在dispatch方法中第一步操作加工之后获得的,里面封装了原生的request对象(快递包裹中真正要寄送的东西),认证类对象(寄件人身份检查方法),以及其他的一些功能函数。这里的request.user就是封装在Request中的一个方法(可不是属性变量),我们来看看user方法的源码:

def user(self):

    if not hasattr(self, '_user'):

        with wrap_attributeerrors():

            self._authenticate()

    return self._user

        在user中,如果有“_user”属性,直接返回这个属性,如果没有调用“_authenticate”方法,再来看看“_authenticate”方法:

def _authenticate(self):

    # 这里的authenticators就是封装在Request对象中的认证对象(在dispatch第一步操作中封装进去的)

    #这个authenticators就犹如快递运送中的寄件人身份核验规范,而且这个规范可能不止一个

    for authenticator in self.authenticators:#遍历每一个认证实例

        try:

            # 执行认证实例中的认证方法(快递开始安检)

            user_auth_tuple = authenticator.authenticate(self)#返回一个tuple

        except exceptions.APIException:#如果安检没有通过

            self._not_authenticated()

            raise   #抛出异常

        if user_auth_tuple is not None:#如果在安检中得到了寄件人的身份信息(也就是那个tuple不为空)

            self._authenticator = authenticator

            self.user, self.auth = user_auth_tuple#在包裹(Request)中添加寄件人信息和核验信息

            return  #返回None

    self._not_authenticated()#如果认证类设置为空
        再来看看认证实例的authenticate方法,我们以TokenAuthentication类为例进行分析,其他的认证类流程大体一致。贴出TokenAuthentication类的authenticate方法源码:
def authenticate(self, request):

    #拿到认证信息(token信息)

    #形如:Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

    auth = get_authorization_header(request).split()#得到['Token', '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b']

    #如果认证信息为空,或者第一个元素不是'Token’

    if not auth or auth[0].lower() != self.keyword.lower().encode():

        return None

    #如果拿到的认证信息不是两个元素

    if len(auth) == 1:

        msg = _('Invalid token header. No credentials provided.')

        raise exceptions.AuthenticationFailed(msg)

    elif len(auth) > 2:

        msg = _('Invalid token header. Token string should not contain spaces.')

        raise exceptions.AuthenticationFailed(msg)

    try:

        token = auth[1].decode()#对第二个元素进行解码

    except UnicodeError:

        msg = _('Invalid token header. Token string should not contain invalid characters.')

        raise exceptions.AuthenticationFailed(msg)

    return self.authenticate_credentials(token)
def authenticate_credentials(self, key):

    model = self.get_model()#token模型类

    try:

        # 根据token在数据库中进行核验

        #这一步操作就类似于对包裹中的寄件人信息与公安局的身份证号码进行比对

        #下面的token是一个token模型类,外键关联user表,可以拿到user信息

        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)#返回一个tuple
        至此,认证实例里面的authenticate方法就分析完了。我们在回到_authenticate方法,如果有认证实例列表(self.authenticators)不为空就执行authenticate方法,如果认证实例列表为空或者,或者执行authenticate方法认证失败,就执行_not_authenticated方法,来看看_not_authenticated方法源码:
def _not_authenticated(self):

    self._authenticator = None#将认证实例设为空

    # 读取配置中的如果没有认证成功生成的用户:默认是匿名用户,可设置为空

    if api_settings.UNAUTHENTICATED_USER:

        self.user = api_settings.UNAUTHENTICATED_USER()

    else:

        self.user = None

    # 读取配置中的如果没有认证成功生成的用户:默认为空

    if api_settings.UNAUTHENTICATED_TOKEN:

        self.auth = api_settings.UNAUTHENTICATED_TOKEN()

    else:

        self.auth = None

        如下是在restframework的settings.py文件中对UNAUTHENTICATED_USER和UNAUTHENTICATED_TOKEN默认设置:

# Authentication

'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',

'UNAUTHENTICATED_TOKEN': None,

        这些设置可以在自己项目文件的settings.py文件中进行重新配置。_not_authenticated方法也是直接修改Request对象中的对象和token,然后返回None。_not_authenticated方法执行完之后,一个认证就完成了,如果认证实例列表还有其他元素,就继续下一个认证,认证流程是一样的,如果所有认证实例都遍历完成,那么整个认证流程就跑完了,Request对象中就包含了认证之后的认证信息。

3. 自定义认证类

    上面分析的是djangorestframework自带的认证类,如果我们要自己创建认证类该怎么做呢?从上面认证类分析中介绍到一个名为authenticate的方法,这个方法在执行认证的时候回被调用,所以自定义认证类的时候,这个方法是必须创建的,另外还有一个名为authenticate_header的方法,也是必须写的。djangorestframework有一个名为BaseAuthentication的类,这是所有认证类的父类,最好继承这个类(也可以不继承,但是那两个方法必须包含)。如下代码是自定义的一个认证类:
class MyTokenAuthentication(BaseAuthentication):

    '''自定义的认证类'''

    def authenticate(self, request):

        token = request.GET.get('token')

        success=check_token(token)

        if success:

            return

        else:

            raise AuthenticationFailed('认证失败')

    def authenticate_header(self, request):

        pass

        创建好认证类之后如何用上呢,这就涉及到配置认证类了。

4. 配置认证类

        在视图中,如果某个类需要指定认证类,该如何进行配置呢?有两种方法——局部配置和全局配置。

        局部配置是只在配置的当前视图类中生效,配置方法时在试图类中加入下面代码:

authentication_classes = [MyTokenAuthentication , ]

        注意:当认证类只有一个时,列表末尾一定要加一个逗号。在局部配置了认证类之后,restframework就只会读取当前配置的认证类,忽略默认设置。

        全局配置是在项目的settings.py文件中进行配置,这样的话所有视图都会执行该配置(除非该视图自定义了局部配置)。配制方法是在项目settings.py文件中REST_FRAMEWORK中加入如下代码:

REST_FRAMEWORK={

  "DEFAULT_AUTHENTICATION_CLASSES":["myapp.auth. MyTokenAuthentication ",]

}

        同样的,当认证类只有一个时,列表末尾一定要加一个逗号,列表中的元素是认证类的路径。