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中,可以使用内置的
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
三、
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框架中用于清空会话数据的方法。当你调用这个方法时,会话中保存的所有数据都会被删除,包括用户登录状态、用户的临时数据等。这在某些情况下非常有用,例如:
-
注销用户:当用户登出或注销时,你可以使用
request.session.flush()
来删除与用户相关的会话数据,以确保用户的隐私和安全。 -
限制会话时长:有时你可能希望会话在一段时间后过期,以确保用户在一段时间内没有活动时会话数据会被清除。
-
数据隔离:当用户切换到不同的角色或状态时,你可能希望清除之前的会话数据,以确保不同状态之间的数据不会相互干扰。
-
数据重置:如果用户执行某些特定操作后,你可能需要重置会话以确保操作后的会话状态是干净的。
四、
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、效果