# 跨域单点登录解决方案分析
## 问题解构与需求分析
### 1. 问题背景
用户有两个后台管理系统:
- `https://admin-backend.leap.ling.space`
- `https://lingos-admin-v.mo.ling.space`
这两个系统属于**不同域名**,且技术栈可能为:
- 一个确定是 Java 项目
- 另一个可能是 Java 或 Python 项目
### 2. 核心挑战
实现跨域单点登录面临的主要技术难点:
| 挑战维度 | 具体问题 | 影响程度 |
|---------|---------|---------|
| 域名差异 | 两个系统完全不同的域名 | ⭐⭐⭐⭐⭐ |
| 技术异构 | 可能涉及 Java 和 Python 混合技术栈 | ⭐⭐⭐⭐ |
| Cookie 限制 | 浏览器 SameSite 策略限制跨域 Cookie | ⭐⭐⭐⭐⭐ |
| 会话管理 | 需要统一的会话状态管理 | ⭐⭐⭐⭐ |
## 解决方案推演
### 方案一:基于认证中心的标准化 SSO
这是**推荐的首选方案**,特别适合跨域和技术异构场景 [ref_1]。
#### 架构设计
```
用户浏览器
↓
应用系统A (Java) → 认证中心 (独立服务)
↓
应用系统B (Java/Python) → 认证中心 (独立服务)
```
#### 具体实现步骤
**1. 认证中心搭建**
```java
// 认证中心核心代码示例 (Java Spring Boot)
@RestController
@RequestMapping("/sso")
public class SSOAuthController {
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
// 验证用户凭证
User user = userService.authenticate(request.getUsername(), request.getPassword());
if (user != null) {
// 生成 JWT Token
String token = JwtUtil.generateToken(user);
// 存储会话状态
redisTemplate.opsForValue().set(
"sso:session:" + token,
user.getId(),
Duration.ofHours(2)
);
return ResponseEntity.ok(new AuthResponse(token, "登录成功"));
}
return ResponseEntity.status(401).body(new AuthResponse(null, "认证失败"));
}
@GetMapping("/validate")
public ResponseEntity<ValidationResponse> validateToken(@RequestParam String token) {
// 验证 Token 有效性
if (JwtUtil.validateToken(token) &&
redisTemplate.hasKey("sso:session:" + token)) {
String userId = redisTemplate.opsForValue().get("sso:session:" + token);
User user = userService.findById(userId);
return ResponseEntity.ok(new ValidationResponse(true, user));
}
return ResponseEntity.ok(new ValidationResponse(false, null));
}
}
```
**2. Java 应用系统集成**
```java
// Java 系统拦截器
@Component
public class SSOInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 检查本地会话
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("user") != null) {
return true;
}
// 检查 Token
String token = getTokenFromRequest(request);
if (token != null) {
// 向认证中心验证 Token
ValidationResponse validation = ssoClient.validateToken(token);
if (validation.isValid()) {
// 创建本地会话
request.getSession().setAttribute("user", validation.getUser());
return true;
}
}
// 重定向到认证中心
String redirectUrl = buildSSOLoginUrl(request.getRequestURL().toString());
response.sendRedirect(redirectUrl);
return false;
}
private String getTokenFromRequest(HttpServletRequest request) {
// 从请求参数、Header 或 Cookie 中获取 Token
return request.getParameter("sso_token");
}
}
```
**3. Python 应用系统集成**
```python
# Python Flask 示例
from flask import Flask, request, redirect, session, jsonify
import requests
import jwt
app = Flask(__name__)
app.secret_key = 'your-secret-key'
SSO_CENTER = "https://sso-auth.yourdomain.com"
@app.before_request
def check_authentication():
# 检查本地会话
if 'user' in session:
return
# 检查 Token
token = request.args.get('sso_token') or request.cookies.get('sso_token')
if token:
# 向认证中心验证 Token
validation_url = f"{SSO_CENTER}/sso/validate"
response = requests.get(validation_url, params={'token': token})
if response.json().get('valid'):
user_data = response.json().get('user')
session['user'] = user_data
return
# 重定向到认证中心
sso_login_url = f"{SSO_CENTER}/sso/login?redirect_url={request.url}"
return redirect(sso_login_url)
@app.route('/callback')
def sso_callback():
token = request.args.get('token')
if token:
# 验证并创建本地会话
validation_url = f"{SSO_CENTER}/sso/validate"
response = requests.get(validation_url, params={'token': token})
if response.json().get('valid'):
session['user'] = response.json().get('user')
return redirect('/')
return "认证失败", 401
```
#### 技术优势对比
| 特性 | 认证中心方案 | 父域 Cookie 方案 | LocalStorage 方案 |
|------|-------------|------------------|------------------|
| 跨域支持 | ✅ 完美支持 | ❌ 仅同父域 | ✅ 支持 |
| 技术异构 | ✅ 语言无关 | ⚠️ 有限支持 | ✅ 前端主导 |
| 安全性 | ✅ 高 | ⚠️ 中等 | ⚠️ 依赖前端 |
| 扩展性 | ✅ 易于扩展 | ❌ 受限 | ✅ 良好 |
| 标准程度 | ✅ 行业标准 | ⚠️ 非标准 | ⚠️ 非标准 |
### 方案二:基于 JWT 的分布式方案
如果不想部署独立的认证中心,可以考虑基于 JWT 的轻量级方案。
#### JWT Token 结构示例
```java
// JWT Payload 示例
{
"userId": "12345",
"username": "admin",
"exp": 1719840000,
"iss": "sso-system",
"systems": ["admin-backend", "lingos-admin"]
}
```
#### 共享密钥验证
```python
# Python JWT 验证
import jwt
from datetime import datetime, timedelta
class JWTManager:
def __init__(self, secret_key):
self.secret_key = secret_key
def generate_token(self, user_data):
payload = {
**user_data,
'exp': datetime.utcnow() + timedelta(hours=2),
'iat': datetime.utcnow()
}
return jwt.encode(payload, self.secret_key, algorithm='HS256')
def validate_token(self, token):
try:
payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
```
## 技术选型建议
### 基于项目特点的推荐
| 考虑因素 | 推荐方案 | 理由 |
|---------|---------|------|
| 跨域需求 | 认证中心 SSO | 完美解决跨域问题 [ref_1] |
| 技术异构 | 认证中心 SSO | 语言无关,Java/Python 都可集成 |
| 长期维护 | 认证中心 SSO | 标准方案,易于扩展新系统 |
| 快速实现 | JWT 方案 | 部署简单,不需要独立服务 |
### 具体实施建议
**阶段一:认证中心部署**
1. 选择技术栈:推荐使用 Java Spring Boot(与现有系统技术栈一致)
2. 部署独立域名:`https://sso-auth.ling.space`
3. 实现核心功能:登录、验证、注销
**阶段二:系统集成**
1. Java 系统:添加拦截器,处理认证重定向
2. Python 系统:添加中间件,实现认证回调
3. 测试跨系统登录流程
**阶段三:安全加固**
1. 添加 HTTPS 强制
2. 实现 Token 刷新机制
3. 添加操作日志和监控
## 代码示例完整实现
### Java 系统完整配置
```java
// SSO 配置类
@Configuration
@EnableWebMvc
public class SSOConfig implements WebMvcConfigurer {
@Autowired
private SSOInterceptor ssoInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ssoInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**", "/static/**");
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
// SSO 客户端
@Component
public class SSOClient {
@Value("${sso.center.url}")
private String ssoCenterUrl;
@Autowired
private RestTemplate restTemplate;
public ValidationResponse validateToken(String token) {
String url = ssoCenterUrl + "/sso/validate?token=" + token;
return restTemplate.getForObject(url, ValidationResponse.class);
}
public String buildLoginUrl(String redirectUrl) {
return ssoCenterUrl + "/sso/login?redirect_url=" +
URLEncoder.encode(redirectUrl, StandardCharsets.UTF_8);
}
}
```
### Python 系统完整配置
```python
# config.py
import os
class Config:
SSO_CENTER_URL = os.environ.get('SSO_CENTER_URL', 'https://sso-auth.ling.space')
SECRET_KEY = os.environ.get('SECRET_KEY', 'your-secret-key-here')
# sso_client.py
import requests
from flask import current_app
class SSOClient:
@staticmethod
def validate_token(token):
try:
response = requests.get(
f"{current_app.config['SSO_CENTER_URL']}/sso/validate",
params={'token': token},
timeout=5
)
return response.json() if response.status_code == 200 else None
except requests.RequestException:
return None
@staticmethod
def get_login_url(redirect_url):
return (f"{current_app.config['SSO_CENTER_URL']}/sso/login"
f"?redirect_url={redirect_url}")
```
## 总结
针对用户的两个跨域后台管理系统,**基于认证中心的单点登录方案**是最佳选择。该方案具有以下优势:
1. **完美的跨域支持**:通过独立的认证中心解决不同域名的会话共享问题 [ref_1]
2. **技术栈无关**:无论第二个系统是 Java 还是 Python,都能顺利集成
3. **标准化实现**:符合行业标准,便于后续维护和扩展
4. **安全性高**:集中的认证逻辑,便于实施统一的安全策略
实施该方案需要部署一个独立的认证中心服务,然后在两个后台系统中分别集成 SSO 客户端逻辑。Java 系统可以通过拦截器实现,Python 系统可以通过中间件实现,确保用户体验的无缝衔接。