用 Python 实现一个简化但可运行的 单点登录(SSO)系统
好的!下面我将用 Python 实现一个简化但可运行的 单点登录(SSO)系统,其中:
- A 系统 作为 身份提供商(IdP)
- B 系统 作为 服务提供商(SP)
- 用户在 A 登录后,点击链接跳转到 B,B 自动登录,无需输入密码
我们将使用:
- Flask 作为 Web 框架
- JWT(JSON Web Token) 作为 SSO 令牌
- 加密签名 保证令牌不可伪造
- HTTPS(本地用 HTTP 演示,生产务必用 HTTPS)
🧩 系统架构
用户浏览器│├── 访问 A 系统 → 登录 → 获得 session│└── 点击 "打开 B 系统" → 跳转到 A 的 /sso?target=B→ A 生成 JWT → 重定向到 B 的 /sso?token=xxx→ B 验证 JWT → 创建本地会话 → 展示页面
第一步:A 系统(IdP) — idp.py
# idp.py - A 系统(身份提供商)
from flask import Flask, request, redirect, url_for, session, render_template_string
import jwt
import time
import urllib.parseapp = Flask(__name__)
app.secret_key = 'super-secret-key-for-session' # 用于 Flask session# JWT 签名密钥(A 和 B 必须共享!生产环境应使用安全存储)
JWT_SECRET = 'sso-shared-secret-2025'# 模拟用户数据库
USERS = {'alice': 'password123','bob': 'securepass'
}# A 系统首页(登录页)
@app.route('/')
def home():if 'user' in session:return render_template_string('''<h2>A 系统 - 已登录:{{ user }}</h2><a href="/sso?target={{ b_url }}">👉 打开 B 系统(SSO)</a><form method="post" action="/logout"><button>退出登录</button></form>''', user=session['user'], b_url='http://localhost:5001')return render_template_string('''<h2>A 系统 - 登录</h2><form method="post" action="/login">用户名: <input name="username"><br>密码: <input name="password" type="password"><br><button type="submit">登录</button></form>''')@app.route('/login', methods=['POST'])
def login():username = request.form['username']password = request.form['password']if USERS.get(username) == password:session['user'] = usernamereturn redirect('/')return '登录失败!', 401@app.route('/logout', methods=['POST'])
def logout():session.pop('user', None)return redirect('/')# SSO 接口:生成 JWT 并重定向到目标系统
@app.route('/sso')
def sso():if 'user' not in session:return redirect('/') # 未登录则跳回首页target = request.args.get('target')if not target:return '缺少 target 参数', 400# 构造 JWT payloadpayload = {'sub': session['user'], # 用户名'iss': 'A-System', # 签发者'exp': int(time.time()) + 60, # 60秒过期(生产可设为 30~120 秒)'iat': int(time.time()),'target': target # 可选:限制目标}# 生成 JWTtoken = jwt.encode(payload, JWT_SECRET, algorithm='HS256')# 重定向到 B 系统的 SSO 入口redirect_url = f"{target}/sso?token={urllib.parse.quote(token)}"return redirect(redirect_url)if __name__ == '__main__':# A 系统运行在 5000 端口app.run(port=5000, debug=True)
第二步:B 系统(SP) — sp.py
# sp.py - B 系统(服务提供商)
from flask import Flask, request, session, redirect, render_template_string
import jwt
import time
import urllib.parseapp = Flask(__name__)
app.secret_key = 'b-system-secret-session-key'# 必须与 A 系统的 JWT_SECRET 一致!
JWT_SECRET = 'sso-shared-secret-2025'@app.route('/')
def home():if 'user' in session:return render_template_string('''<h2>B 系统 - 欢迎 {{ user }}!</h2><p>✅ 你已通过 A 系统单点登录成功!</p><form method="post" action="/logout"><button>退出 B 系统</button></form>''', user=session['user'])else:return '''<h2>B 系统 - 未登录</h2><p>请通过 A 系统访问本系统。</p><a href="http://localhost:5000">前往 A 系统登录</a>'''# SSO 入口:接收并验证 JWT
@app.route('/sso')
def sso():token = request.args.get('token')if not token:return '缺少 token', 400try:# 解码并验证 JWTpayload = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])# 检查是否过期(jwt.decode 自动验证 exp)# 可选:检查 iss 是否为 'A-System'if payload.get('iss') != 'A-System':return '无效签发者', 403# 可选:检查 target 是否匹配当前系统# target = payload.get('target')# if not target or 'localhost:5001' not in target:# return '目标不匹配', 403# 创建 B 系统本地会话session['user'] = payload['sub']return redirect('/')except jwt.ExpiredSignatureError:return 'SSO 令牌已过期', 401except jwt.InvalidTokenError:return '无效的 SSO 令牌', 403@app.route('/logout', methods=['POST'])
def logout():session.pop('user', None)return redirect('/')if __name__ == '__main__':# B 系统运行在 5001 端口app.run(port=5001, debug=True)
第三步:运行与测试
1. 安装依赖
pip install flask PyJWT
2. 启动两个系统
# 终端 1
python idp.py # A 系统,http://localhost:5000# 终端 2
python sp.py # B 系统,http://localhost:5001
3. 测试流程
- 打开浏览器访问
http://localhost:5000
- 用用户名
alice
、密码password123
登录 A 系统 - 点击 “打开 B 系统(SSO)” 链接
- 自动跳转到 B 系统,并显示:
B 系统 - 欢迎 alice!
✅ 你已通过 A 系统单点登录成功!
✅ 全程无需在 B 系统输入用户名密码!
🔒 安全增强建议(生产环境)
- 使用 HTTPS:防止 token 被窃听(本地开发可用
mkcert
生成证书) - JWT 密钥安全存储:不要硬编码,使用环境变量或密钥管理服务
- 缩短 token 有效期:建议 30~60 秒
- 绑定用户上下文(可选):
- 在 JWT 中加入
jti
(唯一 ID)防止重放 - 绑定 IP 或 User-Agent(但可能影响移动设备体验)
- 在 JWT 中加入
- 验证 target 参数:防止 token 被用于其他系统
- 日志审计:记录 SSO 登录事件
🔄 扩展方向
- 支持多个 SP(C、D 系统):只需共享 JWT_SECRET 并实现
/sso
接口 - 添加单点登出(SLO):A 登出时通知所有 SP
- 使用 OpenID Connect 替代自定义 JWT(更标准)
这个实现虽然简化,但完整展示了 A 作为 IdP 实现 SSO 的核心逻辑。你可以在此基础上扩展为生产级系统。需要我帮你加上 HTTPS、数据库用户管理或 OIDC 支持吗?