PHP8.1.21版本已发布
vue8.1.21版本已发布
jquery8.1.21版本已发布

博客列表 > 会话跟踪cookie与session学习和令牌自动登录实战

会话跟踪cookie与session学习和令牌自动登录实战

吾逍遥
吾逍遥 原创
2020年12月02日 17:21:35 1269浏览

一、会话跟踪

会话(Session)跟踪是 Web 程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术 Cookie 与 Session。Cookie 通过在客户端记录信息确定用户身份,Session 通过在服务器端记录信息确定用户身份。

理论是,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆。而 Web 应用程序是使用 HTTP 协议传输数据的。HTTP 协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。要跟踪该会话,必须引入一种机制。Cookie 和 Session 就是这样的一种机制。

Cookie 实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用 response 向客户端浏览器颁发一个 Cookie。客户端浏览器会把 Cookie 保存起来。当浏览器再次请求该网站时,浏览器会把请求的网址连同该 Cookie 一同提交给服务器,服务器检查该 Cookie,以此来辨认用户状态。服务器还可以根据需要修改 Cookie 的内容。

  1. // 存储字符串数据
  2. setcookie('course','cookie');
  3. var_dump($_COOKIE['course']);

为什么刷新浏览器才能获取 cookie 值?

  • 第一步:服务端解析setcookie('course','cookie');通过请求头告诉浏览器,我要设置一个 cookie 值 cookie。
  • 第二步: 服务端返回信息,返回的信息头中带有set-cookie = cookie,浏览器接受到这个信息头,把 cookie 储存在客户端计算机的某个文件中~ *
  • 第三:刷新浏览器 cookie 中的信息会被带到请求头中发送,var_dump($_COOKIE['course']);就能显示 cookie 了。

前端查看某个网站的 Cookie 也很简单。在 chrome 浏览器中访问该网站相应的页面后,在开发者工具下输入 alert(document.cookie),JavaScript 脚本会弹出一个对话框显示本网站的所有 Cookie 的内容。当然也可以通过开发者工具的 Network 面板或 Application 面板中都可以看到。

cookie1
cookie2

注意:Cookie 功能需要浏览器的支持。如果浏览器不支持 Cookie 或者把 Cookie 禁用了,Cookie 功能就会失效。不同的浏览器采用不同的方式保存 Cookie。

chrome 浏览器的 cookie 实质就是 SQLite 文件 如果是安装版,它的位置在C:\Users\xxx\AppData\Local\Google\Chrome\User Data\Default下,我用的绿色便携版,它的位置在\Data\Default,自己可以试着找下。可以使用老师给我们的 navicat 软件打开。

cookie

Cookie属性名 Cookie发展值
String name 该Cookie的名称。Cookie一旦创建,名称便不可更改。
Object value 该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码。
int maxAge 该Cookie失效的时间,单位为秒。如果为正数,则该Cookie在maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为-1。
boolean secure 该Cookie是否仅被使用安全协议传输。安全协议有HTTPS、SSL等,在网络上传输数据之前先将数据加密,默认为false。该属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密。
String path 该Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为”/sessionWeb”的程序可以访问该Cookie。如果设置为“/”则本域名下contextPath都可以访问该Cookie。注意第一个字符必须为“/”。
String domain 可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”。
String comment 该Cookie的说明。浏览器显示Cookie信息的时候显示该说明。
int version 该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2019规范。

语法:setcookie($name,$value,$expire) name 和 value 是键值对,expire 是过期时间

  • 一般 cookie 都是存储 字符串型数据 ,若是数值也建议存储为字符串形式。
  • 若是 数组数据 则可转换为字符串、JSON 字符串或序列成字符中,当然也可以直接保存为数组形式的 cookie。如setcookie('user[name]','woxiaoyao');setcookie('user[pwd]',md5('123456'));
  • 如果是 存储特殊的符号 ,如邮箱@符号,直接使用setcookie将进行转义,如果想保持原来形式,使用setrawcookie()即可,它的使用和setcookie一样,唯一区别就是不转义特殊符号。
  • expire 是过期时间,一般形式是time()+60*60*24*7time()是当前时间戳,是 11 位整数,后面60*60*24*7意思就是 60 秒为 1 分钟,60 分钟为 1 小时,24 小时为 1 天,7 天为一周,就是过期时限为一周。不建议使用 strtotime 方法设置时限。一旦设置了过期时间,cookie 保存在本地文件中,浏览器重新打开仍然存在。

读取就比较简单了,PHP 中是通过超全局变量$_COOKIE来访问,如上面的读取 cookie 中 course 键名的存储值就是$_COOKIE['user']

其实就是对同一个变量重新赋值,它就会覆盖以前的,如setcookie('course','php');

再次调用setcookie('键名'),只要传第一个参数键名,就可以清除或释放该键名-键值,如setcookie('token');。无论是临时的 cookie 还是设置过期时间的 cookie,这种方法都可以清除。目前没有什么函数直接删除所有,可间接实现

5、expire 过期时间

老师在授课时,设置了过期时间,但在开发者工具 Application 的 Cookies 中未发现过期时间指示,我又切换到 Network,在响应头中看到 Max-Age,它就是过期时间,单位为秒

maxage

Session 是另一种记录客户端状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 Session 保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查询该客户的状态就可以了。如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 Session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。相比 Cookie 简单一些,相应的也增加了服务器的存储压力。

正常情况下,Session 都会在 cookie 中保存”通行证”,在请求服务器时会通过它在服务端的 sessioni 缓存文件中查找客户信息,从而实现用户自动登录或认证。通过session_start()开启

session_start ([ array $options = array() ] ) : bool,options 是 PHP7 新增的参数,当session_start()执行失败, 无法开始一个会话的时候,会返回 FALSE, 并且不会初始化超级变量$_SESSION。至于 options 怎么设置,后面有介绍,当然主要是参考官方

简单的 session 设置流程:

  1. session_start()开启 session
  2. 通过超全局变量$_SESSION数组设置、修改 session 中值。如$_SESSION['name']='woxiaoyao';。此时在前端的 cookie 就能找到它的
  3. 释放或删除某 session 变量,是通过 unset 实现。如unset($_SESSION['name']);
  4. 释放所有变量可以是 unset,也可通过session_unset();
  5. 删除 session 文件是session_destroy();
  6. session文件中每项格式是变量名|类型:长度:值,多个用分号隔开。如pwd|s:32:"e10adc3949ba59abbe56e057f20f883e"
    实际测试时 session_unset()和 session_destroy()都是释放变量和删除文件。
  1. session_start();
  2. $_SESSION['name']='woxiaoyao';
  3. $_SESSION['pwd']=md5('123456');
  4. var_dump($_SESSION);
  5. // 旧的注销某变量方法session_unregister,在现在已经放弃了
  6. // session_unregister('name');
  7. // 注释某个session变量方法是unset
  8. unset($_SESSION['name']);
  9. // 释放当前会话中所有session变量和文件
  10. session_unset();
  11. session_destroy();

session

至于 cookie 中 session 对应键名和保存位置均可以在服务端的 php.ini 中找到位置

session2

session 过期时限: 以在 PHP 中,设置 php.ini,找到 session.gc_maxlifetime = 1440 #(PHP5 默认 24 分钟),这里你可以随便设置一下过期时间.但是有人说设置以后,好象不起作用!其实不是不起作用,而是因为系统默认:
session.gc_probability = 1 > session.gc_divisor = 1000
1/1000 就是 session 1000 次才有一次被回收。只要你的访问量大了,那就能达到回收的效果.或者你也可以设置一下 session.gc_divisor 的值,比如:session.gc_divisor = 1,这样就能明显的看到 SESSION 过期的效果了。不过这种将降低 PHP 的效率,不是推荐方式。

session 过期时限实现一: 通过给 session 变量附加时间 ,每次读取时和当前时间进行比较就可以,由于是设置时间和当前时间都是服务器时间,所以不存在用户能恶意修改的可能。

  1. if(!isset($_SESSION['last_access'])||(time()-$_SESSION['last_access'])>60)
  2. $_SESSION['last_access'] = time();

session 过期时限实现二:通过 cookie 保存 session 的过期时间 其实这种方法并不推荐,它是控制总个 session 文件,而不像第一种,可以精确控制某个变量。它的实现是通过修改 php.ini 中参数完成的

  • session.use_cookies 把这个的值设置为 1,利用 cookie 来传递 sessionid
  • session.cookie_lifetime 这个代表 SessionID 在客户端 Cookie 储存的时间,默认是 0,代表浏览器一关闭 SessionID 就作废……就是因为这个所以 PHP 的 session 不能永久使用。
  • session.gc_maxlifetime 这个是 Session 数据在服务器端储存的时间,如果超过这个时间,那么 Session 数据就自动删除! 那么我们也把它设置为过期时间。不过单独设置它是无效的。理由上面已经解释了。

PHP官方演示的session_start携带参数案例

  • session_start(['cookie_lifetime' => 86400,]); 设置 cookie 的有效时间为 1 天
  • session_start(['read_and_close' => true,]); 如果确定不修改会话中的数据,我们可以在会话文件读取完毕之后立即关闭它,来避免由于给会话文件加锁导致其他页面阻塞

这里我就不测试了,就转网上文章了:

  1. 手动通过URL传值、隐藏表单传递session id。
  2. 设置php.ini的session.use_trans_sid = 1或者打开enable-trans-sid选项,让PHP自动跨页传递session id。
  3. 用文件、数据库等形式保存session_id,在跨页过程中手动调用。

第一种:在每个超链接上添加一个 PHPSESSID=$sid

  1. //防止返回初始页产生新的 session
  2. if(isset($_GET["PHPSESSID"])){
  3. session_id($\_GET["PHPSESSID"]);
  4. }
  5. //启动一个 session
  6. session_start();
  7. //获取当前 session 的 session_id()
  8. $sid=session_id();
  9. //在每个链接上添加参数PHPSESSID=$sid
  10. 其他页面的获取方式为:
  11. if(isset($_GET["PHPSESSID"])){
  12. //设置当前的session为初始的session,session_id()一致即可
  13. session_id($\_GET["PHPSESSID"])
  14. }
  15. session_start();

第二种方式:修改php.inik中配置

在php.ini配置中设置session.use_trans_sid=1, 这种方式会在url上自动加上SID(href,location,action,注意:js 跳转不会添加上 SID)

1、从存取方式上比较

Cookie中只能保存ASCII字符串,如果需要存取Unicode字符或者二进制数据,需要进行UTF-8、GBK或者BASE64等方式的编码。而Session中可以存取任何类型的数据,它类似序列化serialize数据为字符串

2、从隐私安全上比较

Cookie存储在客户端浏览器中,对客户端是可见的,客户端的一些程序可能会窥探、复制甚至修改Cookie中的内容。而Session存储在服务器上,对客户端是透明的,不存在敏感信息泄露的危险。如果选用Cookie,比较好的办法是,敏感的信息如账号密码等尽量不要写到Cookie中,最好是将Cookie信息加密,提交到服务器后再进行解密。

3、从有效期上比较

要达到长久地记录用户的登录信息的效果,使用Cookie会是比较好的选择。只需要设置Cookie的maxAge属性为一个很大很大的数据字或者Integer.MAX_VALUE就可以了。Cookie的maxAge属性支持这样的效果。使用Session理论上也能实现这种效果。但是由于Session依赖于Cookie,而Cookie PHPSESSID的maxAge默认为-1,关闭了浏览器该Session就会失效,因此Session不能实现信息永久有效的效果。使用URL地址重写也不能实现。而且如果设置Session的超时时间过长,服务器累计的Session就会越多,越容易导致内存溢出。

4、从对服务器的负担上比较

Session是保存在服务器端的,每个用户都会产生一个Session。如果并发访问的用户非常多,会产生非常多的Session,消耗大量的内存。而Cookie保存在客户端,不占用服务器资源。如果并发浏览的用户非常多,Cookie昌很好的选择。

5、从跨域名上比较

Cookie支持跨域名访问,例如将domain属性设置为“.helloweenvsfei.com”,则以它为后缀的所有域名均可以访问该Cookie。而Session则不会支持跨域名访问,仅在它所在的域名内有效。

七、会话跟踪实战:Session存储令牌实现用户自动登录

实现思路: 默认页面index.php,登录页面login.php和检测页面check.php

  • index.php,用户默认是进入页面,它的逻辑是:
      1. 检测cookie中是否存在token,不存在则跳转登录页面login.php,否则继续
      1. 检测token是否过期,token最后保存了登录时间,过期则跳转登录页面login.php,否则继续
      1. 检测token是否非法,和session的token比较,若不相等则是非法,跳转登录页面login.php,否则继续
      1. 在session读取用户名id和密码
      1. 通过数据库查询id和密码,存在则显示后台,若不存在则跳转login.php或注册页面。
  • login.php 登录页面,最简单就是表单提交,还有就是退出登录时清除cookiet和session.
  • check.php,检查用户登录信息,它的逻辑是:
      1. 检查是否有用户名和密码,没有则跳转登录页面login.php,有则继续
      1. 查询数据库,看用户名和密码是否正确,不正确跳转login.php,正确继续
      1. 若用户勾选了自动登录,默认是7天,将token令牌保存到cookie,同时保存到session,防止用户伪造。同时在session中保存id和密码
      1. 若用户登录时未勾选则清除cookie的token和session。
  1. //index.php
  2. <?php
  3. session_start();
  4. // 1、检测cookie中是否存在token
  5. if (!isset($_COOKIE['token'])) {
  6. exit("
  7. <script>
  8. alert('请您先登录');
  9. location.href='login.php';
  10. </script>
  11. ");
  12. }
  13. // 2、检测token是否过期
  14. $tokentime = intval(substr($_COOKIE['token'], -10));
  15. if (time() - $tokentime > 60 * 60 * 24 * 7) {
  16. exit("
  17. <script>
  18. alert('登录已过期,请重新登录');
  19. location.href='login.php';
  20. </script>
  21. ");
  22. }
  23. // 3、检测token是否非法
  24. $servertoken = $_SESSION['token'];
  25. if ($servertoken != $_COOKIE['token']) {
  26. exit("
  27. <script>
  28. alert('非法令牌,请重新登录');
  29. location.href='login.php';
  30. </script>
  31. ");
  32. }
  33. // 4、在session读取用户名id和密码
  34. $id = $_SESSION['id'];
  35. $pwd = $_SESSION['pwd'];
  36. // 5、从数据库中读取用户信息
  37. $salt = 'woxiaoyao';
  38. $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
  39. $stmt = $pdo->prepare('SELECT uname,pwd,id FROM user where id = ? and pwd = ?;');
  40. $stmt->execute(array($id, $pwd));
  41. $res = $stmt->fetch(PDO::FETCH_ASSOC);
  42. if ($stmt->rowCount() < 1) {
  43. exit("
  44. <script>
  45. alert('用户不存在,请重新登录或注册');
  46. location.href='login.php';
  47. </script>
  48. ");
  49. }
  50. ?>
  51. <!DOCTYPE html>
  52. <html lang="en">
  53. <head>
  54. <meta charset="UTF-8" />
  55. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  56. <title>首页</title>
  57. <style>
  58. nav {
  59. height: 40px;
  60. background-color: deepskyblue;
  61. padding: 0 20px;
  62. display: flex;
  63. justify-content: space-between;
  64. align-items: center;
  65. }
  66. nav>a {
  67. color: white;
  68. text-decoration: none;
  69. }
  70. </style>
  71. </head>
  72. <body>
  73. <nav>
  74. <a href="index.php">简书后台管理</a>
  75. <a href="" id="logout">
  76. <span style="color: yellow;"> 欢迎您<?php echo $res['uname']; ?></span> |
  77. 退出</a>
  78. </nav>
  79. <script>
  80. document
  81. .querySelector("#logout")
  82. .addEventListener("click", function(ev) {
  83. // 禁用链接跳转行为
  84. ev.preventDefault();
  85. // 询问用户是否退出,并执行对应操作
  86. if (confirm("是否退出?"))
  87. window.location.assign("login.php?action=logout");
  88. });
  89. </script>
  90. </body>
  91. </html>
  1. //login.php
  2. <?php
  3. if (isset($_GET['action']) && $_GET['action'] == 'logout') {
  4. setcookie('token');
  5. session_unset();
  6. session_destroy();
  7. }
  8. ?>
  9. <!DOCTYPE html>
  10. <html lang="zh">
  11. <head>
  12. <meta charset="UTF-8">
  13. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  14. <title>用户登录</title>
  15. <style>
  16. .container {
  17. width: 30em;
  18. margin: 2em auto;
  19. border-radius: 1em;
  20. background-color: #007d20;
  21. color: white;
  22. padding: 0.2em 0.5em 1em;
  23. text-align: center;
  24. }
  25. form {
  26. display: grid;
  27. grid-template-columns: 5em 1fr;
  28. grid-template-rows: repeat(4, 2em);
  29. place-items: center initial;
  30. gap: 0.5em;
  31. }
  32. form>button {
  33. grid-column: 2/3;
  34. }
  35. form>input[type="checkbox"] {
  36. width: 2em;
  37. height: 2em;
  38. }
  39. </style>
  40. </head>
  41. <body>
  42. <div class="container">
  43. <h3>用户登录</h3>
  44. <form action="check.php" method="post">
  45. <label for="email">用户名:</label>
  46. <input type="text" name="username" id="email" placeholder="输入用户名" required autofocus>
  47. <label for="password">密码:</label>
  48. <input type="password" name="password" id="password" placeholder="密码不少于6位" required>
  49. <label for="autoLogin">自动登录</label>
  50. <input type="checkbox" name="autoLogin" id="autoLogin">
  51. <button>提交</button>
  52. </form>
  53. </div>
  54. </body>
  55. </html>
  1. //check.php
  2. <?php
  3. session_start();
  4. extract($_POST);
  5. // 1、检查是否有用户信息
  6. if (empty($username) || empty($password)) {
  7. exit("
  8. <script>
  9. alert('非法登录');
  10. location.href='login.php';
  11. </script>
  12. ");
  13. }
  14. $password = md5($password);
  15. $salt = 'woxiaoyao';
  16. // 2、查询数据库,看用户名和密码是否正确
  17. $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
  18. $stmt = $pdo->prepare('SELECT uname,pwd,id FROM user where uname = ? and pwd = ?;');
  19. $stmt->execute(array($username, $password));
  20. $res = $stmt->fetch(PDO::FETCH_ASSOC);
  21. if ($stmt->rowCount() == 1) {
  22. // 3、若勾选了自动登录则记录用户信息
  23. if ($autoLogin == 'on') {
  24. $token = md5($res['id'] . $res['pwd'] . $salt) . time();
  25. setcookie('token', $token, time() + 60 * 60 * 24 * 7);
  26. $_SESSION['token'] = $token;
  27. $_SESSION['id'] = $res['id'];
  28. $_SESSION['pwd'] = $res['pwd'];
  29. } else {
  30. // 4、否则清除cookie和session
  31. setcookie('token');
  32. session_unset();
  33. session_destroy();
  34. }
  35. exit("
  36. <script>
  37. location.href='index.php';
  38. </script>
  39. ");
  40. } else {
  41. exit("
  42. <script>
  43. alert('用户名和密码不正确');
  44. location.href='login.php';
  45. </script>
  46. ");
  47. }

测试中最坑的地方: 就是session的使用了,要使用$_SESSION超全局变量必须使用session_start(),否则读取失败,因为session_start初始化超全局变量$_SESSION.

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议
吾逍遥2020-12-05 07:39:24 2楼
受教了,初次使用PHP的session刚开始还是容易忘记session_start,也好,错误能记得更深刻
灭绝师太2020-12-04 13:37:01 1楼
周同学作业依然是那么的仔细全面 1. 判断一个变量是否存在, 可以isset()&&!empty().因为声明一个空字符串,isset()返回的依然是true. 2.session_start();是让PHP核心程序将和session有关的内建环境变量预先载入到内存中.