Blogger Information
Blog 54
fans 6
comment 31
visits 106561
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
会话跟踪cookie与session学习和令牌自动登录实战
吾逍遥
Original
1308 people have browsed it

一、会话跟踪

会话(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.

Correcting teacher:灭绝师太灭绝师太

Correction status:qualified

Teacher's comments:
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
2 comments
吾逍遥 2020-12-05 07:39:24
受教了,初次使用PHP的session刚开始还是容易忘记session_start,也好,错误能记得更深刻
2 floor
灭绝师太 2020-12-04 13:37:01
周同学作业依然是那么的仔细全面 1. 判断一个变量是否存在, 可以isset()&&!empty().因为声明一个空字符串,isset()返回的依然是true. 2.session_start();是让PHP核心程序将和session有关的内建环境变量预先载入到内存中.
1 floor
Author's latest blog post
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!