cookie和session

如何使用会话 | Django 文档 | Django (djangoproject.com)

一、cookie和session简介

1、背景

HTTP协议的特性之一:无状态

痛点:访问网站,每次需要重新从浏览器登录。为了保存用户状态产生了cookie技术,只要cooike值还在浏览器中,浏览器自动和服务器端验证,不需要用户再一次手动登录。

但是数据保存在浏览器上面,很明显的问题是:数据不够安全。做了优化:把原本存在浏览器上的数据存到后端,就称之为是session。session就解决了cookie数据不安全的问题

2、cookie原理

是一种存储在用户计算机上的小数据片段,用于跟踪用户在网站上的活动并存储一些用户偏好。它是通过网页服务器发送到用户的浏览器,然后由浏览器存储并在每次请求特定网站时将其发送回服务器的。

1 创建和发送 Cookie:当用户访问一个网站时,网站的服务器可以向用户的浏览器发送一个包含一些数据的 Cookie。这个数据可以包括用户的身份验证信息、访问时间、用户偏好等等。

2 存储 Cookie:一旦用户的浏览器接收到服务器发来的 Cookie,它会将 Cookie 存储在用户计算机的一个特定位置,通常是浏览器的 Cookie 存储区域。每个 Cookie 都有一个名称、一个值以及一些可选属性(如过期时间、域、路径等)。

3 发送 Cookie:在用户之后的每个请求中,浏览器都会自动将与当前网站相关的 Cookie 发送回服务器。这允许服务器识别用户、跟踪用户的会话状态,或根据用户的偏好提供定制化的体验。

4 服务器处理 Cookie:服务器收到 Cookie 后,可以根据其中的数据进行各种操作,比如识别用户、保持用户的登录状态、跟踪用户的浏览历史等等。

5 过期和删除:每个 Cookie 都可以设置一个过期时间,过了这个时间,浏览器将不再发送该 Cookie。此外,服务器也可以要求浏览器删除某个特定的 Cookie,或者用户自己在浏览器中删除 Cookie。

3、session原理

与 Cookie 不同,Session 数据存储在服务器上而不是用户的浏览器中。Session 通常用于跟踪用户的登录状态、存储用户数据、在不同页面之间共享数据等。

1 会话创建:当用户第一次访问网站时,服务器会为该用户创建一个新的会话。服务器为每个会话分配一个唯一的标识符,通常称为“Session ID”。

2 Session ID:服务器会将 Session ID 发送给用户的浏览器,通常通过 Cookie(称为 Session Cookie)的方式。这个 Cookie 包含了 Session ID,使得浏览器在后续的请求中能够告诉服务器该请求属于哪个会话。

3 数据存储:在会话中,服务器可以存储用户的数据。这些数据可以是用户的登录信息、购物车内容、用户偏好设置等等。服务器会根据 Session ID 来区分不同用户的数据。

4 数据访问:当用户在不同页面之间跳转或与网站进行交互时,浏览器会自动将包含 Session ID 的 Cookie 发送回服务器。服务器根据 Session ID 找到相应的会话,并访问其中的数据,以便在不同页面之间保持用户状态或共享数据。

5 会话过期:会话通常具有一定的过期时间。一旦用户一段时间内没有活动,或者会话达到设定的过期时间,服务器会自动清除会话数据,从而释放服务器资源。用户需要重新进行登录或者重新开始新的会话。

4、面试点

1. 保存在浏览器上的数据都称之为是cookie
2. session是保存在服务端的
3. session的数据相对更加安全,cookie不够安全
4. session是基于cookie工作的? 对还是不对? 对
5. django让浏览器保存cookie,用户有权可以设置浏览器不保存
6. session离开cookie一定就不能工作了,对还是不对? 不对 

二、Django操作cookie

在Django中,可以使用内置的request和三板斧(render, redirect, HttpResponse)对象来操作cookie

1、设置cookie(将cookie存在浏览器)

from django.http import HttpResponse

def set_cookie(request):
    response = HttpResponse("Cookie has been set!")
    response.set_cookie('username', 'myuser', max_age=3600)  # 设置cookie的名称、值和过期时间(秒)
    return response

注意参数:

  • key, 键
  • value=’’, 值
  • max_age=None, 超时时间 cookie需要延续的时间(以秒为单位)如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止
  • expires=None, 超时时间(IE requires expires, so set it if hasn’t been already.)
  • path=’/‘, Cookie生效的路径,/ 表示根路径
  • ecure=False, 浏览器将通过HTTPS来回传cookie
  • httponly=False 只能http协议传输,无法被JavaScript获取

2、获取cookie

from django.http import HttpResponse

def get_cookie(request):
    username = request.COOKIES.get('username', 'Guest')  # 如果cookie不存在,使用默认值'Guest'
    return HttpResponse("Hello, " + username)

3、删除cookie(用在退出登录)

from django.http import HttpResponse
import datetime

def delete_cookie(request):
    response = HttpResponse("Cookie has been deleted!")
    response.delete_cookie('username')  # 删除名为'username'的cookie
    return response

三、 Django操作session

1、需要知道的概念

session的数据是保存在后端,保存在后端的载体其实有很多种,比如:可以把数据保存在数据库、文件、Redis等

Django的默认保存位置在数据库中,在django_session表中,这张表是默认生成的

session的默认过期时间是14天

2、获取session

  • 获取session发生了哪些事?
  • 浏览器先把sessionid回传到Django的后端
  • Django后端获取到sessionid,然后去数据表中根据session_key查询
  •   # 如果查到了,说明之前已经登陆过了
  •   # 如果查不到,就返回None
  • 查询出来的数据默认是加密的,Django后端又把数据解密之后封装到request.session中
  •   #在取session值的时候,就从request.session中取
from django.http import HttpResponse

def get_session(request):
    # 获取会话数据
    username = request.session.get('username', 'Guest')
    is_logged_in = request.session.get('is_logged_in', False)
    return HttpResponse("Username: {}, Logged in: {}".format(username, is_logged_in))

注意:  

  • request.session[key]: 通过键(key)获取会话数据的值。
  • request.session.get(key, default=None): 获取会话数据的值,如果键不存在,返回默认值。

3、 设置session

设置成功一个session值有什么变化

  • 会生成一个随机字符串
  • 会把用户设置的信息保存在django_session表中,数据也做了加密处理
  • 把数据封装到了request.session里去了
  • Django后端把随机字符串保存到了浏览器中
  • 随机字符串保存在浏览器中的key=sessionid
  • 当设置多个session值的时候,session_key是不变的,变的是session_Data
  • 当设置多个session值的时候,django_Session表中只存在一条记录(一台电脑的一个浏览器)
from django.http import HttpResponse

def set_session(request):
    # 设置会话数据
    request.session['username'] = 'myuser'
    request.session['is_logged_in'] = True
    return HttpResponse("Session data has been set!")

4、 删除session

from django.http import HttpResponse

def delete_session(request):
    # 删除会话数据
    if 'username' in request.session:
        del request.session['username']
    if 'is_logged_in' in request.session:
        del request.session['is_logged_in']
    return HttpResponse("Session data has been deleted!")

注:

  • del request.session[key]: 通过键(key)删除会话数据。
  • request.session.pop(key, None): 删除会话数据,如果键不存在,不抛出异常。

5、session数据操作:

  • request.session.keys(): 获取会话中所有键的列表。
  • request.session.values(): 获取会话中所有值的列表。
  • request.session.items(): 获取会话中所有键值对的列表。
  • request.session.clear(): 清空会话中的所有数据。
  • request.session.session_key: 获取会话的唯一标识符(session ID)

退出登录,session版本 session.flush()

def logout(request):
    request.session.flush()
    return redirect('/login/')

request.session.flush() 是Django框架中用于清空会话数据的方法。当你调用这个方法时,会话中保存的所有数据都会被删除,包括用户登录状态、用户的临时数据等。这在某些情况下非常有用,例如:

  1. 注销用户:当用户登出或注销时,你可以使用request.session.flush() 来删除与用户相关的会话数据,以确保用户的隐私和安全。

  2. 限制会话时长:有时你可能希望会话在一段时间后过期,以确保用户在一段时间内没有活动时会话数据会被清除。

  3. 数据隔离:当用户切换到不同的角色或状态时,你可能希望清除之前的会话数据,以确保不同状态之间的数据不会相互干扰。

  4. 数据重置:如果用户执行某些特定操作后,你可能需要重置会话以确保操作后的会话状态是干净的。

四、 Django中的Session配置

django项目的settings.py文件中可以配置session的配置

1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 

4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

其他公用设置项:
SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

session 设置过期时间

在 Django 中,可以使用 request.session.set_expiry 方法来动态设置会话的过期时间。作用在特定视图或代码块中更改会话的过期时间而不是在全局设置中固定一个值。

from django.shortcuts import render

def my_view(request):
    # 设置会话的过期时间为60秒
    request.session.set_expiry(60)

    # 在会话中存储数据
    request.session['user_id'] = 123

    # 从会话中获取数据
    user_id = request.session.get('user_id')

    return render(request, 'my_template.html', {'user_id': user_id})

五、基于cookie的登录功能,用户名和密码从数据表中读取!

1、html 简单的登录页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<h1>这里是登录页面</h1>
<br>
<form action="" method="post" enctype="application/x-www-form-urlencoded">
    username:<input type="text" name="username">
    <br>
    <br>
    password:<input type="password" name="password">
    <br>
    <br>
    提交:<input type="submit">
</form>
</body>
</html>

2、views 后端处理逻辑

验证登录页面、给主页加上认证装饰器。当cookie过期,访问主页自动跳转到登录页面。

#  登录页面
def login(request):
    if request.method == 'POST':
        # 从前端获取用户输入的帐号密码
        username = request.POST.get('username')
        password = request.POST.get('password')

        # 读取数据库中的账号密码
        user_obj = models.User.objects.filter(name=username, password=password).first()
        print(user_obj)
        if user_obj:
            print('登录成功!')
            # 保存用户的信息,使用cookie保存
            obj = redirect('/home/')
            obj.set_cookie('username', user_obj.name, max_age=10)
            return obj
    return render(request, 'login.html')

# 登录认证装饰器
def login_auth(func):
    def inner(request, *args, **kwargs):
        if request.COOKIES.get('username'):
            return func(request, *args, **kwargs)
        else:
            return redirect('/login/')

    return inner

# 主页加上登录认证装饰器
@login_auth
def home(request):
    '''访问这个home页面,必须登录之后才能范围,否则不让访问?'''
    # 判断用户是否登录了?
    # 就是判断是否有cookie
    # print(request.COOKIES.get('username'))
    # if request.COOKIES.get('username'):
    #     return HttpResponse("登录之后才能看到我哦")
    # else:
    #     return redirect('/login/')
    return render(request, 'home.html')

注意⚠️:

1. obj.set_cookie('username', user_obj.name, max_age=10) 

max_age=10是设置cookie 10秒过期

2. 查看cookie保存的值

六、登录显示用户名、退出登录

1、views

# 退出登录
def offline(request):
    response = redirect('/login1/')
    response.delete_cookie('username')  # 删除名为 'username' 的 Cookie
    return response

注意:

使用三板斧创建一个response对象,这里用redirect返回一个登录页面

通过这个对象点方法去删除cookie

2、html

home页面动态获取登录用户名、退出登录跳转到offline视图函数

 <ul class="nav navbar-nav navbar-right">
                <li><a href="#">{{ login1_name }}</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">更多操作<span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="/offline/">退出登录</a></li>
                        <li><a href="#">刷新</a></li>
                        <li><a href="#">待开发</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">待开发</a></li>
                    </ul>
                </li>
            </ul>

3、登录界面

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            background: url('https://pic2.zhimg.com/3ae866e7992a94069c7e0c417aac807d_r.jpg') no-repeat;
            background-size: 100% 130%;
        }

        #login_box {
            width: 20%;
            height: 400px;
            background-color: #00000060;
            margin: auto;
            margin-top: 10%;
            text-align: center;
            border-radius: 10px;
            padding: 50px 50px;
        }

        h2 {
            color: #ffffff90;
            margin-top: 5%;
        }

        #input-box {
            margin-top: 5%;
        }

        span {
            color: #fff;
        }

        input {
            border: 0;
            width: 60%;
            font-size: 15px;
            color: #fff;
            background: transparent;
            border-bottom: 2px solid #fff;
            padding: 5px 10px;
            outline: none;
            margin-top: 10px;
        }

        button {
            margin-top: 50px;
            width: 60%;
            height: 30px;
            border-radius: 10px;
            border: 0;
            color: #fff;
            text-align: center;
            line-height: 30px;
            font-size: 15px;
            background-image: linear-gradient(to right, #30cfd0, #330867);
        }

        #sign_up {
            margin-top: 45%;
            margin-left: 60%;
        }

        a {
            color: #b94648;
        }
    </style>
</head>

<body>
<div id="login_box">
    <h2>图书管理</h2>
    <form action="" method="post">
        <div id="input_box">
            <input type="text" placeholder="请输入用户名" name="username">
        </div>
        <div class="input_box">
            <input type="password" placeholder="请输入密码" name="password">
        </div>
        <button>登录</button>
        <br>
    </form>
</div>
</body>
</html>

七、基于session的登录功能,用户名和密码从数据表中读取!

1、views

request.session['username'] = user_obj.name  保存session到django_session数据库

def login2(request):
    if request.method == 'POST':
        # 从前端获取用户输入的帐号密码
        username = request.POST.get('username')
        password = request.POST.get('password')

        # 读取数据库中的账号密码
        user_obj = models.User.objects.filter(name=username, password=password).first()
        if user_obj:
            print('登录成功!')
            # 保存用户的信息,使用session方式保存
            request.session['username'] = user_obj.name
            return redirect('/book_list/')
    return render(request, 'login.html')

2、登录认证装饰器session版

def login_auth1(func):
    def inner(request, *args, **kwargs):
        if 'username' in request.session and request.session['username']:
            return func(request, *args, **kwargs)
        else:
            return redirect('/login2/')
    return inner

3、给图书展示页面加上装饰器

@login_auth1
def book_list(request):
    # 分页器代码
    current_page = request.GET.get('page')
    try:
        current_page = int(current_page)
    except Exception:
        current_page = 1

    book_list = models.Book.objects.all()
    all_count = book_list.count()
    page_pbj = Pagination(current_page, all_count, per_page_num=4)

    book_list = book_list[page_pbj.start:page_pbj.end]  # 前端循环取数据
    page_html = page_pbj.page_html()

    return render(request, 'book_list.html', locals())

4、同一个浏览器不同的用户,比如:zjz、ldj,django_session 数据表中只会存在一条记录

 当使用不同的浏览器登录时, 数据库中才会多存一条记录

八、分页器

1、 封装分页相关数据(在Django中utils引用)

class Pagination(object):
    def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
        """
        封装分页相关数据
        :param current_page: 当前页
        :param all_count:    数据库中的数据总条数
        :param per_page_num: 每页显示的数据条数
        :param pager_count:  最多显示的页码个数
        """
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1

        if current_page < 1:
            current_page = 1

        self.current_page = current_page

        self.all_count = all_count
        self.per_page_num = per_page_num

        # 总页码
        all_pager, tmp = divmod(all_count, per_page_num)
        if tmp:
            all_pager += 1
        self.all_pager = all_pager

        self.pager_count = pager_count
        self.pager_count_half = int((pager_count - 1) / 2)

    @property
    def start(self):
        return (self.current_page - 1) * self.per_page_num

    @property
    def end(self):
        return self.current_page * self.per_page_num

    def page_html(self):
        # 如果总页码 < 11个:
        if self.all_pager <= self.pager_count:
            pager_start = 1
            pager_end = self.all_pager + 1
        # 总页码  > 11
        else:
            # 当前页如果<=页面上最多显示11/2个页码
            if self.current_page <= self.pager_count_half:
                pager_start = 1
                pager_end = self.pager_count + 1

            # 当前页大于5
            else:
                # 页码翻到最后
                if (self.current_page + self.pager_count_half) > self.all_pager:
                    pager_end = self.all_pager + 1
                    pager_start = self.all_pager - self.pager_count + 1
                else:
                    pager_start = self.current_page - self.pager_count_half
                    pager_end = self.current_page + self.pager_count_half + 1

        page_html_list = []
        # 添加前面的nav和ul标签
        page_html_list.append('''
                    <nav aria-label='Page navigation>'
                    <ul class='pagination'>
                ''')
        first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
        page_html_list.append(first_page)

        if self.current_page <= 1:
            prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
        else:
            prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)

        page_html_list.append(prev_page)

        for i in range(pager_start, pager_end):
            if i == self.current_page:
                temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
            else:
                temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
            page_html_list.append(temp)

        if self.current_page >= self.all_pager:
            next_page = '<li class="disabled"><a href="#">下一页</a></li>'
        else:
            next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
        page_html_list.append(next_page)

        last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
        page_html_list.append(last_page)
        # 尾部添加标签
        page_html_list.append('''
                                           </nav>
                                           </ul>
                                       ''')
        return ''.join(page_html_list)

2、views

def book_list(request):
    # 查询图书列表数据
    # book_queryset = models.Book.objects.all()
    # print(book_queryset)
    # <QuerySet [<Book: Book object (1)>, <Book: Book object (4)>, <Book: Book object (6)>, <Book: Book object (7)>, <Book: Book object (8)>, <Book: Book object (9)>, <Book: Book object (10)>, <Book: Book object (11)>]>
    # Book object (1) 为id号,数据删除后,id不翟按照顺序,顺序在增大,删掉的id不再存在

    current_page = request.GET.get('page')
    try:
        current_page = int(current_page)
    except Exception:
        current_page = 1

    book_list = models.Book.objects.all()
    all_count = book_list.count()
    page_pbj = Pagination(current_page, all_count, per_page_num=4)

    book_list = book_list[page_pbj.start:page_pbj.end]
    page_html = page_pbj.page_html()

    return render(request, 'book_list.html', locals())

3、html

{% extends 'home.html' %}


{% block content %}
    <h1 class="text-center">图书列表展示</h1>
    <a href="/book_add/" class="btn btn-info">添加图书</a>
    <table class="table  table-striped table-hover">
        <thead>
        <tr>
            <th>标题</th>
            <th>价格</th>
            <th>出版日期</th>
            <th>出版社</th>
            <th>作者</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody>
        {% for foo in book_list %}
            <tr class="tr_{{ foo.pk }}">
                <td>{{ foo.title }}</td>
                <td>{{ foo.price }}</td>
                <td>{{ foo.publish_date|date:'Y-m-d' }}</td>
                <td>{{ foo.publish.name }}</td>
                {#书查出版社,正向查询,外键字段跳表#}
                <td>
                    {% for author in foo.authors.all %}
                        {% if forloop.last %}
                            {{ author.name }}
                        {% else %}
                            {{ author.name }} |
                        {% endif %}
                    {% endfor %}

                </td>
                <td>
                    {#                    <a href="/book/edit/{{ foo.pk }}" class="btn btn-success">修改</a>#}
                    <a href="/book_edit/?id={{ foo.pk }}" class="btn btn-success">修改</a>
                    <a href="#" class="del  btn btn-danger" delete_id="{{ foo.pk }}">删除</a>
                    {#这里不能使用id标签,因为在for循环中,id不能重复 自定义一个id,a标签自动跳转也相当于有二次提交#}
                </td>
            </tr>
        {% endfor %}


        </tbody>
    </table>
{% endblock %}

{% block js %}

    <script>
        $(".del").click(function () {
            // 删除的逻辑:当我们点击删除按钮的时候,应该获取点击行的id值,然后,把这个id传到后端,后端接收这个id值
            // 做删除逻辑
            var id = $(this).attr('delete_id');
            {#这里的this代表的是$(".btn")对象#}
            var _this = $(this);
            // 紧接着要发送ajax请求,最好做一个二次确认
            layer.confirm('你确定要删除这条数据吗?', {
                btn: ['确定'] //按钮
            }, function () {
                // 发送ajax请求
                $.ajax({
                    url: '/book_del/',  // {# 把请求提交到del视图函数中去#}
                    type: 'post',
                    data: {id: id},
                    success: function (res) {
                        if (res.code == 200) {
                            {#layer.msg(res.msg, {icon:2}, function () {#}
                            {#    location.reload();}  {# ajax不会自动刷新页面 #}
                            layer.msg(res.msg);
                            {# 接收后端返回的信息 #}
                            _this.parent().parent().remove();
                            {# this 指的是function (res) _this引用变量 #}
                            {#$(".tr_" + id).remove();  删除dom的tr行来实现不展示#}
                        }
                    }
                });
            });
        })
    </script>
{% endblock %}

{% block fenye %}
    {{ page_html | safe }}
{% endblock %}

4、home页面划分区域

5、效果

 

posted @ 2023-08-09 19:19  凡人半睁眼  阅读(23)  评论(0编辑  收藏  举报