一个月学通Python(十九):Cookie和Session是什么(Web开发)

news2025/6/6 20:12:58

专栏介绍

结合自身经验和内部资料总结的Python教程,每天3章,1个月就能全方位的完成Python的学习并进行实战开发,学完了定能成为大佬!加油吧!卷起来!

全部文章请访问专栏:《Python全栈教程(0基础)》


文章目录

    • 专栏介绍
    • Cookie和Session
      • 用户登录的准备工作
      • 实现用户跟踪
      • Django框架对session的支持
      • 实现用户登录验证
      • 在视图函数中读写cookie
      • Cookie的替代品


Cookie和Session

我们继续来完成上一章节中的项目,实现“用户登录”的功能,并限制只有登录的用户才能投票。

用户登录的准备工作

我们先为实现用户登录做一些准备工作。

  1. 创建用户模型。之前我们讲解过如果通过Django的ORM实现从二维表到模型的转换(反向工程),这次我们尝试把模型变成二维表(正向工程)。

    class User(models.Model):
        """用户"""
        no = models.AutoField(primary_key=True, verbose_name='编号')
        username = models.CharField(max_length=20, unique=True, verbose_name='用户名')
        password = models.CharField(max_length=32, verbose_name='密码')
        tel = models.CharField(max_length=20, verbose_name='手机号')
        reg_date = models.DateTimeField(auto_now_add=True, verbose_name='注册时间')
        last_visit = models.DateTimeField(null=True, verbose_name='最后登录时间')
    
        class Meta:
            db_table = 'tb_user'
            verbose_name = '用户'
            verbose_name_plural = '用户'
    
  2. 使用下面的命令生成迁移文件并执行迁移,将User模型直接变成关系型数据库中的二维表tb_user

    python manage.py makemigrations polls
    python manage.py migrate polls
    
  3. 用下面的SQL语句直接插入两条测试数据,通常不能将用户的密码直接保存在数据库中,因此我们将用户密码处理成对应的MD5摘要。MD5消息摘要算法是一种被广泛使用的密码哈希函数(散列函数),可以产生出一个128位(比特)的哈希值(散列值),用于确保信息传输完整一致。在使用哈希值时,通常会将哈希值表示为16进制字符串,因此128位的MD5摘要通常表示为32个十六进制符号。

    insert into `tb_user`
        (`username`, `password`, `tel`, `reg_date`)
    values
        ('wangdachui', '1c63129ae9db9c60c3e8aa94d3e00495', '13122334455', now()),
        ('hellokitty', 'c6f8cf68e5f68b0aa4680e089ee4742c', '13890006789', now());
    

    说明:上面创建的两个用户wangdachuihellokitty密码分别是1qaz2wsxAbc123!!

  4. 我们在应用下增加一个名为utils.py的模块用来保存需要使用的工具函数。Python标准库中的hashlib模块封装了常用的哈希算法,包括:MD5、SHA1、SHA256等。下面是使用hashlib中的md5类将字符串处理成MD5摘要的函数如下所示。

    import hashlib
    
    
    def gen_md5_digest(content):
        return hashlib.md5(content.encode()).hexdigest()
    
  5. 编写用户登录的视图函数和模板页。

    添加渲染登录页面的视图函数:

    def login(request: HttpRequest) -> HttpResponse:
        hint = ''
        return render(request, 'login.html', {'hint': hint})
    

    增加login.html模板页:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>用户登录</title>
        <style>
            #container {
                width: 520px;
                margin: 10px auto;
            }
            .input {
                margin: 20px 0;
                width: 460px;
                height: 40px;
            }
            .input>label {
                display: inline-block;
                width: 140px;
                text-align: right;
            }
            .input>img {
                width: 150px;
                vertical-align: middle;
            }
            input[name=captcha] {
                vertical-align: middle;
            }
            form+div {
                margin-top: 20px;
            }
            form+div>a {
                text-decoration: none;
                color: darkcyan;
                font-size: 1.2em;
            }
            .button {
                width: 500px;
                text-align: center;
                margin-top: 20px;
            }
            .hint {
                color: red;
                font-size: 12px;
            }
        </style>
    </head>
    <body>
        <div id="container">
            <h1>用户登录</h1>
            <hr>
            <p class="hint">{{ hint }}</p>
            <form action="/login/" method="post">
                {% csrf_token %}
                <fieldset>
                    <legend>用户信息</legend>
                    <div class="input">
                        <label>用户名:</label>
                        <input type="text" name="username">
                    </div>
                    <div class="input">
                        <label>密码:</label>
                        <input type="password" name="password">
                    </div>
                    <div class="input">
                        <label>验证码:</label>
                        <input type="text" name="captcha">
                        <img id="code" src="/captcha/" alt="" width="150" height="40">
                    </div>
                </fieldset>
                <div class="button">
                    <input type="submit" value="登录">
                    <input type="reset" value="重置">
                </div>
            </form>
            <div>
                <a href="/">返回首页</a>
                <a href="/register/">注册新用户</a>
            </div>
        </div>
    </body>
    </html>
    

    注意,在上面的表单中,我们使用了模板指令{% csrf_token %}为表单添加一个隐藏域(大家可以在浏览器中显示网页源代码就可以看到这个指令生成的type属性为hiddeninput标签),它的作用是在表单中生成一个随机令牌(token)来防范跨站请求伪造(简称为CSRF),这也是Django在提交表单时的硬性要求。如果我们的表单中没有这样的令牌,那么提交表单时,Django框架会产生一个响应状态码为403的响应(禁止访问),除非我们设置了免除CSRF令牌。下图是一个关于CSRF简单生动的例子。
    在这里插入图片描述


接下来,我们可以编写提供验证码和实现用户登录的视图函数,在此之前,我们先说说一个Web应用实现用户跟踪的方式以及Django框架对实现用户跟踪所提供的支持。对一个Web应用来说,用户登录成功后必然要让服务器能够记住该用户已经登录,这样服务器才能为这个用户提供更好的服务,而且上面说到的CSRF也是通过钓鱼网站来套取用户登录信息进行恶意操作的攻击手段,这些都是以用户跟踪技术为基础的。在理解了这些背景知识后,我们就清楚用户登录时到底需要执行哪些操作。

实现用户跟踪

如今,一个网站如果不通过某种方式记住你是谁以及你之前在网站的活动情况,失去的就是网站的可用性和便利性,继而很有可能导致网站用户的流式,所以记住一个用户(更专业的说法叫用户跟踪)对绝大多数Web应用来说都是必需的功能。

在服务器端,我们想记住一个用户最简单的办法就是创建一个对象,通过这个对象就可以把用户相关的信息都保存起来,这个对象就是我们常说的session(用户会话对象)。那么问题来了,HTTP本身是一个无连接(每次请求和响应的过程中,服务器一旦完成对客户端请求的响应之后就断开连接)、无状态(客户端再次发起对服务器的请求时,服务器无法得知这个客户端之前的任何信息)的协议,即便服务器通过session对象保留了用户数据,还得通过某种方式来确定当前的请求与之前保存过的哪一个session是有关联的。相信很多人都能想到,我们可以给每个session对象分配一个全局唯一的标识符来识别session对象,我们姑且称之为sessionid,每次客户端发起请求时,只要携带上这个sessionid,就有办法找到与之对应的session对象,从而实现在两次请求之间记住该用户的信息,也就是我们之前说的用户跟踪。

要让客户端记住并在每次请求时带上sessionid又有以下几种做法:

  1. URL重写。所谓URL重写就是在URL中携带sessionid,例如:http://www.example.com/index.html?sessionid=123456,服务器通过获取sessionid参数的值来取到与之对应的session对象。

  2. 隐藏域(隐式表单域)。在提交表单的时候,可以通过在表单中设置隐藏域向服务器发送额外的数据。例如:<input type="hidden" name="sessionid" value="123456">

  3. 本地存储。现在的浏览器都支持多种本地存储方案,包括:cookie、localStorage、sessionStorage、IndexedDB等。在这些方案中,cookie是历史最为悠久也是被诟病得最多的一种方案,也是我们接下来首先为大家讲解的一种方案。简单的说,cookie是一种以键值对方式保存在浏览器临时文件中的数据,每次请求时,请求头中会携带本站点的cookie到服务器,那么只要将sessionid写入cookie,下次请求时服务器只要读取请求头中的cookie就能够获得这个sessionid,如下图所示。
    在这里插入图片描述


在HTML5时代要,除了cookie,还可以使用新的本地存储API来保存数据,就是刚才提到的localStorage、sessionStorage、IndexedDB等技术,如下图所示。

在这里插入图片描述


总结一下,要实现用户跟踪,服务器端可以为每个用户会话创建一个session对象并将session对象的ID写入到浏览器的cookie中;用户下次请求服务器时,浏览器会在HTTP请求头中携带该网站保存的cookie信息,这样服务器就可以从cookie中找到session对象的ID并根据此ID获取到之前创建的session对象;由于session对象可以用键值对的方式保存用户数据,这样之前保存在session对象中的信息可以悉数取出,服务器也可以根据这些信息判定用户身份和了解用户偏好,为用户提供更好的个性化服务。

Django框架对session的支持

在创建Django项目时,默认的配置文件settings.py文件中已经激活了一个名为SessionMiddleware的中间件(关于中间件的知识我们在后面的章节做详细讲解,这里只需要知道它的存在即可),因为这个中间件的存在,我们可以直接通过请求对象的session属性来操作会话对象。前面我们说过,session属性是一个像字典一样可以读写数据的容器对象,因此我们可以使用“键值对”的方式来保留用户数据。与此同时,SessionMiddleware中间件还封装了对cookie的操作,在cookie中保存了sessionid,这一点我们在上面已经提到过了。

在默认情况下,Django将session的数据序列化后保存在关系型数据库中,在Django 1.6以后的版本中,默认的序列化数据的方式是JSON序列化,而在此之前一直使用Pickle序列化。JSON序列化和Pickle序列化的差别在于前者将对象序列化为字符串(字符形式),而后者将对象序列化为字节串(二进制形式),因为安全方面的原因,JSON序列化成为了目前Django框架默认序列化数据的方式,这就要求在我们保存在session中的数据必须是能够JSON序列化的,否则就会引发异常。还有一点需要说明的是,使用关系型数据库保存session中的数据在大多数时候并不是最好的选择,因为数据库可能会承受巨大的压力而成为系统性能的瓶颈,在后面的章节中我们会告诉大家如何将session保存到缓存服务中以提升系统的性能。

实现用户登录验证

首先,我们在刚才的polls/utils.py文件中编写生成随机验证码的函数gen_random_code,内容如下所示。

import random

ALL_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'


def gen_random_code(length=4):
    return ''.join(random.choices(ALL_CHARS, k=length))

编写生成验证码图片的类Captcha

"""
图片验证码
"""
import os
import random
from io import BytesIO

from PIL import Image
from PIL import ImageFilter
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype


class Bezier:
    """贝塞尔曲线"""

    def __init__(self):
        self.tsequence = tuple([t / 20.0 for t in range(21)])
        self.beziers = {}

    def make_bezier(self, n):
        """绘制贝塞尔曲线"""
        try:
            return self.beziers[n]
        except KeyError:
            combinations = pascal_row(n - 1)
            result = []
            for t in self.tsequence:
                tpowers = (t ** i for i in range(n))
                upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                coefs = [c * a * b for c, a, b in zip(combinations,
                                                      tpowers, upowers)]
                result.append(coefs)
            self.beziers[n] = result
            return result


class Captcha:
    """验证码"""

    def __init__(self, width, height, fonts=None, color=None):
        self._image = None
        self._fonts = fonts if fonts else \
            [os.path.join(os.path.dirname(__file__), 'fonts', font)
             for font in ['Arial.ttf', 'Georgia.ttf', 'Action.ttf']]
        self._color = color if color else random_color(0, 200, random.randint(220, 255))
        self._width, self._height = width, height

    @classmethod
    def instance(cls, width=200, height=75):
        """用于获取Captcha对象的类方法"""
        prop_name = f'_instance_{width}_{height}'
        if not hasattr(cls, prop_name):
            setattr(cls, prop_name, cls(width, height))
        return getattr(cls, prop_name)

    def _background(self):
        """绘制背景"""
        Draw(self._image).rectangle([(0, 0), self._image.size],
                                    fill=random_color(230, 255))

    def _smooth(self):
        """平滑图像"""
        return self._image.filter(ImageFilter.SMOOTH)

    def _curve(self, width=4, number=6, color=None):
        """绘制曲线"""
        dx, height = self._image.size
        dx /= number
        path = [(dx * i, random.randint(0, height))
                for i in range(1, number)]
        bcoefs = Bezier().make_bezier(number - 1)
        points = []
        for coefs in bcoefs:
            points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
                                for ps in zip(*path)))
        Draw(self._image).line(points, fill=color if color else self._color, width=width)

    def _noise(self, number=50, level=2, color=None):
        """绘制扰码"""
        width, height = self._image.size
        dx, dy = width / 10, height / 10
        width, height = width - dx, height - dy
        draw = Draw(self._image)
        for i in range(number):
            x = int(random.uniform(dx, width))
            y = int(random.uniform(dy, height))
            draw.line(((x, y), (x + level, y)),
                      fill=color if color else self._color, width=level)

    def _text(self, captcha_text, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
        """绘制文本"""
        color = color if color else self._color
        fonts = tuple([truetype(name, size)
                       for name in fonts
                       for size in font_sizes or (65, 70, 75)])
        draw = Draw(self._image)
        char_images = []
        for c in captcha_text:
            font = random.choice(fonts)
            c_width, c_height = draw.textsize(c, font=font)
            char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
            char_draw = Draw(char_image)
            char_draw.text((0, 0), c, font=font, fill=color)
            char_image = char_image.crop(char_image.getbbox())
            for drawing in drawings:
                d = getattr(self, drawing)
                char_image = d(char_image)
            char_images.append(char_image)
        width, height = self._image.size
        offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                  for i in char_images[:-1]) -
                      char_images[-1].size[0]) / 2)
        for char_image in char_images:
            c_width, c_height = char_image.size
            mask = char_image.convert('L').point(lambda i: i * 1.97)
            self._image.paste(char_image,
                              (offset, int((height - c_height) / 2)),
                              mask)
            offset += int(c_width * squeeze_factor)

    @staticmethod
    def _warp(image, dx_factor=0.3, dy_factor=0.3):
        """图像扭曲"""
        width, height = image.size
        dx = width * dx_factor
        dy = height * dy_factor
        x1 = int(random.uniform(-dx, dx))
        y1 = int(random.uniform(-dy, dy))
        x2 = int(random.uniform(-dx, dx))
        y2 = int(random.uniform(-dy, dy))
        warp_image = Image.new(
            'RGB',
            (width + abs(x1) + abs(x2), height + abs(y1) + abs(y2)))
        warp_image.paste(image, (abs(x1), abs(y1)))
        width2, height2 = warp_image.size
        return warp_image.transform(
            (width, height),
            Image.QUAD,
            (x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1))

    @staticmethod
    def _offset(image, dx_factor=0.1, dy_factor=0.2):
        """图像偏移"""
        width, height = image.size
        dx = int(random.random() * width * dx_factor)
        dy = int(random.random() * height * dy_factor)
        offset_image = Image.new('RGB', (width + dx, height + dy))
        offset_image.paste(image, (dx, dy))
        return offset_image

    @staticmethod
    def _rotate(image, angle=25):
        """图像旋转"""
        return image.rotate(random.uniform(-angle, angle),
                            Image.BILINEAR, expand=1)

    def generate(self, captcha_text='', fmt='PNG'):
        """生成验证码(文字和图片)
        :param captcha_text: 验证码文字
        :param fmt: 生成的验证码图片格式
        :return: 验证码图片的二进制数据
        """
        self._image = Image.new('RGB', (self._width, self._height), (255, 255, 255))
        self._background()
        self._text(captcha_text, self._fonts,
                   drawings=['_warp', '_rotate', '_offset'])
        self._curve()
        self._noise()
        self._smooth()
        image_bytes = BytesIO()
        self._image.save(image_bytes, format=fmt)
        return image_bytes.getvalue()


def pascal_row(n=0):
    """生成毕达哥拉斯三角形(杨辉三角)"""
    result = [1]
    x, numerator = 1, n
    for denominator in range(1, n // 2 + 1):
        x *= numerator
        x /= denominator
        result.append(x)
        numerator -= 1
    if n & 1 == 0:
        result.extend(reversed(result[:-1]))
    else:
        result.extend(reversed(result))
    return result


def random_color(start=0, end=255, opacity=255):
    """获得随机颜色"""
    red = random.randint(start, end)
    green = random.randint(start, end)
    blue = random.randint(start, end)
    if opacity is None:
        return red, green, blue
    return red, green, blue, opacity

说明:上面的代码中用到了三个字体文件,字体文件位于polls/fonts目录下,大家可以自行添加字体文件,但是需要注意字体文件的文件名跟上面代码的第45行保持一致。

接下来,我们先完成提供验证码的视图函数。

def get_captcha(request: HttpRequest) -> HttpResponse:
    """验证码"""
    captcha_text = gen_random_code()
    request.session['captcha'] = captcha_text
    image_data = Captcha.instance().generate(captcha_text)
    return HttpResponse(image_data, content_type='image/png')

注意上面代码中的第4行,我们将随机生成的验证码字符串保存到session中,稍后用户登录时,我们要将保存在session中的验证码字符串和用户输入的验证码字符串进行比对,如果用户输入了正确的验证码才能够执行后续的登录流程,代码如下所示。

def login(request: HttpRequest) -> HttpResponse:
    hint = ''
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username and password:
            password = gen_md5_digest(password)
            user = User.objects.filter(username=username, password=password).first()
            if user:
                request.session['userid'] = user.no
                request.session['username'] = user.username
                return redirect('/')
            else:
                hint = '用户名或密码错误'
        else:
            hint = '请输入有效的用户名和密码'
    return render(request, 'login.html', {'hint': hint})

说明:上面的代码没有对用户名和密码没有进行验证,实际项目中建议使用正则表达式验证用户输入信息,否则有可能将无效的数据交给数据库进行处理或者造成其他安全方面的隐患。

上面的代码中,我们设定了登录成功后会在session中保存用户的编号(userid)和用户名(username),页面会重定向到首页。接下来我们可以稍微对首页的代码进行调整,在页面的右上角显示出登录用户的用户名。我们将这段代码单独写成了一个名为header.html的HTML文件,首页中可以通过在<body>标签中添加{% include 'header.html' %}来包含这个页面,代码如下所示。

<div class="user">
    {% if request.session.userid %}
    <span>{{ request.session.username }}</span>
    <a href="/logout">注销</a>
    {% else %}
    <a href="/login">登录</a>&nbsp;&nbsp;
    {% endif %}
    <a href="/register">注册</a>
</div>

如果用户没有登录,页面会显示登录和注册的超链接;而用户登录成功后,页面上会显示用户名和注销的链接,注销链接对应的视图函数如下所示,URL的映射与之前讲过的类似,不再赘述。

def logout(request):
    """注销"""
    request.session.flush()
    return redirect('/')

上面的代码通过session对象flush方法来销毁session,一方面清除了服务器上session对象保存的用户数据,一方面将保存在浏览器cookie中的sessionid删除掉,稍后我们会对如何读写cookie的操作加以说明。

我们可以通过项目使用的数据库中名为django_session 的表来找到所有的session,该表的结构如下所示:

session_keysession_dataexpire_date
c9g2gt5cxo0k2evykgpejhic5ae7bfplMmI4YzViYjJhOGMyMDJkY2M5Yzg3…2019-05-25 23:16:13.898522

其中,第1列就是浏览器cookie中保存的sessionid;第2列是经过BASE64编码后的session中的数据,如果使用Python的base64对其进行解码,解码的过程和结果如下所示。

import base64

base64.b64decode('MmI4YzViYjJhOGMyMDJkY2M5Yzg3ZWIyZGViZmUzYmYxNzdlNDdmZjp7ImNhcHRjaGEiOiJzS3d0Iiwibm8iOjEsInVzZXJuYW1lIjoiamFja2ZydWVkIn0=')

第3列是session的过期时间,session过期后浏览器保存的cookie中的sessionid就会失效,但是数据库中的这条对应的记录仍然会存在,如果想清除过期的数据,可以使用下面的命令。

python manage.py clearsessions

Django框架默认的session过期时间为两周(1209600秒),如果想修改这个时间,可以在项目的配置文件中添加如下所示的代码。

# 配置会话的超时时间为1天(86400秒)
SESSION_COOKIE_AGE = 86400

有很多对安全性要求较高的应用都必须在关闭浏览器窗口时让会话过期,不再保留用户的任何信息,如果希望在关闭浏览器窗口时就让会话过期(cookie中的sessionid失效),可以加入如下所示的配置。

# 设置为True在关闭浏览器窗口时session就过期
SESSION_EXPIRE_AT_BROWSER_CLOSE = True

如果不希望将session的数据保存在数据库中,可以将其放入缓存中,对应的配置如下所示,缓存的配置和使用我们在后面讲解。

# 配置将会话对象放到缓存中存储
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 配置使用哪一组缓存来保存会话
SESSION_CACHE_ALIAS = 'default'

如果要修改session数据默认的序列化方式,可以将默认的JSONSerializer修改为PickleSerializer

SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'

接下来,我们就可以限制只有登录用户才能为老师投票,修改后的praise_or_criticize函数如下所示,我们通过从request.session中获取userid来判定用户是否登录。

def praise_or_criticize(request: HttpRequest) -> HttpResponse:
    if request.session.get('userid'):
        try:
            tno = int(request.GET.get('tno'))
            teacher = Teacher.objects.get(no=tno)
            if request.path.startswith('/praise/'):
                teacher.good_count += 1
                count = teacher.good_count
            else:
                teacher.bad_count += 1
                count = teacher.bad_count
            teacher.save()
            data = {'code': 20000, 'mesg': '投票成功', 'count': count}
        except (ValueError, Teacher.DoesNotExist):
            data = {'code': 20001, 'mesg': '投票失败'}
    else:
        data = {'code': 20002, 'mesg': '请先登录'}
    return JsonResponse(data)

当然,在修改了视图函数后,teachers.html也需要进行调整,用户如果没有登录,就将用户引导至登录页,登录成功再返回到投票页,此处不再赘述。

在视图函数中读写cookie

下面我们对如何使用cookie做一个更为细致的说明以便帮助大家在Web项目中更好的使用这项技术。Django封装的HttpRequestHttpResponse对象分别提供了读写cookie的操作。

HttpRequest封装的属性和方法:

  1. COOKIES属性 - 该属性包含了HTTP请求携带的所有cookie。
  2. get_signed_cookie方法 - 获取带签名的cookie,如果签名验证失败,会产生BadSignature异常。

HttpResponse封装的方法:

  1. set_cookie方法 - 该方法可以设置一组键值对并将其最终将写入浏览器。
  2. set_signed_cookie方法 - 跟上面的方法作用相似,但是会对cookie进行签名来达到防篡改的作用。因为如果篡改了cookie中的数据,在不知道密钥和盐的情况下是无法生成有效的签名,这样服务器在读取cookie时会发现数据与签名不一致从而产生BadSignature异常。需要说明的是,这里所说的密钥就是我们在Django项目配置文件中指定的SECRET_KEY,而盐是程序中设定的一个字符串,你愿意设定为什么都可以,只要是一个有效的字符串。

上面提到的方法,如果不清楚它们的具体用法,可以自己查阅一下Django的官方文档,没有什么资料比官方文档能够更清楚的告诉你这些方法到底如何使用。

刚才我们说过了,激活SessionMiddleware之后,每个HttpRequest对象都会绑定一个session属性,它是一个类似字典的对象,除了保存用户数据之外还提供了检测浏览器是否支持cookie的方法,包括:

  1. set_test_cookie方法 - 设置用于测试的cookie。
  2. test_cookie_worked方法 - 检测测试cookie是否工作。
  3. delete_test_cookie方法 - 删除用于测试的cookie。
  4. set_expiry方法 - 设置会话的过期时间。
  5. get_expire_age/get_expire_date方法 - 获取会话的过期时间。
  6. clear_expired方法 - 清理过期的会话。

下面是在执行登录之前检查浏览器是否支持cookie的代码。通常情况下,浏览器默认开启了对cookie的支持,但是可能因为某种原因,用户禁用了浏览器的cookie功能,遇到这种情况我们可以在视图函数中提供一个检查功能,如果检查到用户浏览器不支持cookie,可以给出相应的提示。

def login(request):
    if request.method == 'POST':
        if request.session.test_cookie_worked():
            request.session.delete_test_cookie()
            # Add your code to perform login process here
        else:
            return HttpResponse("Please enable cookies and try again.")
    request.session.set_test_cookie()
    return render_to_response('login.html')

Cookie的替代品

之前我们说过了,cookie的名声一直都不怎么好,当然我们在实际开发中是不会在cookie中保存用户的敏感信息(如用户的密码、信用卡的账号等)的,而且保存在cookie中的数据一般也会做好编码和签名的工作。对于支持HTML5的浏览器来说,可以使用localStorage和sessionStorage做为cookie的替代方案,相信从名字上你就能听出二者的差别,存储在localStorage的数据可以长期保留;而存储在sessionStorage的数据会在浏览器关闭时会被清除 。关于这些cookie替代品的用法,建议大家查阅MDN来进行了解。


👇🏻遇到问题,文中示例代码获取请点击下方卡片联系作者👇🏻

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/762164.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++:这门语言优势在哪?命名空间以及缺省参数?

文章目录 C的优势解决命名空间的问题 缺省参数 C的优势 C和C语言比起来有许多优势&#xff0c;这里我们先举一个例子&#xff0c;后续进行补充 解决命名空间的问题 首先看这样的代码&#xff1a; #include <stdlib.h> #include <stdio.h>int rand 0;int main(…

k8s服务发现之第二弹Service详解

创建 Service Kubernetes Servies 是一个 RESTFul 接口对象&#xff0c;可通过 yaml 文件创建。 例如&#xff0c;假设您有一组 Pod&#xff1a; 每个 Pod 都监听 9376 TCP 端口每个 Pod 都有标签 appMyApp apiVersion: v1 kind: Service metadata:name: my-service spec:s…

创建users子应用

1.创建&#xff1a;(venv) xxxx>python manage.py startapp users 2.移动到apps里面&#xff1b; 3.在settings里面注册子应用&#xff1b; # 注册子应用 apps/users apps/users/apps name apps.users 这个路径是什么就写什么 INSTALLED_APPS [ # 用[../namage.py st…

架构训练营学习笔记:4-3存储架构模式之分片架构和分区架构

分片架构的本质&#xff1a; 分片架构能提升写性能和存储性能&#xff0c;对应的主备架构的本质是备份&#xff0c;主从架构本质提升读性能。 分片架构的两个关键点&#xff1a;分片规则跟路由规则 分片规则&#xff1a; 选择基数比较大的某个数据键值&#xff0c;让数据均匀…

第七章 集成学习

文章目录 第七章 集成学习7.1个体和集成7.2Boosting和AdaBoost7.3Bagging和随机森林7.3.1Bagging7.3.2随机森林 7.4结合策略7.4.1平均法7.4.2投票法7.4.3学习法 7.6实验&#xff1a;Adaboost 第七章 集成学习 7.1个体和集成 集成学习通过构建并结合多个学习器来完成学习任务&…

TS报错Cannot find module ‘xxx‘ or its corresponding type declarations

最近使用 vite vue3 ts 开发一个文本标注的 web 平台&#xff0c;在项目中使用了一个 js-mark 的 npm 包&#xff0c;但是在 import 导入后出现了 TS 报错&#xff1a;TS2307: Cannot find module js-mark or its corresponding type declarations.、无法解析模块 js-mark 的…

Safe Policy Optimization 复现

复现结果 在 PointGoal1、CarGoal1、Velocity-Walker2d 三个任务上测试了 RCPO&#xff0c;CRPO 以及 Safe-Policy-Optimization 中实现的 CPO&#xff0c;PPO-Lag 算法。 CarGoal PointGoal PointGoal1 和 CarGoal1 任务相对比较困难&#xff0c;在探索初期就很容易违反约束…

Dart语言(01)环境安装基础语法总结

0 说明&#xff1a; 说明&#xff1a;该系列教程主要是为有一定语言基础 C/C的程序员&#xff0c;快速学习一门新语言所采用的方法&#xff0c;属于在C/C基础上扩展新语言只是体系的方式。 1 Dart语言简介 Dart亮相于2011年10月10日至12日在丹麦奥尔胡斯举行的GOTO大会&…

【动手学深度学习】--09.PyTorch神经网络基础

文章目录 PyTorch神经网络基础1.层和块1.1自定义块1.2顺序块1.3在前向传播函数中执行代码 2.参数管理2.1参数访问2.1.1目标参数2.1.2一次性访问所有参数2.1.3从嵌套块收集参数 2.2参数初始化2.2.1内置初始化2.2.2自定义初始化 2.3参数绑定 3.自定义层3.1不带参数的层3.2带参数的…

Python爬虫学习笔记:1688商品详情API 开发API接口文档

1688API接口是阿里巴巴集团推出的一种开放平台&#xff0c;提供了丰富的数据接口、转换工具以及开发资源&#xff0c;为开发者提供了通用的应用接口及大量数据资源&#xff0c;支持开发者在1688上进行商品搜索、订单管理、交易报表及物流等方面的操作。 1688API接口主要包含以…

解密成功之道!加湿制冷设备企业如何借助CRM系统降低经营成本?

如何降低企业经营成本的同时提高效率是加湿制冷设备企业关注的重要问题。在这篇软文中&#xff0c;我们将探讨如何通过CRM系统来实现这一目标。企业可以借助CRM系统提高客户关系管理水平&#xff0c;优化销售流程&#xff0c;提升售后服务&#xff0c;从而降低成本&#xff0c;…

价格监测的作用有哪些

随着品牌的发展&#xff0c;销售渠道也会逐步增多&#xff0c;渠道中的问题也会逐渐显现&#xff0c;比如低价、乱价、窜货、假货&#xff0c;所以品牌一般都会进行控价治理&#xff0c;控价最重要的一步便是价格监测&#xff0c;监测出来的价格高低&#xff0c;能直接反应渠道…

抑郁症缓解方法:从心理到行为,全方位提升生活质量

抑郁症是一种常见的心理疾病&#xff0c;不仅仅是心情低落&#xff0c;它还伴随着一系列的身体和心理症状。在应对抑郁症时&#xff0c;除了积极寻求医学专业人士的帮助之外&#xff0c;以下这些招数也可以帮助你缓解抑郁症。 1.保持规律的生活习惯&#xff1a;抑郁症患者往往会…

小程序地图个性化样式组件要收费了!

地图个性化样式组件 自2023年6月29日0点起&#xff0c;该能力需要先购买再使用。若未购买&#xff0c;届时将无法使用该能力。具体购买方式见付费管理。自2023年6月29日0时起&#xff0c;个性化地图配置界面的入口统一为微信公众平台-付费管理&#xff0c;请从此入口进入&#…

STUN/TURN/ICE

RFC 5389 - Session Traversal Utilities for NAT (STUN) https://www.cnblogs.com/pannengzhi/p/5041546.html https://www.cnblogs.com/pannengzhi/p/5048965.html STUN : Session Traversal Utilities for NAT, 一个协助穿越NAT的工具&#xff0c;运行在UDP和TCP之上。 NA…

yolov5(v7.0)网络修改实践一:集成YOLOX的backbone(CSPDarknet和Pafpn)到yolov5(v7.0)框架中

yolov5太好用了&#xff0c;无论是实际做工程还是学习研究&#xff0c;yolov5都比较好上手&#xff0c;而且现在工业界yolov5也应用广泛。但是&#xff0c;作为学习研究&#xff0c;有不少在yolov5之后提出的涨点算法&#xff0c;还是有价值进行研究的&#xff0c;也便于跟进当…

高效管理物流:利用批量查询申通物流信息的技巧

随着电子商务的飞速发展&#xff0c;快递已经成为了现代人生活中不可或缺的一部分。作为国内一家领先的快递公司&#xff0c;申通快递服务广泛&#xff0c;覆盖全国各地。在使用申通快递服务时&#xff0c;我们通过申通快递单号可以方便地查询物流信息。然而&#xff0c;许多人…

python开发项目基于语音识别的智能垃圾分类系统的设计与实现

博主介绍&#xff1a;擅长Java、微信小程序、Python、Android等&#xff0c;专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不然下次找不到哟 Java项目精品实战案例…

Ae 效果:CC Mr. Smoothie

风格化/CC Mr. Smoothie Stylize/CC Mr. Smoothie CC Mr. Smoothie&#xff08;平滑先生&#xff09;效果可以从一个图层上的两个点进行颜色采样&#xff0c;并将这个两点之间的颜色重映射到另一个图层上&#xff0c;可通过控制重映射的平滑度从而创建迷幻的外观效果。 ◆ ◆ …

图像处理之高斯滤波

文章目录 高斯函数1.一维高斯函数2. 二维高斯函数 高斯滤波1.高斯核生成2.滤波过程 高斯函数 高斯函数广泛应用于统计学领域&#xff0c;用于表述正态分布&#xff0c;在信号处理领域&#xff0c;用于定义高斯滤波器&#xff0c;在图像处理领域&#xff0c;二维高斯核函数常用…