用户登录、认证、验证码、注销

1. 用户登录

什么是cookie和session?

  • 无状态 & 短链接
http://127.0.0.1:8000/admin/list/
https://127.0.0.1:8000/admin/list/
  • session(数据库、Redis、文件…)

1.1 继承关系

bootstrap.py

class BootStrap:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 循环ModelForm中的所有字段,给每个字段的插件设置
        for name, field in self.fields.items():
            # 字段中有属性,保留原有属性;没有属性,才增加
            if field.widget.attrs:
                field.widget.attrs["class"] = "form-control"
                field.widget.attrs["placeholder"] = "请输入内容"
            else:
                field.widget.attrs = {
                    'class': 'form-control',
                    'placeholder': "请输入内容"
                }

class BootStrapModelForm(BootStrap, forms.ModelForm):
    pass

class BootStrapForm(BootStrap, forms.Form):
    pass

1.2 实现用户登录

class LoginForm(BootStrapForm):
    username = forms.CharField(
        label='用户名',
        widget=forms.TextInput,
        required=True
    )
    password = forms.CharField(
        label='密码',
        widget=forms.PasswordInput(render_value=True),
        required=True # 必填/不能为空
    )
    def clean_password(self):
        pwd = self.cleaned_data.get("password")
        return md5(pwd)


# 与上方Form实现效果相同
# class LoginModelForm(forms.ModelForm):
#     class Meta:
#         model = models.Admin
#         fields = ['username', 'password']


""" 用户登录 """
def login(request):

    if request.method == "GET":
        form = LoginForm()
        return render(request,'login.html', {'form': form})
    
    form = LoginForm(data = request.POST)
    if form.is_valid():
        # 验证成功,获取到的用户名和密码
        # {'username': 'admin', 'password': '123456'}
        # print(form.cleaned_data)

        # 去数据库校验用户名和密码是否正确,获取用户对象、None
        # admin_object = models.Admin.objects.filter(username=form.cleaned_data['username'], password=form.cleaned_data['password']).first()
        admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
        if not admin_object:
            form.add_error('password', '用户名或密码错误')
            # form.add_error('username', '用户名或密码错误')
            return render(request,'login.html', {'form': form})

        # 用户名和密码验证正确
        # 网站要生成随机字符串;给用户浏览器的cookie中;再写入到session中
        request.session['info'] = {'id': admin_object.id, 'name': admin_object.username}
        return redirect('/admin/list/')
    return render(request,'login.html', {'form': form})

2. 请求地址进行用户信息验证

登录成功后:

  • cookie,随机字符串
  • session,用户信息

在其他需要登录才能访问的页面中,都需要加入

# 检查用户是否已登录,已登录,继续走下去;未登录,跳转回登录页面
# 用户发来请求,获取cookie中的随机字符串,拿着随机字符串看看session中是否存在
# request.seesion["info"]
info = request.session.get("info")
if not info:
    return redirect('/login/')

目标:在N多个视图函数前面统一加入用户认证判断

info = request.session.get("info")
if not info:
    return redirect('/login/')

3. 中间件

3.1 体验

  • 定义中间件
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

""" 中间件1 """
class M1(MiddlewareMixin):
    def process_request(self, request):

        # 如果方法中没有返回值(则直接返回None),继续向后走
        # 如果有返回值 HttpResponse、render、redirect
        print("M1.进来了")
        return HttpResponse("无权访问")

    def process_response(self, request, response):
        print("M1.离开了")
        return response

""" 中间件2 """
class M2(MiddlewareMixin):
    def process_request(self, request):
        print("M2.进来了")


    def process_response(self, request, response):
        print("M2.离开了")
        return response
  • 应用中间件 settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'app01.middleware.auth.M1',
    'app01.middleware.auth.M2',
]
  • 在中间件中,如果process_request方法有返回值
# 如果方法中没有返回值(则直接返回None),继续向后走
# 如果有返回值 HttpResponse、render、redirect,则返回后结束

3.2 中间件实现登录校验

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect

""" 用户登录校验 """
class AuthMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 0. 排除那些不需要登录就能访问的页面
        # 获取当前用户请求的url
        if request.path_info == "/login/":
            return

        # 1. 读取当前访问的用户的session信息,如果能读到说明已登陆过,就可以继续向后走
        info_dict = request.session.get('info')
        if info_dict:
            return
        # 2. 没有登陆过,未获取到session信息,跳转回到登录页面
        # return HttpResponse('请先登录')
        return redirect('/login/')
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'app01.middleware.auth.AuthMiddleware',
]

导航栏右侧显示当前登录的用户名

  • layout.html
<li class="dropdown">
  <a
    href="#"
    class="dropdown-toggle"
    data-toggle="dropdown"
    role="button"
    aria-haspopup="true"
    aria-expanded="false"
    >{{ request.session.info.name }} <span class="caret"></span
  ></a>
  <ul class="dropdown-menu">
    <li><a href="#">个人资料</a></li>
    <li><a href="#">通知中心</a></li>
    <li role="separator" class="divider"></li>
    <li><a href="/logout/">注销</a></li>
  </ul>
</li>

4. 用户注销/退出登录

""" 退出登录/注销 """
def logout(request):
    # 清除session
    request.session.clear()
    
    return redirect('/login/')

5. 登录图片验证码

5.1 生成验证码图片

pip install pillow

示例

from PIL import Image,ImageDraw,ImageFont

# 生成一张图片
img = Image.new(mode='RGB', size=(120,30), color=(255,255,255))
# 创建一支画笔
draw = ImageDraw.Draw(img, mode='RGB')
# 使用特殊字体文件
font = ImageFont.truetype("Monaco.ttf", 28)
# 使用画笔绘制文本
draw.text([0, 0], '郭梦茹', 'red', font=font)

# 将生成的图片保存至本地
with open('code.png', 'wb') as f:
    img.save(f, format='png')

生成验证码

from PIL import Image,ImageDraw,ImageFont
import random
from PIL import ImageFilter

def check_code(width=120,height=30,char_length=5,font_file='kumo.ttf',font_size=28):
    code = []
    img = Image.new(mode='RGB',size=(width,height),color=(255,255,255))
    draw = ImageDraw.Draw(img,mode='RGB')

    def rndChar():
        """
        生成随机字母
        :return:
        """
        return chr(random.randint(65,90))


    def rndColor():
        """
        生成随机颜色
        :return:
        """
        return (random.randint(0,255),random.randint(10,255),random.randint(64,255))

    # 写文字
    font = ImageFont.truetype(font_file,font_size)
    for i in range(char_length):
        char = rndChar()
        code.append(char)
        h = random.randint(0,4)
        draw.text([i*width/char_length,h],char,font=font,fill=rndColor())

    # 写干扰点
    for i in range(40):
        draw.point([random.randint(0,width),random.randint(0,height)],fill=rndColor())


    # 写干扰圆圈
    for i in range(40):
        draw.point([random.randint(0,width),random.randint(0,height)],fill=rndColor())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())


    # 画干扰线
    for i in range(5):
        x1 = random.randint(0, width)
        y1 = random.randint(0, height)
        x2 = random.randint(0, width)
        y2 = random.randint(0, height)

        draw.line((x1, y1, x2, y2), fill=rndColor())

    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
    return img,''.join(code)





if __name__ == '__main__':
    image,code = check_code()
    image.show()
    print(code)

    with open('code.png','wb') as f:
        image.save(f, format='png')
""" 生成图片验证码 """
def image_code(request):

    # 调用pillow函数,生成图片验证码
    img, code_str = check_code()

    # 将验证码内容写入到session中,以便于后续获取验证码再进行校验
    request.session['image_code'] = code_str
    # 给session设置60秒超时
    request.session.set_expiry(60)

    # 将img写入内存中
    stream = BytesIO()
    img.save(stream, 'png')

    return HttpResponse(stream.getvalue())

5.2 校验验证码

""" 用户登录 """
def login(request):

    if request.method == "GET":
        form = LoginForm()
        return render(request,'login.html', {'form': form})
    
    form = LoginForm(data = request.POST)
    if form.is_valid():
        # 验证成功,获取到的用户名和密码
        # {'username': 'admin', 'password': '123456', 'code': '1234'}
        # print(form.cleaned_data)

        # 数据库中没有验证码字段,直接从cleaned_data中剔取出该字段,即cleaned_data中仅剩用户名和密码
        user_input_code = form.cleaned_data.pop('code')
        code = request.session.get('image_code', "")
        if code == "":
            form.add_error("code", "验证码已过期")
            return render(request,'login.html', {'form': form})
        elif code.upper() != user_input_code.upper():
            form.add_error("code", "验证码错误")
            return render(request,'login.html', {'form': form})


        # 去数据库校验用户名和密码是否正确,获取用户对象、None
        # admin_object = models.Admin.objects.filter(username=form.cleaned_data['username'], password=form.cleaned_data['password']).first()
        admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
        if not admin_object:
            form.add_error('password', '用户名或密码错误')
            # form.add_error('username', '用户名或密码错误')
            return render(request,'login.html', {'form': form})

        # 用户名和密码验证正确
        # 网站要生成随机字符串;给用户浏览器的cookie中;再写入到session中
        request.session['info'] = {'id': admin_object.id, 'name': admin_object.username}
        # 设置session过期时间为7天
        request.session.set_expiry(60*60*24*7)
        return redirect('/admin/list/')
    return render(request,'login.html', {'form': form})

评论