主系统登录后,子系统怎么实现自动登录?

核心回答

跨域没法共享 Cookie,就靠中央认证中心:子系统没登录就跳过去,认证中心确认已登录后再跳回来,子系统建立自己的登录态。

这句话先说到这里就够了。


假设:

text 复制代码
主系统:
https://www.example.com

订单系统:
https://order.example.com

数据看板:
https://dashboard.example.com

它们属于同一个父域名:

text 复制代码
example.com
   ├── www.example.com
   ├── order.example.com
   └── dashboard.example.com

这种情况下,可以考虑:

http 复制代码
Set-Cookie: token=xxx; Domain=.example.com

浏览器访问:

text 复制代码
order.example.com

时,就可能自动携带:

text 复制代码
token=xxx

所以:

同一父域名下,可以通过 Cookie Domain 共享登录态。

但如果变成:

text 复制代码
www.example.com
order.example.net
dashboard.example.org

就完全不同了。因为:

text 复制代码
example.com
example.net
example.org

之间不能通过 Cookie 的 Domain 属性互相共享 Cookie。

所以:

text 复制代码
Cookie Domain=.example.com

不可能让:

text 复制代码
example.net
example.org

也收到这个 Cookie。


2. 跨一级域名怎么办?

这时候就不能再想着:

text 复制代码
主系统 Cookie
      ↓
直接共享
      ↓
子系统

而应该变成:

text 复制代码
             ┌──────────────────┐
             │   中央认证中心    │
             │   Auth Server     │
             └────────┬─────────┘
                      │
              保存全局登录态
                      │
       ┌──────────────┼──────────────┐
       ↓              ↓              ↓
   主系统           订单系统        数据看板
 example.com      example.net      example.org

核心思想非常简单:

不是让不同域名共享 Cookie,而是让不同系统共享同一个认证中心。


3. 最经典的 SSO 流程

例如用户第一次访问:

text 复制代码
https://order.example.net

订单系统发现:

text 复制代码
当前没有自己的登录态

于是:

text 复制代码
订单系统
   ↓
跳转认证中心
   ↓
认证中心检查自己的登录态

假设认证中心发现:

text 复制代码
用户已经登录

那么它就不需要用户重新输入账号密码。然后给订单系统一个一次性的认证结果。

以经典 CAS 为例,这个结果就是:

text 复制代码
ticket

完整过程:

text 复制代码
用户
 │
 │ 访问订单系统
 ▼
订单系统
 │
 │ 没有本地 Session
 ▼
认证中心
 │
 │ 检查中央 Session
 │
 │ 已登录
 ▼
生成一次性 Ticket
 │
 │ 重定向回订单系统
 ▼
订单系统后端
 │
 │ 用 Ticket 服务端请求认证中心
 ▼
认证中心
 │
 │ 校验 Ticket
 │
 │ 返回用户身份
 ▼
订单系统
 │
 │ 创建自己的 Session
 ▼
用户登录成功

这里有一个特别重要的点:

Ticket 不是最终的登录 Token

不要简单理解成:

text 复制代码
认证中心
    ↓
把用户 Token 放 URL
    ↓
订单系统直接使用

经典 CAS 更像:

text 复制代码
认证中心
    ↓
给一个一次性 Ticket
    ↓
订单系统后端
    ↓
拿 Ticket 找认证中心兑换用户身份
    ↓
创建自己的 Session

所以 Ticket 更接近:

"这次认证已经成功了,你拿着这个凭证来找认证中心确认。"

而不是:

"这是用户以后一直使用的登录 Token。"

现代 Web 更常见的是 OAuth 2.0 + OIDC

text 复制代码
子系统
  ↓
跳转认证中心
  ↓
认证中心发现用户已登录
  ↓
返回 Authorization Code
  ↓
子系统后端拿 Code 换 Token
  ↓
OIDC 返回 ID Token / 用户身份
  ↓
子系统建立自己的登录态

这里的核心变化是:

text 复制代码
CAS:
Ticket → 换用户信息

OIDC:
Authorization Code → 换 ID Token + Access Token

认证中心到底怎么知道回哪个子系统?

子系统发起登录时,会带上自己的身份和回调地址:

text 复制代码
订单系统
   ↓
认证中心
   │
   ├── client_id=order
   └── redirect_uri=https://order.example.com/callback

client_id 告诉认证中心:

"我是哪个子系统。"

redirect_uri 告诉认证中心:

"登录成功后回哪里。"

认证中心通常会提前登记:

text 复制代码
order
  → https://order.example.com/callback

dashboard
  → https://dashboard.example.com/callback

所以认证中心不是自己猜,而是根据 client_id 找到对应的回调地址,并校验 redirect_uri 是否匹配。

最终完整流程

text 复制代码
用户访问子系统
       │
       ▼
子系统发现没登录
       │
       │ client_id
       │ redirect_uri
       ▼
   认证中心
       │
       ├── 已登录 ─────────────┐
       │                      │
       └── 未登录              │
             ↓                │
          用户登录             │
             ↓                │
        建立中央 Session       │
             └───────┬────────┘
                     ↓
             Ticket / Code
                     ↓
                跳回子系统
                     ↓
             子系统后端校验
                     ↓
              获取用户身份
                     ↓
             建立本地 Session
                     ↓
                 自动登录

4. 为什么 Ticket 要一次性?

这是这道题很容易继续追问的地方。假设 URL 是:

text 复制代码
https://order.example.net/callback?ticket=ST-123456

如果这个 Ticket 永久有效,就存在明显风险。攻击者拿到:

text 复制代码
ST-123456

以后可能再次拿它换取用户身份。所以通常要求:

text 复制代码
Ticket
  ↓
只能使用一次
  ↓
认证中心校验成功
  ↓
立即失效

于是:

text 复制代码
第一次使用:

Ticket → 校验成功 → 返回用户信息 → Ticket 失效

第二次使用:

Ticket → 校验失败

这就是典型的防重放思路。


5. 为什么不直接把 Token 放 URL?

面试官很可能继续问:

"那我直接把 Token 放 URL 里不就行了吗?"

例如:

text 复制代码
https://order.example.net?token=eyJ...

这种方案风险比较大。因为 URL 可能出现在:

text 复制代码
浏览器历史记录
Referer
访问日志
代理日志
监控系统
截图
用户复制分享的链接

所以:

text 复制代码
长期有效 Token
        ↓
直接放 URL

通常是不合适的。更合理的是:

text 复制代码
URL
 ↓
短生命周期、一次性的认证凭证
 ↓
后端服务端校验
 ↓
创建本地登录 Session

6. 前端项目和后端渲染项目有什么区别?

这是一个非常好的追问。假设订单系统根本不是 React/Vue:

text 复制代码
Java Spring MVC

而是:

text 复制代码
浏览器
   ↓
Java 服务
   ↓
HTML

它一样可以做 SSO。流程甚至更简单:

text 复制代码
浏览器
  ↓
访问 Java 系统
  ↓
Java 发现没有 Session
  ↓
302 Redirect
  ↓
认证中心
  ↓
认证中心发现已经登录
  ↓
302 Redirect
  ↓
Java 系统 callback
  ↓
Java 后端拿 ticket
  ↓
服务端调用认证中心
  ↓
获取用户身份
  ↓
创建 Java Session
  ↓
Set-Cookie: JSESSIONID=xxx
  ↓
返回页面

所以:

SSO 本质上不是前端路由问题,而是认证系统之间如何建立信任和传递认证结果的问题。

这一点非常重要。


7. 如果是现代 SPA 呢?

如果子系统是:

text 复制代码
React
Vue
Angular

现代项目更常见的方案不一定是 CAS。还可能使用:

text 复制代码
OAuth 2.0
OIDC

尤其是:

text 复制代码
OIDC = OpenID Connect

可以理解成:

text 复制代码
OAuth 2.0
    +
用户身份认证
    ↓
OIDC

所以面试时最好不要说:

"不同一级域名必须使用 CAS。"

这个说法太绝对。更准确的是:

跨一级域名不能靠 Cookie 共享解决,需要引入中央认证中心;具体可以使用 CAS、OIDC 等标准协议。

这个回答明显更高级。


8. state 到底干什么?

如果继续追问 OIDC / OAuth:

text 复制代码
为什么要 state?

不要简单回答:

"state 防 CSRF。"

可以进一步说:

text 复制代码
客户端发起认证请求
        ↓
生成随机 state
        ↓
保存本地
        ↓
带 state 跳转认证中心
        ↓
认证中心完成认证
        ↓
回调携带 state
        ↓
客户端校验两边 state 是否一致

例如:

text 复制代码
客户端保存:

state = abc123

跳转:

text 复制代码
/auth?
  client_id=xxx
  &state=abc123

回调:

text 复制代码
/callback?
  code=xxx
  &state=abc123

然后:

js 复制代码
if (callbackState !== savedState) {
  reject();
}

核心目的就是:

确认这次回调确实对应当前客户端发起的那次认证请求,避免攻击者伪造认证流程。

所以可以说它用于防 CSRF,但不要把 state 简化成"一个普通防 CSRF Token"。


9. 用户退出登录怎么办?

这又是 SSO 的另一半:

登录统一,退出也要考虑统一。

假设:

text 复制代码
认证中心
   │
   ├── 主系统 Session
   ├── 订单系统 Session
   └── 数据看板 Session

用户在主系统:

text 复制代码
点击退出

不能只做:

text 复制代码
删除主系统 Cookie

否则可能出现:

text 复制代码
主系统:已退出 ❌

订单系统:还登录着 ✅

数据看板:还登录着 ✅

这就不是完整的 SSO 退出。


10. 统一登出怎么做?

一种典型架构是:

text 复制代码
用户
 ↓
主系统 Logout
 ↓
认证中心
 ↓
销毁中央登录态
 ↓
通知各个子系统
 ↓
子系统销毁自己的 Session

例如:

text 复制代码
                认证中心
                   │
          全局 Session 删除
                   │
       ┌───────────┼───────────┐
       ↓           ↓           ↓
    主系统       订单系统     数据看板
       ↓           ↓           ↓
    Session       Session      Session
      删除          删除         删除

通知子系统的具体方式可以有很多:

text 复制代码
HTTP 回调
消息队列
事件总线
前端重定向退出
标准协议提供的 Logout 机制

不能简单说:

"统一登出就是 MQ 广播。"

因为 MQ 只是一种工程实现方式,不是 SSO 的必然要求。


11. 这道题真正考什么?

其实不是考:

text 复制代码
Cookie 怎么设置?

而是考你能不能把这个问题拆开:

text 复制代码
登录态在哪里保存?
        ↓
不同系统之间能不能共享 Cookie?
        ↓
不能共享怎么办?
        ↓
有没有中央认证中心?
        ↓
认证结果怎么安全传递?
        ↓
怎么防重放?
        ↓
怎么防 CSRF?
        ↓
子系统怎么建立自己的 Session?
        ↓
用户退出后怎么同步?

这才是这道题的核心。


12. 面试官继续追问链

这道题可以一路追到很深:

text 复制代码
主系统登录后,子系统怎么自动登录?
        ↓
为什么不能直接共享 Cookie?
        ↓
什么情况下 Cookie 可以共享?
        ↓
不同一级域名怎么办?
        ↓
CAS 是什么?
        ↓
CAS Ticket 和 Token 有什么区别?
        ↓
为什么 Ticket 必须一次性?
        ↓
为什么不能直接把 Token 放 URL?
        ↓
state 是干什么的?
        ↓
OAuth 2.0 和 OIDC 什么关系?
        ↓
SPA 和后端渲染项目分别怎么接?
        ↓
Session 和 Token 怎么选?
        ↓
用户退出后怎么实现统一登出?
        ↓
多个子系统怎么通知?
        ↓
如果认证中心挂了怎么办?
        ↓
如果 Ticket 被截获怎么办?
        ↓
SSO 和权限控制有什么区别?

其中最后两个尤其容易拉开水平。


13. 一个面试时可以直接说的版本

第一段先说这个,不要一上来背 CAS 全流程:

主系统登录后,子系统能不能自动登录,关键看它们能不能共享登录态。同一父域名下可以考虑 Cookie 共享;如果是不同一级域名,就不能直接共享 Cookie,而是让多个系统依赖统一认证中心。子系统发现自己没登录,就跳到认证中心,认证中心确认用户已经登录后返回一次性的认证凭证,子系统后端再向认证中心校验并建立自己的登录态。

如果面试官继续追问 CAS:

经典 CAS 里,这个一次性凭证就是 Ticket。Ticket 通过浏览器带回子系统,但子系统不会直接信任它,而是由后端拿 Ticket 去认证中心校验,成功后创建自己的 Session。Ticket 用一次就失效,这样可以降低重放风险。

如果继续问统一登出:

退出登录也不能只删主系统自己的 Session,而是要先销毁认证中心的全局登录态,再通知各个子系统清理自己的局部 Session,这样才能真正做到单点登录和统一登出。

这三段基本就是这道题的主干答案。

相关推荐
许彰午1 小时前
05-静默安装常见报错与解决
数据库
ShineWinsu1 小时前
对于Redis:缓存的解析
java·redis·缓存·缓存穿透·缓存击穿·缓存雪崩·缓存预热
害人终害己1 小时前
redis修改密码的地方在哪里
数据库·redis·缓存
rannn_1111 小时前
【Java面试题】高频面试题1|Java 后端、MySQL、Redis 与 RAG 面试整理
java·jvm·数据库·后端·面试
写后端的胖头鱼1 小时前
一文详解Spring 事务失效场景
java·spring·事务·transactional·事务传播机制
Q26433650231 小时前
【有源码】基于Spring Boot和Vue的在线考试与交流系统-教育信息化背景下在线考试与交流一体化平台
java·vue.js·spring boot·mysql·spring·毕业设计·课程设计
谢亮_vipxieliang1 小时前
Go 并发安全性保障
服务器·网络·golang
杨云龙UP1 小时前
Oracle 19c RAC到RAC Active Data Guard标准搭建与巡检指南
运维·服务器·数据库·oracle·adg·data guard·rac到rac
Mikko71 小时前
JVM 线上排查实战(七):jps 看不到进程、jstack 报不允许的操作怎么办?attach 失败的六种情况实测
java·运维·jvm·后端