当企业内部系统数量逐渐增多时,每个系统独立的登录模块不仅造成开发资源浪费,更导致用户体验割裂。想象一下,员工每天需要在十几个系统间反复输入账号密码的场景——这不仅降低工作效率,还增加了密码泄露风险。本文将带你深入理解单点登录(SSO)的核心机制,并基于SpringBoot构建一个完整可落地的认证中心解决方案。
单点登录的本质在于一次认证,全网通行。其核心在于解耦认证与授权过程,通过集中式的认证中心协调各子系统间的信任关系。典型的SSO系统包含以下核心组件:
mermaid复制sequenceDiagram
participant User
participant Client
participant SSO Server
User->>Client: 访问受限资源
Client->>SSO Server: 重定向到登录页
User->>SSO Server: 提交凭证
SSO Server->>User: 颁发Token并重定向回Client
User->>Client: 携带Token访问
Client->>SSO Server: 验证Token有效性
SSO Server->>Client: 返回验证结果
Client->>User: 创建局部会话并返回资源
注意:实际生产中建议使用JWT等标准化令牌格式,而非示例中的UUID简单实现
为模拟多域名场景,需配置本地hosts文件(位置:/etc/hosts或C:\Windows\System32\drivers\etc\hosts):
bash复制# SSO 演示环境配置
127.0.0.1 auth.company.com
127.0.0.1 hr.company.com
127.0.0.1 crm.company.com
核心Maven依赖(SpringBoot 2.7.x):
xml复制<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Thymeleaf模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Lombok简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
在分布式环境中,会话管理有几种常见方案:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Session复制 | 实现简单 | 内存消耗大,扩展性差 | 小型集群 |
| 集中存储(Redis) | 扩展性好,一致性高 | 增加外部依赖 | 中大型分布式系统 |
| Token无状态 | 完全无状态,扩展性最佳 | 实现复杂,令牌撤销困难 | 微服务架构 |
本示例采用Session结合内存存储的简化方案:
java复制public class SSOSessionManager {
private static final ConcurrentHashMap<String, String> TOKEN_STORE =
new ConcurrentHashMap<>();
public static String createToken(User user) {
String token = UUID.randomUUID().toString();
TOKEN_STORE.put(token, user.getId());
return token;
}
public static boolean validateToken(String token) {
return TOKEN_STORE.containsKey(token);
}
}
java复制@PostMapping("/login")
public String handleLogin(@Valid LoginForm form,
HttpSession session,
RedirectAttributes attributes) {
// 1. 身份验证(生产环境应使用加密校验)
User user = userService.authenticate(form.getUsername(),
form.getPassword());
if (user == null) {
model.addAttribute("error", "Invalid credentials");
return "login";
}
// 2. 创建全局会话
String token = SSOSessionManager.createToken(user);
session.setAttribute("sso_token", token);
// 3. 处理重定向逻辑
if (StringUtils.isNotBlank(form.getRedirectUrl())) {
attributes.addAttribute("token", token);
return "redirect:" + form.getRedirectUrl();
}
return "redirect:/dashboard";
}
java复制@GetMapping("/verify")
@ResponseBody
public ResponseEntity<Boolean> verifyToken(
@RequestParam String token,
@RequestParam String clientId,
HttpServletRequest request) {
// 记录客户端注册信息(用于统一登出)
ClientRegistry.registerClient(token, clientId,
request.getSession().getId());
return ResponseEntity.ok(SSOSessionManager.validateToken(token));
}
客户端需要实现的核心功能:
典型拦截器实现:
java复制public class SSOInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
HttpSession session = request.getSession();
// 检查局部会话
if (session.getAttribute("user") != null) {
return true;
}
// 处理认证中心回调
String token = request.getParameter("token");
if (StringUtils.isNotBlank(token)) {
if (verifyTokenWithSSO(token)) {
createLocalSession(token, session);
return true;
}
}
// 重定向到认证中心
String redirectUrl = buildSSOLoginUrl(request);
response.sendRedirect(redirectUrl);
return false;
}
private boolean verifyTokenWithSSO(String token) {
// 调用认证中心验证接口
// 实现HTTP客户端调用逻辑
}
}
生产环境应考虑:
登出流程的特殊性在于需要同时销毁全局会话和所有相关的局部会话。我们通过Session监听器实现:
java复制@WebListener
public class SSOSessionListener implements HttpSessionListener {
@Override
public void sessionDestroyed(HttpSessionEvent se) {
String token = (String) se.getSession()
.getAttribute("sso_token");
if (token != null) {
// 1. 清理token存储
SSOSessionManager.invalidateToken(token);
// 2. 通知所有客户端登出
ClientRegistry.getClients(token).forEach(client -> {
notifyClientLogout(client);
});
}
}
private void notifyClientLogout(ClientInfo client) {
// 实现HTTP调用通知客户端
}
}
| 组件 | 保障措施 | 故障转移方案 |
|---|---|---|
| 认证中心 | 集群部署 + 负载均衡 | DNS轮询 + 健康检查 |
| Redis | 哨兵模式或集群模式 | 自动故障检测与切换 |
| 客户端SDK | 本地缓存 + 退避重试 | 降级为独立登录模式 |
关键监控项应包括:
使用Spring Boot Actuator暴露相关指标:
yaml复制management:
endpoints:
web:
exposure:
include: health,metrics,sso-stats
metrics:
tags:
application: ${spring.application.name}
在实现SSO系统的过程中,最容易被忽视的是会话状态的同步问题。曾经在一个金融项目中,我们发现由于网络分区导致登出信号未能及时传播,使得部分子系统会话残留。最终通过引入二次验证机制(定期向认证中心确认会话状态)解决了这个问题。这也提醒我们,分布式环境下的状态一致性需要特别关注。