lnmp - 登录技术方案设计与实现

概述

登录功能是对于每个动态系统来说都是非常基础的功能,用以区别用户身份、和对应的权限和信息,设计出一套安全的登录方案尤为重要,接下来我介绍一下常见的认证机制的登录设计方案。

方案设计

HTTP 是一种无状态的协议,客户端每次发送请求时,首先要和服务器端建立一个连接,在请求完成后又会断开这个连接。系统登录的本质是确认用户的合法性和身份

在 B/S 系统中,登录功能通常都是基于 Cookie 来实现的。当用户登录成功后,一般会将登录状态记录到 Session 中。要实现服务端对客户端的登录信息进行验证都,需要在客户端保存一些信息(SessionId),并要求客户端在之后的每次请求中携带它们。在这样的场景下,使用 Cookie 无疑是最方便的,因此我们一般都会将 Session 的 Id 保存到 Cookie 中,当服务端收到请求后,通过验证 Cookie 中的信息来判断用户是否登录 。

用户首次登录流程

1、用户访问 www.stark.com/login,并输入密码登录。

2、服务器验证密码无误后,会创建 SessionId,并将它保存起来。

3、服务器端响应这个 HTTP 请求,并通过 Set-Cookie 头信息,将 SessionId 写入 Cookie 中。

cookice后续校验流程

获取cookice后续的访问就可以直接使用 Cookie 进行身份验证了

1、用户访问 www.stark.com/console 页面时,会自动带上第一次登录时写入的 Cookie

2、服务器端比对 Cookie 中的 SessionId 和保存在服务器端的 SessionId 是否一致。

3、如果一致,则身份验证成功,访问页面;如果无效,则需要用户重新登录。

需要注意的是: Cookie + Session 的方案中最关键的环节是传递Cookie有时可能会面临Cookie禁用的情况,记住只要把Cookie的值传递给服务端得到SessionId即可,可以是存储在LocalStorage,也可以使用URL 的GET方式传输。

Cookie + Session的核心点在于数据的加密和解密的算法,在用户登录进行加密、生成Cookie,在之后的交互的时候携带在header的信息头中。

加密函数代码:

php 复制代码
function passportEncrypt($txt, $key = 'stark-server@2024@#$!'): string
{
    $txt = 'yy-依加衣-' . time() . '-' . $txt;
    srand((double)microtime() * 1000000);
    $encrypt_key = md5(rand(0, 32000));
    // 变量初始化
    $ctr = 0;
    $tmp = '';
    for ($i = 0; $i < strlen($txt); $i++) {
        $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
        $tmp .= $encrypt_key[$ctr] . ($txt[$i] ^ $encrypt_key[$ctr++]);
    }
    return base64_encode(passportKey($tmp, $key));
}

function passportKey($txt, $encrypt_key): string
{
    $encrypt_key = md5($encrypt_key);
    $ctr = 0;
    $tmp = '';
    for ($i = 0; $i < strlen($txt); $i++) {
        $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
        $tmp .= $txt[$i] ^ $encrypt_key[$ctr++];
    }
    return $tmp;
}

字符串解密函数:

php 复制代码
function passportDecrypt($txt, $key = 'stark-server@2024@#$!')
{
    $txt = str_replace(' ', '+', $txt);
    $txt = passportKey(base64_decode($txt), $key);
    $tmp = '';
    for ($i = 0; $i < strlen($txt); $i++) {
        if (!isset($txt[$i]) || !isset($txt[$i + 1])) {
            return 0;
        } else {
            $tmp .= $txt[$i] ^ $txt[++$i];
        }
    }
    $tmp = explode('-', $tmp);
    $tmp[3] = $tmp[3] ?? 0;
    return $tmp[3];
}

加密解密实现的具体逻辑:

php 复制代码
//加密
$data = [
    'admin_id' => $adminInfo['admin_id'],
    'admin_name' => $adminInfo['admin_name'],
];
$demoStr = json_encode($data,JSON_UNESCAPED_UNICODE);
$authorization = passportEncrypt($demoStr);


Cookie::set('Auth-stark', $authorization,
    ['prefix' => 'think', 'expire' => 3600]
);

//解密
$json = passportDecrypt($authorization);
if(mb_strlen($json) > 0){
    $demoData = json_decode($json,true);
}

Token 登录

由于服务器端需要对接大量的客户端,也就需要存放大量的 SessionId,这样会导致服务器压力过大、无法避免 CSRF 攻击等缺点,我们可以使用 Token 的登录方式。

Token是通过服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登录后,服务器会生成一个 Token 并返回给客户端,客户端后续访问时,只需带上这个 Token 即可完成身份认证,很多企业使用JWT的技术来进行登录验证方式。

用户首次登录

1、用户访问 www.stark.com/login,输入账号密码,并点击登录。

2、服务器端验证账号密码无误,创建 Token

3、服务器端将 Token 返回给客户端,由客户端存储在Header头信息里。

后续页面访问

1、用户访问 www.stark.com/login 时,带上第一次登录时获取的 Token

2、服务器端验证该 Token ,有效则身份验证成功,无效则踢回重新的登录。

Token 生成方式

最常见的 Token 生成方式是使用 JWT(Json Web Token),它是一种简洁的、自包含的方法,用于通信双方之间以 JSON 对象的形式安全的传递信息。

答案其实就在 Token 字符串中,其实 Token 并不是一串杂乱无章的字符串,而是通过多种算法拼接组合而成的字符串。

JWT 算法主要分为 3 个部分:header(头信息),playload(消息体),signature(签名)。

  • header 部分指定了该 JWT 使用的签名算法;
  • playload 部分表明了 JWT 的意图;
  • signature 部分为 JWT 的签名,主要为了让 JWT 不能被随意篡改。
JWT Token 技术实现

Compose 安装 Jwt 的两种方式,我使用的是6.10版本 :

shell 复制代码
## 安装
composer require firebase/php-jwt 6.10

使用 composer.json 安装,加入文件,使用composer install

shell 复制代码
"require": {
    "firebase/php-jwt": "^6.10"
}

Jwt 主要是进行加密和解密,$payload定义的是你需要存储的数组信息:

php 复制代码
public static function encode(int $adminId = 0): string
{
    $redis = new Redis(config('cache.stores.redis'));
    $secretKey = Env::get("JWT.key"); // 获取JWT生成签名的密钥
    $alg = Env::get("JWT.alg"); // 获取JWT加密算法
    $payload = [
        'admin_id' => $adminId, // 存储用户ID
        'exp' => time() + Env::get("JWT.exp"), // 设定过期时间
    ];
    $jwt = JWT::encode($payload, $secretKey, $alg); // 生成JWT令牌
    $token = config('prefix.auth');
    $redis->set($token.$adminId, $jwt,Env::get("JWT.exp") - rand(10,99));
    return $jwt;
}

解密的逻辑:

php 复制代码
public static function decode(string $AccessToken = ''){
    $secretKey = Env::get("JWT.key"); // 获取JWT生成签名的密钥
    $alg = Env::get("JWT.alg"); // 获取JWT加密算法

    $secretKeyObj = new Key($secretKey,$alg);
    $headers = new stdClass();
    return JWT::decode($AccessToken, $secretKeyObj,$headers); // 使用JWT解密Token
}
相关推荐
两个人的幸福11 天前
Windows 桌面应用自研 PHP 队列(下):完整代码与六大工程化优化
php
BingoGo13 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack13 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户30745969820714 天前
PHP 扩展——从入门到理解
php
鹏仔先生15 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下15 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip15 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒15 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog25015 天前
不要再继续优化 TCP
网络协议·tcp/ip·php
Channing Lewis15 天前
PHP 解析 Excel 的那些坑:一次“行号错位”引发的数据丢失
开发语言·php·excel