PHP核心之Session

Session(会话)

原理

  • 信息传输流程

    • 第一次访问,服务器给客户端分配一个空间
    • 每个空间都有一个唯一的编号,比如A11
    • 将编号A11放到响应头,带回到客户端,保存在cookie中
    • 客户端下次访问只能访问带有唯一编号的服务器Session会话空间
  • 概念

    • Session是服务器端的技术
    • Session是基于Cookie技术的

Session操作

  • 概述

    • 默认情况下,会话不会自动开启,通过session_start()开启会话
    • 通过session_id()获取会话的编号
    • 通过$_SESSION操作会话
    • 会话可以保存除了资源以外的所有类型
    • 重复开启会话会报错,一般出现在包含文件中
  • session_start()作用

    • 没有会话空间就创建一个空间
    • 有会话空间就打开空间
<?php
session_start();		     //开启会话
@session_start();            //重复开启会话会报错,可以通过错误抑制符来屏蔽错误
$_SESSION['name']='tom';	 //保存会话
$_SESSION['age']=20;

echo $_SESSION['name'],'<br>';
echo $_SESSION['age'],'<br>';
echo '会话编号:'.session_id();   //获取会话编号
?>

与会话有关的Session配置

  • 主要配置

    • session.save_path="F:\wamp\tmp\" Session保存的地址
    • session.auto_start = 1 session自动开启,默认不自动开启
    • session.save_handler = files 会话以文件的形式保存
    • session.gc_maxlifetime = 1440 会话的生命周期是1440秒
  • 次要配置

    • session.name = PHPSESSID 会话命名
    • session.cookie_lifetime = 0 会话编号的过期时间
    • session.cookie_path = / 会话编号整站有效
    • session.cookie_domain = 会话编号在当前域名下有效

销毁会话

  • 概念
    • 通过session_destroy()销毁会话
    • 销毁会话就是删除自己的会话文件
<?php
session_start();
session_destroy();	//销毁会话
?>

垃圾回收

  • 概念
    • 会话文件超过了生命周期是垃圾文件
    • PHP自动进行垃圾回收
    • 垃圾回收的概率默认是1/1000
session.gc_probability = 1
session.gc_divisor = 1000
  • Session

    • 保存于 服务器
    • 存储数据 量大
    • 可存储 除了资源以外的所有类型
    • 安全
  • Cookie

    • 保存于 客户端
    • 存储数据 量小(4K)
    • 可存储 字符串
    • 不安全

禁用Cookie对Session的影响

  • 影响

    • session是基于cookie的
    • 如果禁用cookie,session无法使用
  • 解决方案

    • 默认情况下,Session只依赖于Cookie,Session的编号只能通过Cookie传输
    • 可以设置为Session不仅仅依赖于Cookie
    • 设置后,php自动添加get和post传递session_id
session.use_only_cookies = 0    // session不仅仅依赖于cookie
session.use_trans_sid = 1		//允许通过其他方式传递session_id

Session存储到数据库中

  • 概念
    • Session默认情况下存储到文件中
    • 我们可以将Session存储到数据库中

创建sess表

  • memory引擎的注意事项
    • memory引擎数据存储在内存中,速度快,但是重启服务后数据清空
    • memory引擎中的字段不可以是text类型
-- 如果用了text类型就不能使用memory引擎
drop table if exists sess;
create table sess(
       sess_id varchar(50) primary key comment '会话编号',
       sess_value text comment '会话值',
       sess_time int unsigned not null comment '会话产生时间'
)engine=innodb  charset=utf8 comment '会话表'

-- memory引擎数据存储在内存中
drop table if exists sess;
create table sess(
       sess_id varchar(50) primary key comment '会话编号',
       sess_value varchar(2000) comment '会话值',
       sess_time int unsigned not null comment '会话产生时间'
)engine=memory  charset=utf8 comment '会话表'

更改会话存储(Session入库)

  • 通过session_set_save_handler()更改存储

    • session_set_save_handler()必须在session_start()之前
    • 有6个回调函数(open,close,read,write,destroy,gc)
    • 回调函数read必须返回字符串,其他函数返回布尔值
  • 6个回调函数执行的时间

    • open() 开启会话执行
    • close() 关闭会话执行
    • read() 打开会话后就执行
    • write() 更改会话会话的值和关闭会话之前执行
      • 如果调用了session_destroy()就不会调用write()
    • destroy() 调用session_destroy()的时候自动执行
    • gc() 垃圾回收的时候自动执行
<?php
//打开会话
function open() {
	global $link;
	$link=mysqli_connect('localhost','root','','sel');
	mysqli_set_charset($link,'utf8');
	return true;
}
//关闭会话
function close() {
	return true;
}
//读取会话
function read($sess_id) {
	global $link;
	$sql="select sess_value from sess where sess_id='$sess_id'";
	$rs=mysqli_query($link,$sql);
	$rows=mysqli_fetch_row($rs);
	return (string)$rows[0];
}
//写入会话
function write($sess_id,$sess_value) {
	global $link;
	$sql="insert into sess values ('$sess_id','$sess_value',unix_timestamp()) on duplicate key update sess_value='$sess_value',sess_time=unix_timestamp()";
	return mysqli_query($link,$sql);
}
//销毁会话
function destroy($sess_id) {
	global $link;
	$sql="delete from sess where sess_id='$sess_id'";
	return mysqli_query($link,$sql);
}
//垃圾回收
function gc($lifetime) {
	global $link;
	$expires=time()-$lifetime;	//过期时间点
	$sql="delete from sess where sess_time<$expires";
	return mysqli_query($link,$sql);
}
//更改会话存储
session_set_save_handler('open','close','read','write','destroy','gc');
//开启会话
session_start();
//session_destroy();
?>

项目封装

  • 概念
    • 只要访问控制器就会启动session入库

session封装

# Framework\Lib\Session.class.php
<?php
namespace Lib;
class Session{
    private $mypdo;
    public function __construct() {
        session_set_save_handler(
            [$this,'open'],
            [$this,'close'],
            [$this,'read'],
            [$this,'write'],
            [$this,'destroy'],
            [$this,'gc']
        );
        session_start();
    }
    public function open() {
        $this->mypdo= \Core\MyPDO::getInstance($GLOBALS['config']['database']);
        return true;
    }
    //关闭会话
    public function close() {
        return true;
    }
    //读取会话
    public function read($sess_id) {
        $sql="select sess_value from sess where sess_id='$sess_id'";
        return (string)$this->mypdo->fetchColumn($sql);
    }
    //写入会话
    public function write($sess_id,$sess_value) {
        $sql="insert into sess values ('$sess_id','$sess_value',unix_timestamp()) on duplicate key update sess_value='$sess_value',sess_time=unix_timestamp()";
        return $this->mypdo->exec($sql)!==false;
    }
    //销毁会话
    public function destroy($sess_id) {
        $sql="delete from sess where sess_id='$sess_id'";
        return $this->mypdo->exec($sql)!==false;
    }
    //垃圾回收
    public function gc($lifetime) {
        $expires=time()-$lifetime;	//过期时间点
        $sql="delete from sess where sess_time<$expires";
        return $this->mypdo->exec($sql)!==false;
    }
}
?>

基础控制器

# Framework\Core\Controller.class.php
<?php
//基础控制器
namespace Core;
class Controller{
    public function __construct() {
        $this->initSession();
    }
    //初始化session
    private function initSession(){
        new \Lib\Session();
    }
}
?>

所有的控制器都继承基础控制器

<?php
namespace Controller\Admin;
//商品模块
class ProductsController extends \Core\Controller{
    ...
?>

登录模块

创建用户表

drop table if exists `user`;
create table `user`(
       user_id smallint unsigned auto_increment primary key comment '主键',
       user_name varchar(20) not null comment '用户名',
       user_pwd char(32) not null comment '密码',
       user_face varchar(50) comment '头像',
       user_login_ip int comment '最后登录的IP',
       user_login_time int unsigned comment '最后登录的时间',
       user_login_count smallint unsigned default 0 comment '登录次数',
       is_admin tinyint default 0 comment '超级管理员'
)engine=innodb charset=utf8 comment '用户表';

显示界面

  • 目录
    • 将HTML模板页面拷贝到View\Admin目录下
    • 将images、css 拷贝到Public\Admin目录下

显示登录 注册界面

  • 显示登录 注册界面
    • 在Controller\Admin目录下创建LoginController.class.php
    • 更改login.html、register.html页面中的静态资源路径
      • 在HTML中路径要使用绝对路径,从根目录开始匹配
      • 在CSS页面图片的路径要使用相对路径,相对于当前页面本身
    • 将login.html、register.html页面联通起来
# LoginController.class.php
namespace Controller\Admin;
use Core\Controller;    //引入基础控制器
class LoginController extends Controller{
    //登录
    public function loginAction(){
        require __VIEW__.'login.html';
    }
    //注册
    public function registerAction(){
        require __VIEW__.'register.html';
    }
}
<link rel="stylesheet" href="/Public/Admin/css/pintuer.css">
<link rel="stylesheet" href="/Public/Admin/css/admin.css">
-- login.html跳转到register.html
<input type="button" value="用户注册"  class="button button-block bg-main text-big" onClick="location.href='index.php?p=Admin&c=Login&a=register'" />

-- register.html跳转到login.html
<input type="button"  class="button button-block bg-main text-big" value="返回" onClick="location.href='index.php?p=Admin&c=Login&a=login'" />

显示后台管理界面

  • 显示后台管理界面
    • 在Controller\Admin目录下创建AdminController.class.php
    • 在admin.html中,更改框架集中的路径
    • 更改top.html、menu.html、main.html的静态资源
# AdminController.class.php
namespace Controller\Admin;
class AdminController extends \Core\Controller{
    public function adminAction(){
        require __VIEW__.'admin.html';
    }
    public function topAction(){
        require __VIEW__.'top.html';
    }
    public function menuAction(){
        require __VIEW__.'menu.html';
    }
    public function mainAction(){
        require __VIEW__.'main.html';
    }
}
<frameset rows="95,*" cols="*" frameborder="no" border="0" framespacing="0">
  <frame src="index.php?p=Admin&c=Admin&a=top" name="topFrame" scrolling="no" noresize="noresize" id="topFrame" />
  <frameset rows="*" cols="180,*" framespacing="0" frameborder="no" border="0">
    <frame src="index.php?p=Admin&c=Admin&a=menu" name="leftFrame" scrolling="no" noresize="noresize" id="leftFrame" />
    <frame src="index.php?p=Admin&c=Admin&a=main" name="mainFrame" id="mainFrame" />
  </frameset>
</frameset>

用户注册

配置文件

# config.php
'app'=>array(
  'key' => 'itcast'     // 加密密钥                                                      

控制器(LoginController)

# LoginController.class.php
public function registerAction(){
    //第二步:执行注册逻辑
    if(!empty($_POST)){
        $data['user_name']=$_POST['username'];
        $data['user_pwd']=md5(md5($_POST['password']).$GLOBALS['config']['app']['key']);
        $model=new \Core\Model('user');
        if($model->insert($data))
            $this->success ('index.php?p=Admin&c=Login&a=login', '注册成功,您可以去登陆了');
        else
            $this->error ('index.php?p=Admin&c=Login&a=register', '注册失败,请重新注册');
    }     
    //第一步:显示注册界面
    require __VIEW__.'register.html';
}

完善注册功能

  • 概念
    • 用户名是不能重复的,但输入用户名以后,通过异步判断一下此用户名是否存在

AJAX

<script>
window.onload=function(){
    var req=new XMLHttpRequest();   //创建ajax对象
    document.getElementById('username').onblur=function(){
        document.getElementById('msg').innerHTML='';
        req.open('get','/index.php?p=admin&c=Login&a=checkUser&username='+this.value);
        req.onreadystatechange=function(){
            if(req.readyState==4 && req.status==200){
                if(req.responseText=='1'){
                    document.getElementById('msg').innerHTML='用户名已经存在';
               }
            }
        }
        req.send();
    }
}
</script>

...

<div class="field field-icon-right">
<input type="text" class="input" name="username" placeholder="请输入用户名" id='username' />
<span id='msg'></span>
 </div>

控制器(LoginController)

public function checkUserAction(){
    $model=new \Model\UserModel();
    echo $model->isExists($_GET['username']);
}

模型(UserModel)

namespace Model;
class UserModel extends \Core\Model{
    //用户存在返回1,否则返回0
    public function isExists($name){
        $info=$this->select(['user_name'=>$name]);
        return empty($info)?0:1;
    }
}

用户登陆

  • 原理
    • 通过用户名和密码找到对应的用户就是登陆成功

控制器(LoginController)

namespace Controller\Admin;
use Core\Controller;    //引入基础控制器
class LoginController extends Controller{
    //登录
    public function loginAction(){
        //第二步:执行登陆逻辑
        if(!empty($_POST)){
            $model=new \Model\UserModel();
           if($info=$model->getUserByNameAndPwd($_POST['username'], $_POST['password'])){
                $this->success('index.php?p=Admin&c=Admin&a=admin', '登陆成功');
            }else{
                $this->error('index.php?p=Admin&c=Login&a=login', '登陆失败,请重新登陆');
            }
        }        
        //第一步:显示登陆界面
        require __VIEW__.'login.html';
    }
}

模型(UserModel)

//通过用户名和密码获取用户的信息
public function getUserByNameAndPwd($name,$pwd){
    //条件数组
    $cond=array(
        'user_name'  =>  $name,
        'user_pwd'   => md5(md5($pwd).$GLOBALS['config']['app']['key'])
    );
    //通过条件数组查询用户
    $info=$this->select($cond);
    if(!empty($info))
        return $info[0];    //返回用户信息
    return array();
}

防止SQL注入

  • 原理

    • 通过输入的字符串和SQL语句拼接成具有其他含义的语句,以达到攻击的目的
  • 防范措施

    • 给特殊字符添加转义
    • 将单引号替换为空
    • md5加密
    • 预处理
    • 如果确定传递的参数是整数,就需要进行强制类型转换
//单引号添加转义字符
echo addslashes("aa'bb'"),'<br>';	//aa\'bb\'
//字符串替换
echo str_replace("'",'',"aa'bb'");	//aabb

防止FQ

  • FQ

    • 通过直接在地址栏输入URL地址进入模板页面
  • 解决

    • 用户登录成功以后,给用户一个令牌(session),在整个访问的过程中,令牌不消失

登录成功以后,将用户信息保存到会话中

public function loginAction(){
    //第二步:执行登陆逻辑
    if(!empty($_POST)){
        $model=new \Model\UserModel();
        if($info=$model->getUserByNameAndPwd($_POST['username'], $_POST['password'])){
            $_SESSION['user']=$info;    //将用户信息保存到会话中
            $this->success('index.php?p=Admin&c=Admin&a=admin', '登陆成功');
            ....
        }
    }
}

后台基础控制器(BaseController)

<?php
//后台基础控制器
namespace Controller\Admin;
class BaseController extends \Core\Controller{
    public function __construct() {
        parent::__construct();
        $this->checkLogin();
    }
    //验证是否登录
    private function checkLogin(){
        if(CONTROLLER_NAME=='Login')    //登录控制器不需要验证
            return;
        if(empty($_SESSION['user'])){
            $this->error('index.php?p=Admin&c=Login&a=login', '您没有登录');
        }
    }
}
?>

所有的后台控制器都继承后台基础控制器

namespace Controller\Admin;
class AdminController extends BaseController{
    ....
}

更新登陆信息

控制器(LoginController.class.php)

public function loginAction(){
    //第二步:执行登陆逻辑
    if(!empty($_POST)){
        $model=new \Model\UserModel();
        if($info=$model->getUserByNameAndPwd($_POST['username'], $_POST['password'])){
            $_SESSION['user']=$info;    //将用户信息保存到会话中
            $model->updateLoginInfo();   //更新登陆信息
            ...
        }
    }
}

模型(UserModel)

  • 概念
    • ip2long() IPv4转换成整数
    • long2ip() 整数转换成IPv4
    • addslashes() 添加转义字符
    • $_SERVER['REMOTE_ADDR'] 获取客户端地址
//更新登陆信息
public function updateLoginInfo(){
    //更新的信息
    $_SESSION['user']['user_login_ip']= ip2long($_SERVER['REMOTE_ADDR']);
    $_SESSION['user']['user_login_time']=time();
    $_SESSION['user']['user_login_count']=++$_SESSION['user']['user_login_count'] ;
    //实例化模型
    $model=new \Core\Model('user');
    //更新
    return (bool)$model->update($_SESSION['user']);
}

记住密码

  • 概念
    • 登录成功后,如果需要记录用户名和密码,则将用户名和密码记录在cookie中
public function loginAction(){
  //第二步:执行登陆逻辑
  if(!empty($_POST)){
    //校验验证码
    $captcha=new \Lib\Captcha();
    if(!$captcha->check($_POST['passcode']))
      $this->error ('index.php?p=Admin&c=Login&a=login', '验证码错误');
    $model= new \Model\UserModel();
    if($info= $model->getUserByNameAndPwd($_POST['username'], $_POST['password'])){
      $_SESSION['user']= $info;    //将用户信息保存到会话中
      $model->updateLoginInfo();   //更新登陆信息

      if(isset($_POST['remember'])){
          $time= time()+3600*24*7; //记录7天
          setcookie('name',$_POST['username'],$time);
          setcookie('pwd',$_POST['password'],$time);
      }
      $this->success('index.php?p=Admin&c=Admin&a=admin', '登陆成功');
    }else{
      $this->error('index.php?p=Admin&c=Login&a=login', '登陆失败,请重新登陆');
    }
  }        
  //第一步:显示登陆界面
  $name=$_COOKIE['name']??'';
  $pwd=$_COOKIE['pwd']??'';
  require __VIEW__.'login.html';
}
<input type="text" class="input" name="username" placeholder="登录账号" value="<?=$name?>"  />
...
<input type="password" class="input" name="password" placeholder="登录密码" value="<?=$pwd?>" />

安全退出

  • 概念
    • 退出:退出的时候不销毁令牌
    • 安全退出:退出的时候销毁了令牌(session)
public function logoutAction(){
    session_destroy();
    header('location:index.php?p=Admin&c=Login&a=login');
}
<a class="button button-little bg-yellow" href="index.php?p=Admin&c=Login&a=logout" target="_top">安全退出</a>
posted @ 2020-12-20 18:44  wing1377  阅读(178)  评论(0编辑  收藏  举报