用户认证
Cookie和Session
cookie是http请求header中的一个属性,是浏览器持久化存储数据的一种机制,因为网页无法直接访问浏览器所在主机的文件系统,所以网页要通过向cookie中存储键值对(Cookie中的键值对是程序员自定义的)的方式来把数据存储到浏览器所在主机的硬盘中。
cookie中保存的是键值对,最终还是要把这个键值对发送回服务器,因为服务器要使用cookie中的键值对来完成一些业务逻辑。
比如客户端请求中的cookie包含个人id,服务器就会通过这个个人id在数据库中查询出来这个个人id所关联的信息。
然而每个人都有一个个人id和个人id所关联的信息。这些数据是存储在数据库中的,服务器代码执行过程中这些数据会先临时保存到某个内存结构(相当于一个哈希表)中,后续需要对这些信息进行修改时,就修改内存结构(哈希表)中的内容,然后再将修改后的信息写入数据库。
这里服务器所涉及到的内存结构(哈希表)叫“会话”(session)。
- cookie是客户端存储数据的机制。
- session是服务器存储数据的机制(不是持久化存储)。
在Cookie键值对中存储的用户身份标识经常会理解成sessionId(会话Id)。
服务器会存储很多的session信息(会话信息)和sessionId(会话Id),每个用户都有一个session信息以及sessionId。服务器会通过类似于哈希表这样的数据结构来存储session信息和sessionId,sessionId就是key,session信息就是value,session信息里可以存储用户自身的各种信息(session信息里也是程序员自定义的键值对)。
login.html
登录页面
{% extends 'layout.html' %}
{% block 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>
{% endblock %}
{% block content %}
<div class="account">
<h2>用户登录</h2>
<div class="panel-body">
<form method="post">
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.errors.username.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.errors.password.0 }}</span>
</div>
<button type="submit" class="btn btn-primary center-block" style="width: 80px;">登录</button>
</form>
</div>
</div>
{% endblock %}
登录视图函数 account.py 登录、COOKIES、SESSION实现
# urls.py path('login/', account.login),
# 前端\django\myweb\blog\views\account.py
from django.shortcuts import render, redirect
from blog.models import Admin
from blog.utils.pagination import Pagination
from blog.utils.form import LoginForm
from blog.utils.form import LoginModelForm
from django.http import HttpResponse,HttpResponseRedirect
# 用户登录
def login(req):
# form
if req.method == "GET":
form = LoginForm()
context = {
"form": form,
}
return render(req, "login.html", context)
form = LoginForm(data=req.POST)
if form.is_valid():
# 数据提交验证成功,获取到输入的用户名和密码
# print(form.cleaned_data) # {'username': 'tang', 'password': 'Tang@1234'}
# form 里进行 clean_password 数据处理后返回密码存储的md5值去数据库进行比对
# print(form.cleaned_data) # {'username': 'tang', 'password': '469bb5f078060211a9e6a2a5459bda16'}
# 数据库数据查询比对
# admin_object = Admin.objects.filter(username=form.cleaned_data['username'],password=form.cleaned_data['password']).first()
# 如果数据字段与数据库表字段名一致
# admin_object = Admin.objects.filter(**form.cleaned_data).first()
user_object = Admin.objects.filter(username=form.cleaned_data['username']).first()
admin_object = Admin.objects.filter(username=form.cleaned_data['username'],password=form.cleaned_data['password']).first()
# if not Admin_object:
# form.add_error("username", "用户名或者密码错误!")
# form.add_error("password", "用户名或者密码错误!")
# context = {
# "form": form,
# }
# return render(req, "login.html", context)
if not user_object:
form.add_error("username", "用户名错误!")
context = {
"form": form,
}
return render(req, "login.html", context)
elif not admin_object:
form.add_error("password", "密码错误!")
context = {
"form": form,
}
return render(req, "login.html", context)
else:
# 用户名与密码正确
# 网站生成随机字符串;写到用户浏览器的cookie中;再写入到session中
""" COOKIES
# 例子1:不使用模板
response = HttpResponse("hello world")
response.set_cookie(key,value,max_age)
return response
# 例子2: 使用模板
response = render(request,'xxx.html', context)
response.set_cookie(key,value,max_age)
return response
# 例子3: 重定向
response = HttpResponseRedirect('/login/')
response.set_cookie(key,value,max_age)
return response
# 删除
response.delete_cookie('username')
# 获取 COOKIES值
# 方法一
request.COOKIES['username']
# 方法二
request.COOKIES.get('username','')
# 判断是否存在
request.COOKIES.has_key('cookie_name')
"""
""" session
# 设置session的值
request.session['key'] = value
request.session.set_expiry(time): 设置过期时间,0表示浏览器关闭则失效
# 获取session的值
request.session.get('key',None)
# 删除session的值, 如果key不存在会报错
del request.session['key']
# 判断一个key是否在session里
'fav_color' in request.session
# 获取所有session的key和value
request.session.keys()
request.session.values()
request.session.items()
# 清空
request.session.flush()
"""
response = HttpResponseRedirect('/admin_list/')
# 将username写入浏览器cookie,有效时间为360秒
response.set_cookie('id', admin_object.id, 3600)
response.set_cookie('name', admin_object.username, 3600)
req.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
req.session.set_expiry(1209600) # 默认 1209600 14天
return response
context = {
"form": form,
}
return render(req, "login.html", context)
中间件实现登录验证
中间件会在视图函数下的每个方法执行前调用,不用在每个方法下面进行判断,不然函数太多,过于繁琐。
def xxxxxx(req):
# 检查用户是否已登录,已登录可继续访问,未登录,跳转登录页面
# 用户发来请求,获取cookie随机字符串
# req.session["info"]
info = req.session.get("info")
if not info:
return redirect("/login/")
# 中间件示例
# 前端\django\myweb\myweb\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',
'blog.middleware.auth.M1',
'blog.middleware.auth.M2',
]
# 前端\django\myweb\blog\middleware\auth.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse, redirect
# 登录验证中间件
class AuthMiddleware(MiddlewareMixin):
"""中间件AuthMiddleware"""
def process_request(self, request):
# 排除不需要中间件验证的页面
if request.path_info == "/login/":
return
# 读取当彰访问的用户session的信息,如果能读到,说明已登陆过,就可以向后继续走
info_dict = request.session.get('info')
if info_dict:
print(info_dict)
return
# 如果没有登录过,重定向到登录页面
return redirect("/login/")
# 中间件测试示例
class M1(MiddlewareMixin):
"""中间件M1"""
def process_request(self, request):
# 如果方法中没有返回值(返回None)
# 如果有返回值 HttpResponse、render、redirect,则不再继续向后执行,把返回值返回给用户
print("M1 进来了")
return HttpResponse("无权访问")
def process_response(self, request, response):
print("M1 出去了")
return response
class M2(MiddlewareMixin):
"""中间件M2"""
def process_request(self, request):
print("M2 进来了")
def process_response(self, request, response):
print("M2 出去了")
return response
""" 访问任何页面都会通过中间件
M1 进来了
M2 进来了
M2 出去了
M1 出去了
[18/Apr/2024 11:18:30] "GET /login/ HTTP/1.1" 200 3666
"""
用户登录注销
# 前端\django\myweb\blog\views\account.py
def loginout(request):
""" 注销 """
# 清除当前session
request.session.clear()
return redirect("/login/")
layout.hmtl 可获取session数据 {{ request.session.info.name }}
图片验证码获取函数
pip3 install pillow
# 验证码获取函数 前端\django\myweb\blog\utils\check_code.py
import random, string
from PIL import Image, ImageDraw, ImageFont, ImageFilter
def check_code(width=120, height=30, char_length=5, font_file='D:\\git-python\\前端\\django\\myweb\\blog\\utils\\ttf\\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)) # 随机 ascii 码大写字母范围
return random.choice(string.ascii_letters) # 随机大小写字母
# return str(random.randint(0, 9)) # 随机0-9的数字字符串
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(18):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
# 写干扰圆圈
for i in range(18):
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__':
# 1. 直接打开
img,code = check_code()
img.show()
# 2. 写入文件
# img,code = check_code()
# with open('code.png','wb') as f:
# img.save(f,format='png')
# 3. 写入内存(Python3)
# from io import BytesIO
# stream = BytesIO()
# img.save(stream, 'png')
# stream.getvalue()
# 4. 写入内存(Python2)
# import StringIO
# stream = StringIO.StringIO()
# img.save(stream, 'png')
# stream.getvalue()
pass
验证中间件加正则匹配验证码刷新
# 前端\django\myweb\blog\middleware\auth.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse, redirect
import re
# 登录验证中间件
class AuthMiddleware(MiddlewareMixin):
"""中间件AuthMiddleware"""
def process_request(self, request):
# 假设我们想匹配以'/img/check_code/'开头的路径
path_info_regex = re.compile(r'^/img/check_code/')
# 使用match方法进行匹配
match = path_info_regex.match(request.path_info)
print(request.path_info)
if match:
return
# 排除不需要中间件验证的页面
if request.path_info in ["/login/"]:
return
# 读取当彰访问的用户session的信息,如果能读到,说明已登陆过,就可以向后继续走
info_dict = request.session.get('info')
if info_dict:
# print(info_dict)
return
# 如果没有登录过,重定向到登录页面
return redirect("/login/")
验证码视图函数
# # 前端\django\myweb\blog\views\account.py
# 图片验证码
def check_code_img(request, random):
"""生成图片验证码"""
# 调用pillow函数,生成图片
# from blog.utils.check_code import check_code
img, code_string = check_code(width=120, height=30, char_length=5, font_file='D:\\git-python\\前端\\django\\myweb\\blog\\utils\\ttf\\kumo.ttf', font_size=28)
# img.show()
# print(code_string)
# code_string 写入到自己的session中,以便于后续获取验证码再进行校验
request.session['image_code'] = code_string
# 设置session 超时时间为 60s
request.session.set_expiry(60)
# 写入内存(Python3)
# from io import BytesIO
stream = BytesIO()
img.save(stream, 'png')
stream.getvalue()
return HttpResponse(stream.getvalue())
改造登录页面
前端\django\myweb\blog\templates\login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>员工管理系统-登录</title>
{% load static %}
<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>
<div class="panel-body">
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.errors.username.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.errors.password.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.errors.code.0 }}</span>
</div>
<div class="col-xs-5">
<img id="image_code" src="/img/check_code/666" onclick="change_img()" title="点击换验证码">
</div>
</div>
</div>
<button type="submit" class="btn btn-primary center-block" style="width: 80px;">登录</button>
</form>
</div>
</div>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
<script>
function change_img() {
var img = document.getElementById("image_code");
img.src= '/img/check_code/'+(new Date()).getTime();
}
</script>
</body>
</html>
form 相关修改
# 前端\django\myweb\blog\utils\form.py
from django import forms
class BootStrap_check_code:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 循环ModelForm中的所有字段,给每个字段的插件设置
for _, field in self.fields.items():
# 字段中有属性,保留原来的属性,没有属性,才增加
if field.widget.attrs:
field.widget.attrs["class"] = "form-control"
# field.widget.attrs["style"] = "width: 300px;"
if "placeholder" in field.widget.attrs:
continue
else:
field.widget.attrs["placeholder"] = field.label
else:
field.widget.attrs = {
"class": "form-control",
# "style": "width: 300px;",
"placeholder": field.label
}
# 多继承
class BootStrapForm_check_code(BootStrap_check_code, forms.Form):
pass
# 前端\django\myweb\blog\utils\form.py
# 用户认证
# form方式
# class LoginForm(forms.Form):
class LoginForm(BootStrapForm_check_code):
username = forms.CharField(
label="用户名",
widget=forms.TextInput(attrs={"class": "form-control"}),
required=True,
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(attrs={"class": "form-control"}, render_value=True),
required=True,
)
code = forms.CharField(
label="验证码",
widget=forms.TextInput(attrs={"class": "form-control"}),
required=True,
)
# 获取用户提交的值
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)
完整 account.py
from django.shortcuts import render, redirect
from blog.models import Admin
from blog.utils.form import LoginForm
# from blog.utils.form import LoginModelForm
from django.http import HttpResponse,HttpResponseRedirect
from blog.utils.check_code import check_code
from io import BytesIO
# 用户登录
def login(request):
# form
if request.method == "GET":
form = LoginForm()
context = {
"form": form,
}
return render(request, "login.html", context)
form = LoginForm(data=request.POST)
if form.is_valid():
# 获取用户输入的验证码,使用pod获取,并剔除code,再进行下一步数据校验,数据库没有code数据。
user_input_code = form.cleaned_data.pop('code')
# 获取用户session内image_code值
image_code = request.session.get('image_code', "")
# 区分大小写
# if user_input_code == image_code:
# 不区分大小写
if user_input_code.upper() != image_code.upper():
form.add_error("code", "验证码错误!")
context = {
"form": form,
}
return render(request, "login.html", context)
# 数据提交验证成功,获取到输入的用户名和密码
# print(form.cleaned_data) # {'username': 'tang', 'password': 'Tang@1234'}
# form 里进行 clean_password 数据处理后返回密码存储的md5值去数据库进行比对
# print(form.cleaned_data) # {'username': 'tang', 'password': '469bb5f078060211a9e6a2a5459bda16'}
# 数据库数据查询比对
# admin_object = Admin.objects.filter(username=form.cleaned_data['username'],password=form.cleaned_data['password']).first()
# 如果数据字段与数据库表字段名一致
# admin_object = Admin.objects.filter(**form.cleaned_data).first()
user_object = Admin.objects.filter(username=form.cleaned_data['username']).first()
admin_object = Admin.objects.filter(username=form.cleaned_data['username'],password=form.cleaned_data['password']).first()
# if not Admin_object:
# form.add_error("username", "用户名或者密码错误!")
# form.add_error("password", "用户名或者密码错误!")
# context = {
# "form": form,
# }
# return render(request, "login.html", context)
if not user_object:
form.add_error("username", "用户名错误!")
context = {
"form": form,
}
return render(request, "login.html", context)
elif not admin_object:
form.add_error("password", "密码错误!")
context = {
"form": form,
}
return render(request, "login.html", context)
else:
# 用户名与密码正确
# 网站生成随机字符串;写到用户浏览器的cookie中;再写入到session中
""" COOKIES
# 例子1:不使用模板
response = HttpResponse("hello world")
response.set_cookie(key,value,max_age)
return response
# 例子2: 使用模板
response = render(request,'xxx.html', context)
response.set_cookie(key,value,max_age)
return response
# 例子3: 重定向
response = HttpResponseRedirect('/login/')
response.set_cookie(key,value,max_age)
return response
# 删除
response.delete_cookie('username')
# 获取 COOKIES值
# 方法一
request.COOKIES['username']
# 方法二
request.COOKIES.get('username','')
# 判断是否存在
request.COOKIES.has_key('cookie_name')
"""
""" session
# 设置session的值
request.session['key'] = value
request.session.set_expiry(time): 设置过期时间, 0表示浏览器关闭则失效
# 获取session的值
request.session.get('key', None)
# 判断一个key是否在session里
'fav_color' in request.session
# 获取所有session的key和value
request.session.keys()
request.session.values()
request.session.items()
# 清除所有session
request.session.clear() # 只删除session中值得部分
# 删除所有session
# request.session.flush() # 删除session中的整条记录
# 删除key为age的session
del request.session["age"]
"""
response = HttpResponseRedirect('/admin_list/')
# 将username写入浏览器cookie,有效时间为360秒
response.set_cookie('id', admin_object.id, 3600)
response.set_cookie('name', admin_object.username, 3600)
request.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
# request.session.set_expiry(1209600) # 默认 1209600 14天
request.session.set_expiry(60 * 60 * 24 * 7) # 7天
# 删除key为image_code的session,防止验证码复用
del request.session["image_code"]
return response
context = {
"form": form,
}
return render(request, "login.html", context)
# 用户登录注销
def loginout(request):
""" 注销 """
# 清除当前session
request.session.clear()
return redirect("/login/")
# 图片验证码
def check_code_img(request, random):
"""生成图片验证码"""
# 调用pillow函数,生成图片
# from blog.utils.check_code import check_code
img, code_string = check_code(width=120, height=30, char_length=5, font_file='D:\\git-python\\前端\\django\\myweb\\blog\\utils\\ttf\\kumo.ttf', font_size=28)
# img.show()
# print(code_string)
# code_string 写入到自己的session中,以便于后续获取验证码再进行校验
request.session['image_code'] = code_string
# 设置session 超时时间为 60s
request.session.set_expiry(60)
# 写入内存(Python3)
# from io import BytesIO
stream = BytesIO()
img.save(stream, 'png')
# stream.getvalue()
return HttpResponse(stream.getvalue())