Django 登录及验证码案例

Django+前端\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/")

# 中间件测试示例
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

Django+前端\django\myweb\myweb\settings.py

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',  # 放在最上面,重写了response
    '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.AuthMiddleware',
    # 'blog.middleware.auth.M1',
    # 'blog.middleware.auth.M2',
    'django.middleware.cache.FetchFromCacheMiddleware'    # 放在最下面,重写了request
]

Django+前端\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+前端\django\myweb\blog\utils\password.py

import string
import re

def check_fips_password_complexity(password):

    # 验证密码长度
    if len(password) < 8:
        return "密码长度不合格,不低于8位"

    # 定义字符集
    uppercase_letters = string.ascii_uppercase
    lowercase_letters = string.ascii_lowercase
    digits = string.digits
    special_chars = string.punctuation.replace("?", "")  # 假设 ? 是不允许的特殊字符

    # 检查各个字符集是否至少出现一次
    if not any(char in uppercase_letters for char in password):
        return "密码必须包含至少一个大写字母"
    if not any(char in lowercase_letters for char in password):
        return "密码必须包含至少一个小写字母"
    if not any(char in digits for char in password):
        return "密码必须包含至少一个数字"
    if not any(char in special_chars for char in password):
        return "密码必须包含至少一个特殊字符"

    # 检查密码是否包含不允许的字符
    if re.search(r'[\s\x00]', password):
        return "密码包含不允许的字符(空格、中止字符等)"

    # print("密码符合FIPS复杂度要求")
    return True

# 使用示例
if __name__ == "__main__":
    password = "Example1="
    str = check_fips_password_complexity(password)
    print(str)

Django+前端\django\myweb\blog\views\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+前端\\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+前端\django\myweb\myweb\urls.py

urlpatterns = [
    # 图片验证码
    path('img/check_code/', account.check_code_img),
    re_path(r'img/check_code/(?P<random>\d+)', account.check_code_img),    # 点击刷新验证码
]

Django+前端\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>