10.Cookie和Session
1.状态保持
1 状态保持
我们需要先了解一下什么是会话,可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应.例如你给 10086 打个电话,你就是客户端,而 10086 服务人员就是服务器了.从双方接通电话那一刻起,会话就开始了,到某一方挂断电话表示会话结束.在通话过程中,你会向 10086 发出多个请求,那么这多个请求都在一个会话中.
 在 JavaWeb 中,客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束。
 在一个会话的多个请求中共享数据,这就是会话跟踪技术。例如在一个会话中的请求如下:请求银行主页;
- 请求登录(请求参数是用户名和密码);
- 请求转账(请求参数与转账相关的数据);
- 请求信誉卡还款(请求参数与还款相关的数据)。
在这上会话中当前用户信息必须在这个会话中共享的,因为登录的是张三,那么在转账和还款时一定是相对张三的转账和还款,这就说明我们必须在一个会话过程中有共享数据的能力。
2 怎样实现状态保持
- 浏览器请求服务器是无状态的,HTTP协议是无状态协议。
- 无状态:指一次用户请求时,浏览器,服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。
- 无状态原因:浏览器与服务器是使用Socket套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的Socket连接,而且服务器也会在处理页面完毕之后销毁页面对象。
- 有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等
- 实现状态保持主要有两种方式: 
  - 在客户端存储信息使用Cookie
- 在服务器端存储信息使用Session
 
- 在客户端存储信息使用
2.Cookie
2.1 什么叫 Cookie
Cookie 翻译成中文是小甜点,小饼干的意思.在 HTTP 中它表示服务器送给客户端浏览器的小甜点.其实 Cookie 是 key-value 结构,类似于一个 python 中的字典.随着服务器端的响应发送给客户端浏览器.然后客户端浏览器会把 Cookie 保存起来,当下一次再访问服务器时把 Cookie 再发送给服务器. Cookie 是由服务器创建,Cookie 是通过 HTTP 请求和响应头在客户端和服务器端传递的一个键值对.客户端会保存 Cookie,并会标注出 Cookie 的来源(哪个服务器的 Cookie)当客户端向服务器发出请求时会把所有这个服务器 Cookie 包含在请求中发送给服务器,这样服务器就可以识别客户端了!

2.2 Cookie特点
| HTTP 的 Cookie 规范 | 浏览器规范 | 
|---|---|
| Cookie 大小上限为 4KB | Cookie 大小上限为 8KB | 
| 一个服务器最多在客户端浏览器上保存 20 个 Cookie | |
| 个浏览器最多保存 300 个 Cookie | 一个服务器最多在客户端浏览器上保存 500 个 Cookie | 
| Cookie基于域名安全,不同域名的Cookie是不能互相访问的 | 
2.3 cookie 语法
| 操作 | 例子 | 
|---|---|
| 设置cookie值 | response.set_cookie(健,值) | 
| reponse.set_signed_cookie(健,值,salt=‘加密盐’,…) | |
| 获取 cookie值 | request.COOKIES.get(健) | 
| 删除 cookie值 | response.delete_cookie(健,path="/",domain=name) | 
设置cookie时的参数
| 参数名 | 含义 | 
|---|---|
| max_age | 超长时间 cookie需要延续的时间(以秒为单位)如果参数是\ None,这个cookie会延续到浏览器关闭为止。 | 
| expires | 超长时间 expires默认None ,cookie失效的实际日期/时间。 | 
| path | Cookie生效的路径 浏览器只会把cookie回传给带有该路径的页面,这样可以避免将传给站点中的其他的应用 , / 表示根路径,特殊的根路径的cookie可以被任何url的页面访问 | 
| domain=None | Cookie生效的域名 你可用这个参数来构造一个跨站cookie. 如, domain=".example.com"所构造的cookie对下面这些站点都是可读的: www.example.com , www2.example.com an.other.sub.domain.example.com . 如果该参数设置为 None ,cookie只能由设置它的站点读取 | 
| secure=False | 如果设置为 True ,浏览器将通过HTTPS来回传cookie | 
| httponly=False | 只能http协议传输,无法被JavaScript获取 (不是绝对,底层抓包可以获取到也可以被覆盖) | 
练习
案例 1: 显示上次访问时间。
login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="col-md-6 col-md-offset-3">
    <form action="" method="post">
        {% csrf_token %}
        <div class="form-group">
            <label for="user">用户名</label>
            <input type="text" class="form-control" name="user">
        </div>
        <div class="form-group">
            <label for="pwd">密码</label>
            <input type="password" class="form-control" name="pwd">
        </div>
        <input type="submit">
    </form>
</div>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<h4>当前时间:{{ now_time }}</h4>
{% if username  %}
    <h3>Hi,{{ username }}</h3>
    <p>上次登陆时间:{{ last_visit_time }}</p>
    <a href="/logout/">注销</a>
{% endif %}
</body>
</html>
view.py
def login(request):
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        user = User.objects.filter(name=user, pwd=pwd).first()
        if user:
            response = HttpResponse("登录成功!")
            response.set_cookie("is_login", True)
            response.set_cookie("username", user.name, path="/books_app/index")
            return response
    return render(request, "login.html")
def index(request):
    is_login = request.COOKIES.get("is_login")
    if is_login:
        username = request.COOKIES.get("username")
        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        last_time = request.COOKIES.get("last_visit_time", "")
        response = render(request, "index.html", {"username": username, "last_time": last_time, "now_time": now})
        response.set_cookie("last_visit_time", now)
        return response
    else:
        return redirect("/login/")
案例 2: 显示上次浏览过的商品。
3.session
Session 是服务器端技术,利用这个技术,服务器在运行时可以 为每一个用户的浏览器创建一个其独享的 session 对象,由于 session 为用户浏览器独享,所以用户在访问服务器的 web 资源时,可以把各自的数据放在各自的 session 中,当用户再去访问该服务器中的其它 web 资源时,其它 web 资源再从用户各自的 session 中取出数据为用户服务。

3.1 启用Session
Django项目默认启用Session。
可以在settings.py文件中查看,如图所示
 
如需禁用session,将上图中的session中间件注释掉即可。
3.2 存储方式
在settings.py文件中,可以设置session数据的存储方式,可以保存在数据库、本地缓存等。
3.2.1 数据库
存储在数据库中,如下设置可以写,也可以不写,这是默认存储方式**,Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中
SESSION_ENGINE='django.contrib.sessions.backends.db'
如果存储在数据库中,需要在项INSTALLED_APPS中安装Session应用。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-du9McPWt-1615940937363)(session_install-1615901816273.png)]](https://img-blog.csdnimg.cn/20210317083359442.png)
 数据库中的表如图所示
 
 表结构如下
 ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ut83d3pB-1615940937365)(session_desc.png)]](https://img-blog.csdnimg.cn/20210317083327699.png)
由表结构可知,操作Session包括三个数据:键,值,过期时间。
setting.py文件中的session操作
| 使用 | 含义 | 
|---|---|
| SESSION_COOKIE_NAME=“sessionid” | Session的cookie保存在浏览器上 | 
| 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,默认修改之后才保存(默认) | 
3.2.2 本地缓存
存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快。
SESSION_ENGINE='django.contrib.sessions.backends.cache'
3.2.3 混合存储
优先从本机内存中存取,如果没有则从数据库中存取。
SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
3.2.4 Redis
在redis中保存session,需要引入第三方扩展,我们可以使用django-redis来解决。
1) 安装扩展
pip install django-redis
2)配置
在settings.py文件中做如下设置
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
注意
如果redis的ip地址不是本地回环127.0.0.1,而是其他地址,访问Django时,可能出现Redis连接错误,如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AEfCf0K8-1615940937366)(redis_error.png)]](https://img-blog.csdnimg.cn/20210317083445279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDY4OTYzMA==,size_16,color_FFFFFF,t_70)
解决方法:
修改redis的配置文件,添加特定ip地址。
打开redis的配置文件
sudo vim /etc/redis/redis.conf
在如下配置项进行修改(如要添加10.211.55.5地址)

重新启动redis服务
sudo service redis-server restart
3.3 Session操作
通过HttpRequest对象的session属性进行会话的读写操作。
| 操作 | 列子 | 
|---|---|
| 设置Sessions值 | request.session[‘键’]=值 | 
| 获取Sessions值 | request.session.get(‘键’,默认值) | 
| 删除Sessions值 | del request.session[‘键’] | 
| 清除session数据,在存储中删除session的整条数据 | request.session.flush() | 
| 清除所有session,在存储中删除值部分 | request.session.clear() | 
| 设置session的有效期 | request.session.set_expiry(value) | 
-  如果value是一个整数,session将在value秒没有活动后过期。 
-  如果value为0,那么用户session的Cookie将在用户的浏览器关闭时过期。 
-  如果value为None,那么session有效期将采用系统默认值, 默认为两周,可以通过在settings.py中设置SESSION_COOKIE_AGE来设置全局默认值。 
练习
1 登录案例
视图函数
def login_session(request):
    """
   实现session的增加和更新
   if request.COOKIE.get("sessionid"):
         则sessionid保持不变,session_data数据更新
   else:
        1.则生成随机字符串sessionid
        2设置cookie,response.set_cookie("sessionid",ltv8zy1kh5lxj1if1fcs2pqwodumr45t)
        3在django—session表中创建一条记录
    """
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        user = User.objects.filter(name=user, pwd=pwd).first()
        if user:
            now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            request.session["is_login"] = True
            request.session["username"] = user.name
            request.session["last_visit_time"] = now
        return HttpResponse("登录成功!")
    return render(request, "login.html")
def index_session(request):
    """
    实现session的查询
    1.request.COOKIE.get("sessionid")
    2.django-session表中过滤纪录
    3.obj.session_data.get("is_login")
    """
    is_login = request.session.get("is_login")
    if not is_login:
        return redirect("/login_session/")
    username = request.session.get("username")
    last_visit_time = request.session.get("last_visit_time")
    return render(request, "index.html", {"username": username, "last_visit_time": last_visit_time})
def logout(request):
    """
    实现session的删除
    1.random_str=request.COOKIE.get("sessionid")
    2.django_session.objects.filter(session-key=randon_str).delete()
    3.response.delete_cookie("sessionid",random_str_str)
    """
    request.session.flush()
    return redirect("/login/")
模板
参考:前端代码参照案例 1: 显示上次访问时间。
思考: 如果第二个人再次再同一个浏览器上登录,django-session 表会怎样?
2 验证码案例
验证码可以去识别发出请求的是人还是程序!当然,如果聪明的程序可以去分析验证码图片!但分析图片也不是一件容易的事,因为一般验证码图片都会带有干扰线,人都看不清,那么程序一定分析不出来。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号