【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验证的全部过程,具体业务逻辑需要可以根据特定情况设计;