面试-Django实现登陆的原理

面试-Django实现登陆的原理,第1张

登陆

登陆的主要参数是:手机号/用户名,密码和记住我的选项

class LoginForm(forms.Form):
    # 手机号 和用户名 密码
    user_account = forms.CharField()
    password = forms.CharField(label='密码', max_length=20, min_length=6,
                               error_messages={"min_length": "密码长度要大于6",
                                               "max_length": "密码长度要小于20",
                                               "required": "密码不能为空"}
                               )
    # 勾选的字段
    remember_me = forms.BooleanField(required=False)

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(LoginForm, self).__init__(*args, **kwargs)

    def clean_user_account(self):
        user_info = self.cleaned_data.get('user_account')
        if not user_info:
            raise forms.ValidationError("用户账号不能为空")
        if not re.match(r'^1[3-9]\d{9}$', user_info) and (len(user_info) < 5 or len(user_info) > 20):
            raise forms.ValidationError('格式不正确,请重新输入')
        return user_info
        
    def clean(self):
        cleaned_data = super().clean()
        user_info = cleaned_data.get('user_account')
        passwd = cleaned_data.get('password')
        hold_login = cleaned_data.get('remember_me')
    # 2 查询数据
        user_queryset = Users.objects.filter(Q(mobile=user_info) | Q(username=user_info))  # 满足一个都可以

        if user_queryset:
            user = user_queryset.first()  # 获取第一个
            if user.check_password(passwd):
                if hold_login:  # 如果说勾选有值
                    self.request.session.set_expiry(constants.USER_SESSION_EXPIRES)#只是设置过期时间
                else:
                    self.request.session.set_expiry(None)#浏览器关闭就退出
                login(self.request, user)#真正的保存到数据库中
            else:
                raise forms.ValidationError('密码错误,请重新输入')
        else:
            raise forms.ValidationError("账号不存在,请重新输入")
            

主要流程是:

  1. 用户输入登陆需要的参数
  2. 后端通过post请求接收,进行数据校验
  3. 校验通过,查询数据库的账号密码,同时检查记住我选项,若勾选了就保存session的值
  4. 调用login()函数保存登陆状态

延伸的问题:

1、如何保证密码的保密性?

https://blog.csdn.net/qq_42039417/article/details/103309195 参考文章
首先是储存的安全,作为应用的服务端,我们的后台数据库中存储着所有用户的口令数据,怎样实现安全的存储方案就是我们所需要考虑的。


上面的代码是直接明文储存在服务区的数据库中。


可以看出,这里存储的 password 是直接从表单中获取的字符序列值,中间并没有任何加密处理的过程显然这很不安全,容易被社会工程渗透,而且一旦攻击者入侵了数据库,这些口令就都白给了,所以我们需要先对口令进行加密处理,再存储到数据库中。


摘要处理

  • 一个常用的处理方法就是,使用散列算法对口令进行摘要,存储得到的散列值,这样的话,即使攻击者爆破了数据库,能获取到的也只是口令的散列值,因为散列是一个单向的过程,所以不可能逆推得到口令,当要验证口令时,只需要用相同的散列算法对用户输入的口令进行摘要,把计算得到的散列值和数据库中存储的散列值进行对比,如果一致则认证通过。


  • 但这样也还不够安全,毕竟散列值的长度是固定的,说明散列值的范围是有限的。


    这样必然会出现碰撞情况,即多个不同字符序列的散列值却是一样的
    即便从散列值无法逆推得到完全正确的原口令,只需要找到一个跟原口令散列碰撞的字符序列,同样可以使用这个字符序列通过认证
    而且,有限代表可以遍历。


    如果彩虹表足够大,通过查询彩虹表,不难找到一个与散列值对应的序列
    所以在对口令进行摘要的过程,我们还需要加盐(salt)

撒盐

  • 原序列每一位比特的变化对散列结果的影响都是千差地别的,对撒了盐的口令取摘要,即便攻击者获取了撒盐口令的散列值,他不仅需要猜测口令,还需要猜测盐值,口令破解的难度呈指数剧增

这里可以直接调用 Django 内置哈希模块 django.contrib.auth.hashers 的口令生成函数 make_password

下面是经过改进的注册视图函数

views.py

from django.contrib.auth.hashers import make_password
from django.shortcuts import render
from Blog.models import User

def register(request):
    if request.session.get('is_login', None):
        request.session.flush()
    if request.method == 'POST':
        username = request.POST.get('username', '').strip()
        password = request.POST.get('password', '').strip()
        re_password = request.POST.get('re_password', '').strip()
        if password == re_password:
            try:
                User.objects.get(username=username)
            except User.DoesNotExist:
                encrypted_password = make_password(password, None, 'pbkdf2_sha256')
                new_user = User(username=username, password=encrypted_password)
                new_user.save()
                return render(request, 'register.html', {'mess': '用户注册成功'})
            else:
                return render(request, 'register.html', {'mess': '该用户已被注册'})
        else:
            return render(request, 'register.html', {'mess': '两次密码输入不一致'})
    return render(request, 'register.html')

make_password(password, salt=None, hasher=‘default’)
其中,第二个参数指定盐值,如果为 None 则随机生成盐值(建议随机)

这是一个设置盐值为 ‘set’ 的口令在数据库中存储的值,其使用的散列算法是 pbkdf2_sha256,在登录验证时,使用和 make_password 配套的 check_password 函数进行口令验证

views.py

from django.contrib.auth.hashers import check_password
from django.shortcuts import render, redirect
from Blog.models import User

def login(request):
    if request.session.get('is_login', None):  # 检查是否已登录
        return redirect('/index/')
    if request.method == 'POST':  # 如果有提交表单
        username = request.POST.get('username', '').strip()
        password = request.POST.get('password', '').strip()
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            return render(request, 'login.html', {'mess': '用户不存在'})
        else:
            if check_password(password, user.password):
                request.session['is_login'] = 'T'  # 标记已登录
                request.session['user_id'] = user.id
                request.session['username'] = username
                return redirect('/index/')
            else:
                return render(request, 'login.html', {'mess': '密码输入错误'})
    return render(request, 'login.html')

————————————————

原文链接:https://blog.csdn.net/qq_42039417/article/details/103309195

2、session和login的具体实现和作用是什么?

session的信息回持久化到数据库中,并且设置了过期时间,在过期时间内,Django内置的session会保存用户的信息,最直接的方法就是用户退出浏览器,再次登陆的时候右上角的用户名还是回存在的。


login 的作用,登录的过程(django),Django内置的login函数原理

  1. 查询用户
  2. login的逻辑
    先将用户的基本信息组成json,然后加密生成加密的session字符串
    随机生成一串长的字符,叫做sessionid
    将sessionid和session值绑定在一起保存到数据库中
    将sessionid写入到cookie中
    返回请求给浏览器
加盐是啥?

盐(Salt)

在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。


第三代密码
本来第二代密码设计方法已经很不错了,只要你密码设置得稍微复杂一点,就几乎没有被破解的可能性。


但是如果你的密码设置得不够复杂,被破解出来的可能性还是比较大的。


好事者收集常用的密码,然后对他们执行 MD5 或者 SHA1,原密码作为键,通过hash处理后的称为值,然后做成一个数据量非常庞大的数据字典,然后对泄露的数据库中的密码做对比,如果你的原始密码很不幸的被包含在这个数据字典中,那么花不了多长时间就能把你的原始密码匹配出来。


这个数据字典很容易收集,CSDN 泄露的那 600w 个密码,就是很好的原始素材。


其实就是hash碰撞,于是,第三代密码设计方法诞生,用户表中多了一个字段:

mysql> desc User;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| UserName | varchar(50) | NO   |     |         |       |
| Salt     | char(50)    | NO   |     |         |       |
| PwdHash  | char(32)    | NO   |     |         |       |
+----------+-------------+------+-----+---------+-------+

数据存储形式如下:

mysql> select * from User;
+----------+----------------------------+----------------------------------+
| UserName | Salt                       | PwdHash                          |
+----------+----------------------------+----------------------------------+
| lichao   | 1ck12b13k1jmjxrg1h0129h2lj | 6c22ef52be70e11b6f3bcf0f672c96ce |
| akasuna  | 1h029kh2lj11jmjxrg13k1c12b | 7128f587d88d6686974d6ef57c193628 |
+----------+----------------------------+----------------------------------+

Salt 可以是任意字母、数字、或是字母或数字的组合,但必须是随机产生的,每个用户的 Salt 都不一样,用户注册的时候,数据库中存入的不是明文密码,也不是简单的对明文密码进行散列,而是 MD5( 明文密码 + Salt),也就是说:

MD5('123' + '1ck12b13k1jmjxrg1h0129h2lj') = '6c22ef52be70e11b6f3bcf0f672c96ce'
MD5('456' + '1h029kh2lj11jmjxrg13k1c12b') = '7128f587d88d6686974d6ef57c193628'

当用户登陆的时候,同样用这种算法就行验证。


由于加了 Salt,即便数据库泄露了,但是由于密码都是加了 Salt 之后的散列,坏人们的数据字典已经无法直接匹配,明文密码被破解出来的概率也大大降低。


是不是加了 Salt 之后就绝对安全了呢?淡然没有!坏人们还是可以他们数据字典中的密码,加上我们泄露数据库中的 Salt,然后散列,然后再匹配。


但是由于我们的 Salt 是随机产生的,假如我们的用户数据表中有 30w 条数据,数据字典中有 600w 条数据,坏人们如果想要完全覆盖的坏,他们加上 Salt 后再散列的数据字典数据量就应该是 300000* 6000000 = 1800000000000,一万八千亿啊,干坏事的成本太高了吧。


但是如果只是想破解某个用户的密码的话,只需为这 600w 条数据加上 Salt,然后散列匹配。


可见 Salt 虽然大大提高了安全系数,但也并非绝对安全。


实际项目中,Salt 不一定要加在最前面或最后面,也可以插在中间嘛,也可以分开插入,也可以倒序,程序设计时可以灵活调整,都可以使破解的难度指数级增长。


登陆注册-保密性

1、密码加密后存储

简略写:

from django.contrib.auth.hashers import make_password,check_password
#存储的时候
user.password = make_password(password)
#解析的时候
booltype = check_password(password,user.password )

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/570291.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存