我们来从 Java开发者视角 深入讲清楚 Single Sign-On(SSO) 的 原理 + 流程图 + 常见实现方案 + 实战代码示例(基于 Spring Boot + JWT + Redis)。
🧠 一、什么是 Single Sign-On(SSO)
SSO(单点登录) 是一种身份认证机制,允许用户在多个独立系统之间一次登录、处处通行。
✅ 举例说明:
你在访问公司门户(portal.company.com)登录后,
再打开“工单系统(jira.company.com)”或“文档系统(wiki.company.com)”,
不需要再次输入账号密码,这就是 SSO 的效果。
⚙️ 二、SSO 的核心原理
SSO 的关键目标:
让认证中心(Auth Server)统一处理登录逻辑,
各个子系统(Client Apps)只负责验证 Token。
📈 典型架构图(简化)
┌───────────────────────────────┐
│ Authentication Server │
│ (SSO / OAuth2.0) │
│ /login /token │
└──────────┬────────────────────┘
│
① 用户在 A 系统登录,获得 token
│
▼
┌────────────────────┐
│ System A (portal) │───┐
└────────────────────┘ │
│ ② 访问其他系统时,携带 token
▼
┌────────────────────┐ │
│ System B (wiki) │◄──┘
└────────────────────┘
🔁 三、SSO 登录流程(逻辑步骤)
| 步骤 | 说明 |
|---|---|
| 1️⃣ | 用户访问受保护的系统(如 wiki.company.com) |
| 2️⃣ | 系统发现用户未登录,重定向到 SSO 登录中心 |
| 3️⃣ | 用户在 SSO 登录中心输入账号密码,登录成功 |
| 4️⃣ | SSO 生成一个全局 Ticket / Token,并将该凭证返回 |
| 5️⃣ | 业务系统使用该凭证向 SSO 服务器验证(后端对后端通信) |
| 6️⃣ | 验证通过后,业务系统为用户创建本地 Session(或 JWT) |
| 7️⃣ | 用户再次访问其他系统时,直接使用同一 Token,无需登录 |
🔑 四、主流实现方式
| 实现模式 | 说明 | 代表技术 |
|---|---|---|
| Cookie + Session | 同域名子系统共享 Cookie | 适合同域(如 a.company.com, b.company.com) |
| Token(JWT) | 跨域、移动端友好,服务端无状态 | OAuth2 / OpenID Connect |
| CAS(Central Authentication Service) | 学术界早期标准,支持多语言客户端 | Jasig CAS Server |
| OAuth2 / OIDC | 标准化协议,支持授权与认证 | Spring Authorization Server / Keycloak |
💡 五、SSO 实战实现(Spring Boot + JWT + Redis)
场景:我们要实现一个简单的 SSO 登录中心 + 两个业务系统。
🧩 1. 依赖配置(pom.xml)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
🧠 2. 登录中心 Controller 示例
@RestController
@RequestMapping("/sso")
public class SsoController {
private final JwtService jwtService;
private final StringRedisTemplate redisTemplate;
public SsoController(JwtService jwtService, StringRedisTemplate redisTemplate) {
this.jwtService = jwtService;
this.redisTemplate = redisTemplate;
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// 模拟验证账号密码
if ("admin".equals(request.getUsername()) && "123456".equals(request.getPassword())) {
String token = jwtService.generateToken(request.getUsername());
// 将 token 存入 Redis,设置有效期 30 分钟
redisTemplate.opsForValue().set("SSO:TOKEN:" + token, request.getUsername(), 30, TimeUnit.MINUTES);
return ResponseEntity.ok(Map.of("token", token));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
}
@GetMapping("/validate")
public ResponseEntity<?> validateToken(@RequestParam String token) {
boolean valid = jwtService.validateToken(token);
if (!valid) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
return ResponseEntity.ok(Map.of("username", jwtService.getUsername(token)));
}
}
🔐 3. JWT 工具类
@Service
public class JwtService {
private final String SECRET_KEY = "fancylab-secret-key";
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(Date.from(Instant.now().plusSeconds(1800)))
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()), SignatureAlgorithm.HS256)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(SECRET_KEY.getBytes()).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public String getUsername(String token) {
return Jwts.parserBuilder().setSigningKey(SECRET_KEY.getBytes())
.build().parseClaimsJws(token).getBody().getSubject();
}
}
🏗️ 4. 业务系统验证 Token 逻辑
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private JwtService jwtService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String token = request.getHeader("Authorization");
if (token == null || !jwtService.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 检查Redis
String username = redisTemplate.opsForValue().get("SSO:TOKEN:" + token);
if (username == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
request.setAttribute("username", username);
return true;
}
}
注册拦截器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login", "/public/**");
}
}
🔄 六、Token 与 Cookie 对比总结
| 特性 | Cookie/Session | Token (JWT) |
|---|---|---|
| 存储位置 | 服务端 | 客户端 |
| 跨域支持 | 较差 | 较好 |
| 状态 | 有状态 | 无状态 |
| 适用场景 | 同域 Web 系统 | 微服务 / 移动端 / 前后端分离 |
| 性能 | 需要 session 同步 | 验证轻量、可分布式 |
🚀 七、进阶方向
- 🔑 集成 OAuth2.1 + OIDC(Spring Authorization Server / Keycloak)
- 🧱 实现 统一登出(Single Logout):Redis 删除 token
- 🔄 Token 刷新机制(Refresh Token)
- 🧩 接入前端(Vue/iOS)实现跨系统登录态共享
🎯 八、总结一句话
SSO 本质就是让“身份认证”从每个系统中抽离出来,统一由独立的认证中心负责。
你可以通过 JWT + Redis + Spring Security / Authorization Server
快速实现一个安全、可扩展的企业级单点登录体系。









网友评论