django基于authenticate()函数的源码解析
作者:互联网
如果我们使用自身的一个账号和密码进行登录验证的话,不得不使用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