总结一下PHP中的会话机制(keep-alive,Session,cookie)以及JWT

【1、先大概了解http服务器的握手吧】

在说会话之前我们顺带了解一下http的工作原理。由于本文需要大概先说说。具体参考单独的开篇文章,日后会放出地址。
在这里插入图片描述
我们现说说过程吧。首先用户根据浏览器输入的地址经过dns的解析 会发送到对应的服务器上进行处理。

第一次握手

主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;

第二次握手

主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包

第三次握手

主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。

完成三次握手,主机A与主机B开始传送数据。这个过程之后客户机开始和服务器之间传送,客户机发送请求信息,服务器返回对应的内容。但是服务器面向的是千千万万的客户机,不可能为每个客户机长时间的占用连接通道。所以服务器一般会应答之后关闭http连接。如果客户机在完成请求之后还需要发送其他请求信息,那么需要服务器和客户端再次建立http握手协议。 是不是觉得有些麻烦。。。。

**

【2、简述一下应运而生的keep-alive】

如上文所说的那样。每次都需要建立新的连接太过于麻烦,通常技术的迭代并不是因为老旧的技术不能使用,而是原先的设计理念不适合于现有的状态下,所以我们必须要做出改变。我们通常的想法就是让http服务器将用户的握手状态保持一段时间。所以各个http服务器软件开发出了 keep-alive 具体设置如下,这里只见简述一下,具体等后面的具体开篇文章。
在这里插入图片描述

【3、客户机的身份证–Cookie】

上一段我我们说完keep-alive了,但是现在我们又多了一个问题,在keep-alive的保持时间内我们有办法识别出该用户。但是出了这个时间段,用户再来访问这个网站还是需要重新会话,重新认证身份。所以又出了一门新的技术–cookie
在每个浏览器的application选项里面会保存各个网站的cookie。
在这里插入图片描述
就拿csdn举例,我们可以将用户的部分信息保存在浏览器的cookie里面,每一次http请求 浏览器会将cookie 带上一起发送到服务器以便服务器知道这个用户的身份信息,或者是一个浏览使用记录。那么我们怎么利用php 去给浏览器设置cookie呢
我们先看一下setcookie函数
在这里插入图片描述
这里我们解释一下每个参数的作用。首先name是cookie的名字,这个没什么可以解释的。value是对应的值,也没啥可以解释的。expire是有效期,通常是用规定一个时间戳,到期销毁。path是路径,可以控制对应页面所能访问的cookie,相当于权限,domain规定能访问的域名。secure规定是否只能ssl下传输cookie,保证cookie安全。下面我们动手实现一个cookie的例子:
在这里插入图片描述
这里利用thinkphp5来保存一个testcookie 时间为60s,我们只验证name,value和expire常用的三个参数。
在这里插入图片描述
如图,该cookie 60s后便消失了。

**

【4、Cookie的更安全的策略–Session】

到这里很多人会想,既然网站的cookie 是在本地浏览器的,那我为什么不可以修改这cookie 通过某种方式 访问到本来我无法访问的信息呢! 所以服务器端有引进新的安全策略session。 在php中session的工作逻辑为服务器端保存一个session的映射数组。同时加密一个有规律的字段,将该字段通过cookie的形式保存在本地浏览器,该字段由于是加密的所以不会泄漏任何有效信息。浏览器访问页面会带上cookie,服务器根据保存的cookie字段 读取到映射数组内的值。来加载该客户的信息。在php中 session 是如何设置的呢

在这里插入图片描述
在这里插入图片描述
如上图所示:php在本地保存了一个phpsessid的cookie值。每次请求时,会带上phpsessid,服务器通过该值来读取用户信息。这样的方式比本地直接存储用户信息更加安全,可以防止xss攻击。在keep-alive连接被释放前,都能够读取到对应的session值。是一种短效安全的存储方式。
**

【5、分布式身份认证方式–Json Web Token(jwt)】

先说说应用场景。现在某一个客户登陆到一个某公司旗下的A网站。这时候需要访问公司的B网站,由于之前的A网站已经完成身份认证了。但是session只存储在对应的服务器里面。没有共享,所以该客户还需要重新登陆B网站的身份认证,这样就面临一个新的问题。我们能不能将所有登陆信息以固定加密的形式存储,方便整个应用旗下的所有网站应用,统一识别。
**
JWT一共有三个部分分别为:

head(头):
这部分通常存储的是jwt的元信息。比如加密方式,和认证类型等等

//举个head例子
{
  "alg": "HS256",
  "typ": "JWT"
}

payload(载荷):
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

//下面我列举官方的字段,以及个人字段
{
	//以下为官方字段
	'iss':'签发人',
	'exp':'过期时间',
	'sub':'主题',
	'aud':'受众',
	'nbf':'生效时间',
	'iat':'签发时间',
	'jti':'编号',

	//以下可以写自己的字段
	'auth':'管理员',
	'name':'张三'
	.
	.
	.
}

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

Signature(签名):
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
base64UrlEncode(header) + “.” +base64UrlEncode(payload)
,secret
)

下面我们动手实现一个jwt类

<?php
namespace  app\index\controller;

class Jwt
{
    /**
     * @var array jwt头部
     */
    private static $header = [

        //生成signature的算法
        'alg'   =>  'HS256',

        //类型
        'typ'   =>  'JWT'
    ];

    /**
     * @var array jwt 载荷
     */
    private static $payload = [

        //签发人
        'iss'   =>  '',

        //签发时间
        'iat'   =>  '',

        //过期时间
        'exp'   =>  '',

        //改时间之前不处理该token
        'nbf'   =>  '',

        //面向的用户
        'sub'   =>  '',

        //该Token唯一标识
        'jti'   =>  ''
    ];



    /**
     * @var string 摘要密钥
     */
    private static $key = 'abc123';

    /**
     * Jwt constructor.
     * @param string $sub 用户
     * @param int $exp  过期时间(时间戳)
     * @param int $nbf  处理时间(时间戳)
     */
    public function __construct()
    { }

    /**
     * 参数
     *
     * @param string $sub
     * @param integer $exp
     * @param integer $nbf
     * @return void
     */
    public static function setPayload(string $sub, int $exp, int $nbf = null)
    {
        if (!$nbf) {
            $nbf = time();
        }
        self::$payload = [
            'iss'   =>  'program_name',
            'iat'   =>  time(),
            'exp'   =>  $exp,
            'nbf'   =>  $nbf,
            'sub'   =>  $sub,
            'jti'   =>  md5(uniqid('JWT') . time())
        ];
    }
    /**
     * @return string 获取token
     */
    public static function getToken(string $sub, int $exp, int $nbf)
    {
        self::setPayload($sub, $exp, $nbf);
        //base编码头
        $base64header = self::base64UrlEncode(json_encode(self::$header, JSON_UNESCAPED_UNICODE));
        $base64payload = self::base64UrlEncode(json_encode(self::$payload, JSON_UNESCAPED_UNICODE));
        $token = $base64header . '.' . $base64payload . '.' . self::signature($base64header . '.' . $base64payload, self::$key, self::$header['alg']);
        return $token;
    }


    /**
     * @param string $Token 需要验证的token
     * @return bool|string
     */
    public static function verifyToken(string $Token)
    {
        //分割token
        $tokens = explode('.', $Token);
        if (count($tokens) != 3) {
            return false;
        }
        list($base64header, $base64payload, $sign) = $tokens;
        //获取jwt算法
        $base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
        if (empty($base64decodeheader['alg'])) {
            return false;
        }
        //签名验证
        if (self::signature($base64header . '.' . $base64payload, self::$key, $base64decodeheader['alg']) !== $sign) {
            return false;
        }

        $payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);
        //签发时间大于当前服务器时间验证失败
        if (isset($payload['iat']) && $payload['iat'] > time()) {
            return false;
        }
        //过期时间小宇当前服务器时间验证失败
        if (isset($payload['exp']) && $payload['exp'] < time()) {
            return false;
        }
        //该nbf时间之前不接收处理该Token
        if (isset($payload['nbf']) && $payload['nbf'] > time()) {
            return false;
        }
        return $payload;
    }

    /**
     * base64UrlEncode  https://jwt.io/ 中base64UrlEncode编码实现
     * @param string $input 需要编码的字符串
     * @return string
     */
    private static function base64UrlEncode(string $input)
    {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

    /**
     * base64UrlEncode  https://jwt.io/  中base64UrlEncode解码实现
     * @param string $input 需要解码的字符串
     * @return bool|string
     */
    private static function base64UrlDecode(string $input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $addlen = 4 - $remainder;
            $input .= str_repeat('=', $addlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }

    /**
     * HMACSHA256签名   https://jwt.io/  中HMACSHA256签名实现
     * @param string $input 为base64UrlEncode(header).".".base64UrlEncode(payload)
     * @param string $key
     * @param string $alg   算法方式
     * @return mixed
     */
    private static function signature(string $input, string $key, string $alg = 'HS256')
    {
        $alg_config = array(
            'HS256' => 'sha256'
        );
        return self::base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key, true));
    }
}

我们如何调用这个jwt类我们现模拟生成一个jwt 验证串,签发名为test,时长为从现在起一小时内有效;

<?php
namespace app\index\controller;
use app\index\controller\Jwt;

class Index
{
    public function index()
    {
        
        $jwt = new Jwt();
        return  $jwt->getToken('test', time() + 3600, \time());
    }
}

模拟客户端验证:

<?php
namespace app\index\controller;

use app\index\controller\Jwt;

class Index
{
    public function index()
    {
        $str = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwcm9ncmFtX25hbWUiLCJpYXQiOjE1NjA3NDg1ODgsImV4cCI6MTU2MDc1MjE4OCwibmJmIjoxNTYwNzQ4NTg4LCJzdWIiOiJ0ZXN0IiwianRpIjoiMDIwOWY1ZGY4M2Q3MzI0ODc3ZWI4YjBjMDgzNjg5NzkifQ.sP5bmdD1lfwVYglmU7MXf7If4wQQ35FKaTqSrnXA80o';
        $jwt = new Jwt();
        if ($jwt->verifyToken($str)) {
            //如果有效则返回1
            return '1';
        } else {
            //无效则为0;
            return '0';
        }
    }
}

到此 我们完成了 jwt验证的全部过程,具体业务逻辑需要可以根据特定情况设计;

ps:请注意,该字符串签发之后不可取消,只能等待时长到期!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值