ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

django基于authenticate()函数的源码解析

2021-01-22 12:29:16  阅读:205  来源: 互联网

标签:authenticate return django 源码 user password hasher backend


如果我们使用自身的一个账号和密码进行登录验证的话,不得不使用authenticate()函数,
至于authenticate()是怎么实现的,下面一一道来。
下面这个代码是个登录视图,省略了一部分代码,应该可以看懂,看不懂的话,拉到最后,看完整版的:

class LoginView(View):
    def post(self, request):
       #。。。代码省略
        user = authenticate(username=username, password=password)
	   #。。。代码省略

上图是一个继承了View的登录视图,username和password就是从浏览器传递过来的,虽然省略了很多代码,但是不影响理解,只需要知道,如果验证成功后,返回的是登录用户的user对象就行了。
直接进去authenticate()函数里探个究竟:

def authenticate(request=None, **credentials):
    """
    If the given credentials are valid, return a User object.
    如果给定的凭据有效,则返回一个User对象。
    """
    for backend, backend_path in _get_backends(return_tuples=True):
    	#_get_backends获取所有的认证模板,可以自定义,也可以用django默认的
        try:# backend_path = 'django.contrib.auth.backends.ModelBackend' backend = ModelBackend对象
        	#下面这段代码可以暂时性忽略
            inspect.getcallargs(backend.authenticate, request, **credentials)
        except TypeError:
            # This backend doesn't accept these credentials as arguments. Try the next one.
            #此后端不接受这些凭据作为参数。试试下一个,就是说验证不通过,试试下一个验证后端。
            #Django在使用他们的时候,会遍历所有的auth backends,一旦发现有一个backend校验通过,即返回User对象,那么将会停止下面backend的校验,并且将校验成功的backend绑定在该用户上放入session中,此后如果再次调用该方法,那么将会使用session中的backend进行校验,而不再遍历所有backend了。
            continue
        try:#credentials = {"username":13569784692,"password":123456abc}
            user = backend.authenticate(request, **credentials)
        except PermissionDenied:
            # This backend says to stop in our tracks - this user should not be allowed in at all.
            break
        if user is None:
            continue
        # Annotate the user object with the path of the backend.
        user.backend = backend_path
        return user

_get_backends()这个部分的源码我没写,可以参考别人写的关于认证后端的博客接下来注意下面这段代码,它其实就是去调用每个认证后台的authenticate(),只要同过一个,那么我们就认为都通过:

user = backend.authenticate(request, **credentials)

django默认的认证后端只有ModelBackend这一个(详细参考),点进去ModelBackend类的authenticate()方法看一下:

    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
        	#UserModel.USERNAME_FIELD代表你用那个变量验证,是用户名还是手机号,
        	#可以在setting.py文件里自定义,也可以在程序里定义,这样就能实现,用户名可	以是手机号,邮箱或者其他字段
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            user = UserModel._default_manager.get_by_natural_key(username)#把username作为条件,在这一步查了数据库 user:13569784692
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a nonexistent user (#20760).
            #运行默认的密码散列器一次以减少时间
            # 存在用户和不存在用户的区别
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

接下来进入check_password()函数里看一下:

    def check_password(self, raw_password):
        """
        Return a boolean of whether the raw_password was correct. Handles
        hashing formats behind the scenes.
        返回raw_password是否正确的布尔值。处理
        幕后的哈希格式。
        """
        def setter(raw_password):
            self.set_password(raw_password)
            # Password hash upgrades shouldn't be considered password changes.
            self._password = None
            self.save(update_fields=["password"])
        return check_password(raw_password, self.password, setter)

setter是干嘛的直接不管,因为它暂时还没有被用到,目前只是一个参数。直接进入check_password()函数:

def check_password(password, encoded, setter=None, preferred='default'):
	#is_password_usable()检查提供的字符串是否是可以用check_password()验证的哈希密码。
    if password is None or not is_password_usable(encoded):
        return False

    preferred = get_hasher(preferred)##django默认选中加密列表的第一个
    #代码略。。。

进入get_hasher()看一下:

def get_hasher(algorithm='default'):
    """
    返回一个已加载的密码散列器实例。
    如果algorithm是'default',则返回默认的散列值。懒加载的形式导入hashers
    如果需要,在项目的设置文件中指定。
    """
    if hasattr(algorithm, 'algorithm'):
        return algorithm

    elif algorithm == 'default':
        return get_hashers()[0]

    else:
        hashers = get_hashers_by_algorithm()#获取所有加密算法的类的字典{"加密算法名":"django.contrib.auth.hashers.PBKDF2PasswordHasher",....}
        try:
            return hashers[algorithm]#根据algorithm从众多加密算法的字典中选一个合适的算法
        except KeyError:
            raise ValueError("Unknown password hashing algorithm '%s'. "
                             "Did you specify it in the PASSWORD_HASHERS "
                             "setting?" % algorithm)

再回到def check_password()函数:

def check_password(password, encoded, setter=None, preferred='default'):
  	#代码略。。。

    preferred = get_hasher(preferred)##django默认选中加密列表的第一个,这里暂时和数据库中的那个密码的加密方式没关系,后面才进行对比
    try:
        hasher = identify_hasher(encoded)
    except ValueError:
        # encoded is gibberish or uses a hasher that's no longer installed.
        #编码是一个随机的散列值或者使用一个从没有被注册过的散列器。
        return False
	#代码略。。。

到identify_hasher(encoded)代码里看一下:

def  identify_hasher(encoded):
  	#判断数据库里的密码属于那种加密类型
    if ((len(encoded) == 32 and '$' not in encoded) or
            (len(encoded) == 37 and encoded.startswith('md5$$'))):
        algorithm = 'unsalted_md5'
    # Ancient versions of Django accepted SHA1 passwords with an empty salt.
    elif len(encoded) == 46 and encoded.startswith('sha1$$'):
        algorithm = 'unsalted_sha1'
    else:
        algorithm = encoded.split('$', 1)[0]
    return get_hasher(algorithm)#返回使用的加密算法的类的名字

再回到def check_password()函数:

def check_password(password, encoded, setter=None, preferred='default'):
   
   #代码略。。。

    preferred = get_hasher(preferred)##django默认选中加密列表的第一个,因为要通过它对最原生的密码进行加密
    try:
        hasher = identify_hasher(encoded)
    except ValueError:
        # encoded is gibberish or uses a hasher that's no longer installed.
        #编码是一个随机的散列值或者使用一个从没有被注册过的散列器。
        return False

    #判断两者的加密方式是否是同一个
    hasher_changed = hasher.algorithm != preferred.algorithm
    # 官方文档解释为,用户登录之后,如果他们的密码没有以首选的密码算法来储存,Django会自动将算法升级为首	选的那个。
    #个人理解,如果使用了不同的加密方法,把数据库中密码的加密方法改成和preferred的加密方法,使双方保持一致,这里只是进行判断,传递进来的setter()才是真正进行修改的方法。
    must_update = hasher_changed or preferred.must_update(encoded)
    #这段代码其实就是把明文密码按照hasher对应的加密方式进行加密,然后再和encoded进行比较。
    is_correct = hasher.verify(password, encoded)

    if not is_correct and not hasher_changed and must_update:
    	#我也不知道它在干什么
        hasher.harden_runtime(password, encoded)
	#如果传递了setter,并且密码是对的and两个加密方式不一样,需要修改,才掉setter()
    if setter and is_correct and must_update:
        setter(password)
    #如果密码输对了,没啥事,就直接返回了
    return is_correct


至此,check_password()函数执行完,一路返回到

class ModelBackend:
   

    def authenticate(self, request, username=None, password=None, **kwargs):
     #代码略。。。
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

接下来执行user_can_authenticate(),这个函数很好理解,不解释,直接看代码:

    def user_can_authenticate(self, user):
        """
        Reject users with is_active=False. Custom user models that don't have
        that attribute are allowed.
        拒绝is_active=False的用户。定制用户模型没有
        这个属性是允许的
        """
        is_active = getattr(user, 'is_active', None)
        return is_active or is_active is None

此函数执行完,一路返回另一个authenticate()函数:

def authenticate(request=None, **credentials):
    """
    If the given credentials are valid, return a User object.
    如果给定的凭据有效,则返回一个User对象。
    """
    for backend, backend_path in _get_backends(return_tuples=True):
        try:# backend_path = 'django.contrib.auth.backends.ModelBackend' backend = ModelBackend对象
            inspect.getcallargs(backend.authenticate, request, **credentials)
        except TypeError:
            # This backend doesn't accept these credentials as arguments. Try the next one.
            #此后端不接受这些凭据作为参数。试试下一个
            continue
        try:#credentials = {"username":13569784692,"password":123456abc}
            user = backend.authenticate(request, **credentials)
        except PermissionDenied:
            # This backend says to stop in our tracks - this user should not be allowed in at all.
            break
        if user is None:
            continue
        # Annotate the user object with the path of the backend.
        #一旦发现有一个backend校验通过,即返回User对象,那么将会停止下面backend的校验,并且将校验成功的backend绑定在该用户上放入session中,此后如果再次调用该方法,那么将会使用session中的backend进行校验,而不再遍历所有backend了。
        user.backend = backend_path
        return user

接下俩,不用多说,直接调用django内置的login()登录函数登录就行了:

class LoginView(View):
    def post(self, request):
        req_data = json.loads(request.body.decode())
        username = req_data.get('username')
        password = req_data.get('password')
        remember = req_data.get('remember')

        import re
        if re.match(r'^1[3-9]\d{9}$', username):
            User.USERNAME_FIELD = 'mobile'
        else:
            User.USERNAME_FIELD = 'username'

        if not all([username, password]):
            return JsonResponse({'code': 400,
                                 'message': '缺少必传参数'})
        user = authenticate(username=username, password=password)

        if user is None:
            return JsonResponse({'code': 400,
                                 'message': '用户名或密码错误'})

        # ② 保存登录用户的状态信息

        login(request, user)

        if not remember:
            # 如果未选择记住登录,浏览器关闭即失效
            request.session.set_expiry(0)

        # ③ 返回响应,登录成功
        response = JsonResponse({'code': 0,
                                 'message': 'OK'})

        # 设置 cookie 保存 username 用户名
        response.set_cookie('username',
                            user.username,
                            max_age=3600 * 24 * 14)

        return response

参考:https://www.cnblogs.com/wangwei916797941/p/7398976.html
参考:官方文档

标签:authenticate,return,django,源码,user,password,hasher,backend
来源: https://blog.csdn.net/qq_28829081/article/details/112978376

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有