单点登录系统的原理如何用代码体现?

wen java案例 65

本文目录导读:

单点登录系统的原理如何用代码体现?

  1. 核心原理流程图解(文字版)
  2. 代码实现核心环节(伪代码 + 关键逻辑)
  3. 最终用户视角的代码体现(浏览器)
  4. 代码体现的核心原则

单点登录(SSO)的核心原理是在一个独立的认证中心进行一次登录,即可访问所有相互信任的应用系统

要理解其代码体现,我们先拆解其核心思想:Cookie 共享 + Token 验证

最常见的 SSO 实现是基于中央认证服务(CAS,Central Authentication Service) 的模式,下面用最简化的伪代码和逻辑流程,来展示其核心原理。


核心原理流程图解(文字版)

  1. 首次访问 App1:用户未登录 -> 重定向到 SSO 登录页。
  2. 在 SSO 登录:输入账号密码 -> SSO 服务器验证成功。
  3. SSO 发放票据
    • 生成一个全局会话(Global Session),并设置一个 SSO Cookie(如 sso_token)到 sso.com 域。
    • 生成一个 ST(Service Ticket,服务票据),并附加在 URL 上跳转回 App1。
  4. App1 验证 ST:App1 拿着 ST 去 SSO 服务器验证,验证通过,建立 App1 的本地会话(Local Session),设置 App1 域的 Cookie。
  5. 访问 App2:用户点击 App2,发现没有 App2 的本地会话。
  6. App2 重定向到 SSO:SSO 服务器检查到用户浏览器有 sso_token(来自第一步),验证全局会话有效。
  7. SSO 直接发放 Ticket:无需再次输入密码,SSO 直接生成一个新的 ST 跳转回 App2。
  8. App2 验证 ST:流程同第4步,建立 App2 本地会话。

代码实现核心环节(伪代码 + 关键逻辑)

为了方便理解,我们假设有三个服务:

  • sso-server.com:认证中心
  • app1.com:应用1
  • app2.com:应用2
  • user:浏览器用户

环节1:SSO 服务器(认证中心)

SSO 服务器是整个系统的核心,负责管理全局会话和发放票据。

# -*- coding: utf-8 -*-
# 假设这是一个 Python Flask 服务,运行在 sso-server.com
import uuid
import time
# 存储:全局 Session 和 Ticket
# 生产环境会用 Redis
global_sessions = {}  # key: sso_token, value: {user_id, login_time}
ticket_storage = {}   # key: service_ticket, value: {user_id, service_url, timestamp}
@app.route('/login', methods=['GET', 'POST'])
def login():
    # 1. 判断是否已经登录(检查 SSO Cookie)
    sso_token = request.cookies.get('sso_token')
    if sso_token and sso_token in global_sessions:
        # 已经登录,直接颁发 ST(Service Ticket)并跳转回原应用
        service_url = request.args.get('service')  # app1.com/callback
        st = str(uuid.uuid4())
        ticket_storage[st] = {'user_id': global_sessions[sso_token]['user_id'], 'service_url': service_url, time.time()}
        # 重定向到: app1.com/callback?ticket=xxxx
        return redirect(f'{service_url}?ticket={st}')
    # 2. 未登录,显示登录表单(POST 请求处理)
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        # 验证密码(省略具体验证逻辑)
        if authenticate(username, password):
            # 创建全局 Session
            new_sso_token = str(uuid.uuid4())
            global_sessions[new_sso_token] = {'user_id': username, 'login_time': time.time()}
            # 创建 ST(Service Ticket)
            service_url = request.args.get('service')
            st = str(uuid.uuid4())
            ticket_storage[st] = {'user_id': username, 'service_url': service_url, 'timestamp': time.time()}
            # 设置 SSO Cookie (Domain=sso-server.com)
            resp = redirect(f'{service_url}?ticket={st}')
            resp.set_cookie('sso_token', new_sso_token, domain='sso-server.com', httponly=True)
            return resp
    # 3. 显示登录页
    return render_template('login.html')
@app.route('/validate')  # 提供给应用服务器验证 ST 的接口
def validate_ticket():
    # app1 带着 ticket 和 自己的 URL 来验证
    ticket = request.args.get('ticket')
    service = request.args.get('service')
    stored_ticket = ticket_storage.pop(ticket, None)  # 一次性使用
    if stored_ticket and stored_ticket['service_url'] == service:
        # 验证成功,返回用户信息(如 user_id)
        return jsonify({'success': True, 'user_id': stored_ticket['user_id']})
    else:
        return jsonify({'success': False}), 401
@app.route('/logout')
def logout():
    sso_token = request.cookies.get('sso_token')
    if sso_token in global_sessions:
        del global_sessions[sso_token]
    # 清除 SSO Cookie
    resp = redirect('http://sso-server.com/login')
    resp.set_cookie('sso_token', '', expires=0, domain='sso-server.com')
    return resp

关键点解释

  • 全局 Session(global_sessions:用户登录后,服务器记得此人。
  • SSO Cookie(sso_token:浏览器记住“我在 SSO 登录了”。注意 domain 必须一致(如都设为 .sso-server.com)。
  • ST(Service Ticket):一次性、有时效、短字符串,防止重放攻击。

环节2:应用服务器(App1 / App2)

应用服务器负责拦截未登录请求,与 SSO 服务器交互。

# -*- coding: utf-8 -*-
# 假设这是一个 Flask 服务,运行在 app1.com
import requests
# 存储本地的用户会话
local_sessions = {}  # key: app_local_token, value: {user_id}
@app.route('/callback')  # 接收 SSO 跳转回来的 Ticket
def callback():
    ticket = request.args.get('ticket')
    # 1. 拿着 ticket 去 SSO 服务器验证
    validate_url = f'http://sso-server.com/validate?ticket={ticket}&service={request.url}'
    response = requests.get(validate_url)
    if response.json().get('success'):
        user_id = response.json()['user_id']
        # 2. 验证成功,创建本地会话
        app_local_token = str(uuid.uuid4())
        local_sessions[app_local_token] = {'user_id': user_id}
        # 3. 设置本地 Cookie (Domain=app1.com)
        resp = redirect('http://app1.com/dashboard')
        resp.set_cookie('app_session', app_local_token, domain='app1.com', httponly=True)
        return resp
    else:
        return "验证失败", 401
@app.route('/dashboard')
def dashboard():
    # 受保护的页面,检查本地会话
    app_session = request.cookies.get('app_session')
    if app_session in local_sessions:
        return f"欢迎 {local_sessions[app_session]['user_id']} 来到 App1"
    else:
        # 没有本地会话,重定向到 SSO 登录
        # Redirect to: http://sso-server.com/login?service=http://app1.com/callback
        return redirect(f'http://sso-server.com/login?service={request.url}')

关键点解释

  • 重定向引导:未登录 -> 跳到 SSO -> 带 Ticket 回 callback -> 建立本地会话。
  • 本地 Cookie vs SSO Cookieapp_session 用于 App1 业务;sso_token 用于全局认证,不同域名。
  • 验证委托:App1 不完全信任浏览器给的 Ticket,一定要去 SSO 服务器后台验证(防止伪造)。

最终用户视角的代码体现(浏览器)

浏览器在这个过程中,Cookie 的 Domain 和 HttpOnly 属性至关重要。

---
// 步骤1:访问 app1.com
// 浏览器有 app1.com 的 Cookie 吗? 没有。
// 步骤2:重定向到 sso-server.com/login?service=app1.com/callback
// 浏览器有 sso-server.com 的 Cookie 吗? 没有。
// 步骤3:用户输入密码,提交 POST 到 sso-server.com
// SSO 服务器返回:
//   Set-Cookie: sso_token=abc123; Domain=sso-server.com; HttpOnly; Path=/
//   并重定向到 app1.com/callback?ticket=st_xxx
// 步骤4:浏览器访问 app1.com/callback?ticket=st_xxx
// 浏览器自动带上 app1.com 的 Cookie (此时还没有)
// app1 服务器验证 ticket,返回:
//   Set-Cookie: app_session=xyz789; Domain=app1.com; HttpOnly; Path=/
//   并重定向到 app1.com/dashboard
// 步骤5:用户首次访问 app2.com
// 浏览器检查 app2.com 的 Cookie? 没有。
// app2 重定向到 sso-server.com/login?service=app2.com/callback
// 浏览器访问 sso-server.com,自动带上 Cookie: sso_token=abc123
// SSO 服务器验证 sso_token 有效,直接返回 Ticket,免登录!

代码体现的核心原则

  1. 使用 Cookie 传递 Tokensso_token(全局)和 app_session(本地)的存在是 SSO 工作的基础。
  2. Token 验证发生在服务器端validate_ticket() 是安全核心,防止客户端伪造。
  3. 重定向是 SSO 的“传输层”302 Redirect 是连接不同应用的桥梁。
  4. 本地会话隔离:每个应用有自己的 local_sessions,即使 SSO 服务器挂了,已登录的应用短期内仍可正常工作。
  5. 一次性票据ticket 用完即销毁,防止重放攻击。

实际生产中的挑战(超出基础代码范围但必须考虑):

  • 跨域问题:不同域名如何共享 sso_token?解决方案有:子域名共用父域 Cookie、iframe+postMessage、OAuth2/OpenID Connect 的授权码模式。
  • 安全问题:CSRF(跨站请求伪造)、XSS(跨站脚本攻击)防范;HTTPS 必须;Token 过期机制。
  • Session 分布式global_sessionsticket_storage 必须使用 Redis 等分布式缓存,否则单点故障。

简单一句话总结SSO 的代码体现就是“统一认证 + 票据传递 + 本地会话创建”的三层协作,核心的工具是 Cookie 和重定向,核心的规则是“服务器端验证,不信任客户端”

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