用户登录、认证、验证码、注销
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})