一. cookie和session
1.1 http无状态短连接
截止目前为止,我们的项目都是直接访问url的,没有使用用户登陆,此时浏览器与服务器之间都是通过短连接的形式来进行交互的,但是我没操作一次,都会重新建立一次短连接,如果是高并发的环境,会带来性能的瓶颈。
image.png
1.2 cookie和session
如下图所示:
当浏览器与服务器之间建立了一个连接后,就会将创建连接时候的信息保存下来,在浏览器端就是cookie,在server端就是session,然后只要连接不主动断开或不过期,就不用重复认证。
image.png
二. 中间件
2.1 登陆页面设计
登录成功后:
- cookie,随机字符串
- session,用户信息
在其他需要登录才能访问的页面中,都需要加入:
def index(request):
info = request.session.get("info")
if not info:
return redirect('/login/')
目标:在18个视图函数前面统一加入判断。
info = request.session.get("info")
if not info:
return redirect('/login/')
2.2 中间件的体验
中间件有点类似拦截器,在浏览器和服务器中间的一层,所有的操作都需要经过这一层。
有了中间件,我们就不需要在18个视图(后面可能增加更多)的视图函数加入判断了。
定义中间件
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class M1(MiddlewareMixin):
""" 中间件1 """
def process_request(self, request):
# 如果方法中没有返回值(返回None),继续向后走
# 如果有返回值 HttpResponse、render 、redirect
print("M1.process_request")
return HttpResponse("无权访问")
def process_response(self, request, response):
print("M1.process_response")
return response
class M2(MiddlewareMixin):
""" 中间件2 """
def process_request(self, request):
print("M2.process_request")
def process_response(self, request, response):
print("M2.process_response")
return response
应用中间件 setings.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.1 URL调整
# 登录
path('login/', account.login),
path('logout/', account.logout),
path('image/code/', account.image_code),
调整为如下,直接访问默认页面就进入了登陆页面:
3.2 后端功能实现
3.2.1 中间件实现登陆校验
auth.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse, redirect
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request):
# 0.排除那些不需要登录就能访问的页面
# request.path_info 获取当前用户请求的URL /login/
if request.path_info in ["/login/", "/image/code/"]:
return
# 1.读取当前访问的用户的session信息,如果能读到,说明已登陆过,就可以继续向后走。
info_dict = request.session.get("info")
# print(info_dict)
if info_dict:
return
# 2.没有登录过,重新回到登录页面
return redirect('/login/')
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.AuthMiddleware',
]
3.2.2 验证码功能实现
code.py
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
def check_code(width=120, height=30, char_length=5, font_file='Monaco.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 str(random.randint(0, 9))
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)
3.2.3 视图函数
account.py
from django.shortcuts import render, HttpResponse, redirect
from django import forms
from io import BytesIO
from app01.utils.code import check_code
from app01 import models
from app01.utils.bootstrap import BootStrapForm
from app01.utils.encrypt import md5
class LoginForm(BootStrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput,
required=True
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(render_value=True),
required=True
)
code = forms.CharField(
label="验证码",
widget=forms.TextInput,
required=True
)
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)
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': 'wupeiqi', 'password': '123',"code":123}
# {'username': 'wupeiqi', 'password': '5e5c3bad7eb35cba3638e145c830c35f',"code":xxx}
# 验证码的校验
user_input_code = form.cleaned_data.pop('code')
code = request.session.get('image_code', "")
if 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=xxx, password=xxx).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})
def image_code(request):
""" 生成图片验证码 """
# 调用pillow函数,生成图片
img, code_string = check_code()
# 写入到自己的session中(以便于后续获取验证码再进行校验)
request.session['image_code'] = code_string
# 给Session设置60s超时
request.session.set_expiry(60)
stream = BytesIO()
img.save(stream, 'png')
return HttpResponse(stream.getvalue())
def logout(request):
""" 注销 """
request.session.clear()
return redirect('/login/')
3.3 前端页面
3.3.1 login.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<style>
.account {
width: 400px;
border: 1px solid #dddddd;
border-radius: 5px;
box-shadow: 5px 5px 20px #aaa;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
padding: 20px 40px;
}
.account h2 {
margin-top: 10px;
text-align: center;
}
</style>
</head>
<body>
<div class="account">
<h2>用户登录</h2>
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.username.errors.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.password.errors.0 }}</span>
</div>
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
{{ form.code }}
<span style="color: red;">{{ form.code.errors.0 }}</span>
</div>
<div class="col-xs-5">
<img id="image_code" src="/image/code/" style="width: 125px;">
</div>
</div>
</div>
<input type="submit" value="登 录" class="btn btn-primary">
</form>
</div>
</body>
</html>
3.3.2 error.html
{% extends 'layout.html' %}
{% block content %}
<div class="container">
<div class="alert alert-danger" role="alert">{{ msg }}</div>
</div>
{% endblock %}
3.3.3 layout.html
之前我们把页面上登陆的用户写死了,现在需要获取到当前登陆的用户,然后从显示。
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<style>
.navbar {
border-radius: 0;
}
</style>
{% block css %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"> 员工管理系统 </a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/admin/list/">管理员账户</a></li>
<li><a href="/depart/list/">部门管理</a></li>
<li><a href="/user/list/">用户管理</a></li>
<li><a href="/pretty/list/">靓号管理</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<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>
</ul>
</div>
</div>
</nav>
<div>
{% block content %}{% endblock %}
</div>
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
{% block js %}{% endblock %}
</body>
</html>
四. 页面测试
直接访问: http://127.0.0.1:8000/
image.png验证码必须填写:
先验证验证码,后验证用户名和密码:
登陆成功页面:
网友评论