PHP单点登录怎么实现

wen PHP项目 5

PHP单点登录(SSO)实现全攻略:从原理到代码实战

目录导读

  1. 什么是单点登录(SSO)?为什么需要它?
  2. SSO核心机制:Cookie、Session与Token的三角关系
  3. PHP实现SSO的三种主流方案对比
    • 方案A:基于共享Session(同域)
    • 方案B:基于中央认证服务器(CAS)
    • 方案C:基于JWT的无状态SSO
  4. 手写一个轻量级PHP SSO系统(代码+步骤)
  5. SSO安全陷阱与解决方案(CSRF、XSS、重放攻击)
  6. SSO常见问题问答(FAQ)

什么是单点登录(SSO)?为什么需要它?

想象一下:你公司有OA、CRM、财务系统三个独立Web应用,每个都要单独输账号密码,员工每天光登录就要花5分钟,IT部门还要管理三套密码策略——这就是典型的“多点登录”痛点。

PHP单点登录怎么实现

单点登录(Single Sign-On) 的核心价值在于:一次认证,全网通行,用户在任意一个子系统登录后,访问其他子系统时无需再次输入凭证,系统自动识别身份。

PHP实现SSO的三大场景:

  • 企业内网多应用整合(如:OA+ERP+邮件)
  • 多域名商城/平台(如:主站+子站+移动端H5)
  • 微服务架构下的统一用户认证(常见于Laravel + API)

关键认知:SSO不是一种固定技术,而是一种认证架构模式,PHP中的实现方式取决于你的系统分布情况(同域?跨域?)。


SSO核心机制:Cookie、Session与Token的三角关系

要理解SSO,必须先理清这三者:

机制 存储位置 生命周期 SSO中的角色
Cookie 客户端浏览器 可持久化(如7天) 携带会话标识(Session ID或Token)
Session 服务器端(文件/Redis/数据库) 默认过期(通常30分钟) 存储用户状态信息
Token 客户端(或Redis) 可自定义过期时间 无状态认证的核心(如JWT)

SSO核心矛盾点:Cookie的域限制——浏览器对domain.a.com下发的Cookie,在domain.b.com下不会被发送,这就是为什么跨域SSO需要“中央认证服务器”或“Token + 跨域共享”方案。


PHP实现SSO的三种主流方案对比

方案A:基于共享Session(仅限同域)

// 前提:所有子系统在同一个域下(如 app.company.com/oa、app.company.com/crm)
// 通过共享Redis存储Session
session_save_path('tcp://redis-server:6379');
session_start();
$_SESSION['user_id'] = 123; // 所有子系统共用一个Session池

优点:实现最简单,无额外开发量。
缺点:无法跨域(子域不同Cookies仍隔离),若子域为 oa.company.comcrm.company.com,需设置 session_set_cookie_params(['domain' => '.company.com']) 才行。

方案B:基于中央认证服务器(CAS)—— 最经典

流程:
1. 用户访问子系统A → 未登录 → 302重定向到 认证中心
2. 认证中心显示登录页 → 用户输入凭证 → 验证成功
3. 认证中心生成 Service Ticket(ST) → 通过URL参数回跳子系统A
4. 子系统A后台请求认证中心验证ST → 验证通过 → 建立本地Session
5. 访问子系统B → 同样重定向到认证中心 → 认证中心发现已有登录态 → 直接颁发ST → 回跳B

优点:跨域完全支持,安全性高。
缺点:需要单独维护认证中心服务,代码量较大。

方案C:基于JWT的无状态SSO(现代API微服务首选)

// 用户登录后,认证中心签发一个JWT:
$token = JWT::encode(['uid' => 123, 'exp' => time() + 3600], $secretKey);
// 子系统间通过请求头或URL参数传递JWT,本地用公钥验签
$decoded = JWT::decode($token, $publicKey, ['HS256']);

优点:跨域/跨平台友好,无需共享Session,适合前后端分离。
缺点:Token无法主动吊销(除非做黑名单),需警惕Token泄露风险。


手写一个轻量级PHP SSO系统(代码+步骤)

我们以实现方案B(简化版CAS)为例,需两个角色:sso-server(认证中心)和 sso-client(两个子系统)。

步骤1:认证中心 sso-server.php

<?php
session_start();
// 模拟用户数据
$users = ['admin' => '123456'];
// 处理登录表单
if ($_POST['username'] ?? false) {
    if ($users[$_POST['username']] ?? false == $_POST['password']) {
        $_SESSION['user'] = $_POST['username'];
        // 生成一次性服务票据(ST)
        $st = md5(uniqid());
        $_SESSION['tickets'][] = $st;
        // 回跳子系统
        header("Location: {$_GET['redirect']}?st=$st");
        exit;
    }
}
// 校验ST接口(供子系统调用)
if ($_GET['validate'] ?? false) {
    $st = $_GET['st'];
    if (in_array($st, $_SESSION['tickets'] ?? [])) {
        echo json_encode(['success' => true, 'user' => $_SESSION['user']]);
        unset($_SESSION['tickets'][array_search($st, $_SESSION['tickets'])]);
    } else {
        echo json_encode(['success' => false]);
    }
    exit;
}
if ($_SESSION['user'] ?? false) {
    $st = md5(uniqid()); $_SESSION['tickets'][] = $st;
    header("Location: {$_GET['redirect']}?st=$st");
    exit;
}
// 显示登录表单
echo "<form method='post'><input name='username' /><input name='password' /><button>登录</button></form>";

步骤2:子系统客户端 sso-client.php

<?php
session_start();
// 收到ST票据则去认证中心校验
if ($_GET['st'] ?? false) {
    $verify = file_get_contents("http://sso.example.com/sso-server.php?validate=1&st={$_GET['st']}");
    $result = json_decode($verify, true);
    if ($result['success']) $_SESSION['user'] = $result['user'];
}
// 未登录则跳转认证中心
if (!$_SESSION['user'] ?? false) {
    header("Location: http://sso.example.com/sso-server.php?redirect=" . urlencode("http://sub.example.com/client.php"));
    exit;
}
echo "欢迎 " . $_SESSION['user'] . " 登录子系统!";

步骤3:部署测试

  • sso-server.php 放在 认证服务器(如 sso.example.com
  • 将两个 sso-client.php 分别放在 oa.example.comcrm.example.com
  • 访问任一子站 → 自动跳转中央登录 → 登录后回跳 → 直接访问另一子站已自动登录

SSO安全陷阱与解决方案

风险 说明 应对措施
CSRF(跨站请求伪造) 攻击者诱导用户点击链接触发登录回跳 在认证中心生成随机state参数,回跳时校验
XSS(跨站脚本) 攻击者窃取ST票据 所有输出的用户数据用htmlspecialchars()转义
重放攻击 同一ST被多次使用 设计ST为一次性,校验后立即销毁(代码已实现)
Token/ST泄露 通过HTTP明文传输 全站强制HTTPS,不允许HTTP回退
会话固定攻击 攻击者提前伪造Session ID 登录成功后必须调用session_regenerate_id()

SSO常见问题问答(FAQ)

Q1:PHP中SSO一定要用Redis吗?

不一定,若仅同域,使用文件Session即可;若跨域,共享Session才需要Redis,但生产环境建议使用Redis,因为支持过期时间、持久化与分布式。

Q2:考虑使用现成的PHP SSO库?

推荐:

  • phpCAS(官方CAS协议库)
  • firebase/php-jwt(JWT生成/验证)
  • Laravel Passport(Laravel生态的OAuth2/SSO方案) 不建议重复造轮子,除非你只想学习原理。

Q3:多域名(如主站.com和子站.cn)能实现SSO吗?

可以,但Cookie无法跨顶级域名,必须使用方案B(CAS)或方案C(JWT),方案C通常配合前置Nginx将JWT透传,或前端将JWT存localStorage通过fetch头传递。

Q4:用户退出登录,如何做到所有子系统同时退出?

这是SSO最棘手的问题,常见做法:

  • 认证中心设置logout接口,回调所有已登录子系统(需维护子系统注册表)
  • 使用JWT时,维护一个Redis黑名单(但在无状态场景下会增加复杂度)

Q5:SSO性能瓶颈在哪里?如何优化?

每次访问子系统都要查询认证中心(或验签JWT),优化:子系统本地缓存用户信息(如Session),只在初次请求时验证ST;JWT则本地验签无需网络IO。


PHP单点登录的实现路径取决于你的系统架构——同域选共享Session跨域且希望可控选CAS现代API/微服务选JWT,理解核心原理(认证中心、票据、回调)后,你既能使用现成库,也能自己动手造一个满足特定需求的SSO,安全永远是第一位的,任何SSO实现都必须全站HTTPS并对ST/Token生命周期做严格限制。

抱歉,评论功能暂时关闭!