本文目录导读:

- 核心原理(必知)
- 方案一:基于 Cookie + 共享 Session(最简单的同域名 SSO)
- 方案二:基于 JWT + 中央认证服务(最流行的跨域名 SSO)
- 方案三:基于 OAuth2 / CAS(企业级最强)
- 总结:如何选型?
- 关键安全提示
实现单点登录(SSO)在 Java 中是一个经典的企业级分布式系统问题,核心思想是一次登录,多系统通行。
由于“如何实现”这个问题范围较大,下面按照从简单到复杂、从原理到实践的顺序,给出几种主流的 Java 实现方案,并附上核心代码逻辑和架构图思路。
核心原理(必知)
所有 SSO 的实现都围绕着一个独立的认证中心(SSO Server)。
- 用户访问子系统 A,未登录 -> 重定向到 SSO Server。
- 用户输入账号密码,SSO Server 验证成功,创建全局会话(Global Session)并生成一个 Ticket。
- 浏览器携带 Ticket 跳回子系统 A,A 拿着 Ticket 去 SSO Server 验证。
- SSO Server 验证 Ticket 有效,通知 A 登录成功,A 创建自己的局部会话。
- 用户访问子系统 B,B 发现未登录 -> 重定向到 SSO Server。
- SSO Server 发现用户已有全局会话(通过 Cookie/Session),直接生成新 Ticket 并跳回 B,B 验证后登录。
基于 Cookie + 共享 Session(最简单的同域名 SSO)
适用场景: 多个子系统属于同一个顶级域名(如 a.example.com、b.example.com)。
实现思路:
- 将 Session 存储到 Redis 等中间件中(Session 共享)。
- 将 Cookie 的
Domain属性设置为.example.com。 - 所有子系统读取同一个 Redis Session 数据。
Java 核心步骤(以 Spring Boot + Redis 为例):
- 引入依赖:
spring-session-data-redis - 配置文件(application.yml):
spring: session: store-type: redis redis: host: 你的redis地址 server: servlet: session: cookie: domain: .example.com # 关键:顶级域名共享 - 所有子系统配置一样即可。
- 优点: 实现极简单,无需额外开发认证中心。
- 缺点: 无法跨顶级域名(如
a.com和b.com),Cookie 不安全(易被劫持,需配合 HTTPS)。
基于 JWT + 中央认证服务(最流行的跨域名 SSO)
适用场景: 跨不同域名、前后端分离、移动端。
核心组件:
- SSO Server(认证中心): 登录页面、Token 发放、JWT 生成与校验。
- Client 子系统(服务端): 拦截未登录请求,跳转 SSO Server。
- JWT 令牌: 携带用户身份信息,时效短,无状态。
Java 实现流程(伪代码 + 关键类):
SSO Server 登录接口
@RestController
@RequestMapping("/sso")
public class SSOAuthController {
@PostMapping("/login")
public Result login(String username, String password, HttpServletResponse response) {
// 1. 校验账号密码
User user = userService.authenticate(username, password);
if (user == null) {
return Result.error("登录失败");
}
// 2. 生成 JWT Token(使用 JJWT 或 io.jsonwebtoken 库)
String token = JwtUtils.createToken(user.getId(), user.getName(), 3600 * 1000); // 1小时过期
// 3. 将 token 写入 Cookie(HttpOnly, SameSite=Lax)或直接返回给前端
Cookie cookie = new Cookie("SSO_TOKEN", token);
cookie.setPath("/");
cookie.setDomain("sso.com"); // 仅认证中心的域名
cookie.setHttpOnly(true);
cookie.setSecure(true); // 强制 HTTPS
response.addCookie(cookie);
// 4. 重定向回子系统:携带临时票据 code
String code = UUID.randomUUID().toString();
// 将 code -> token 存入 Redis(短期有效,如 5 分钟)
redisTemplate.opsForValue().set("SSO_CODE:" + code, token, 5, TimeUnit.MINUTES);
return Result.success("登录成功", code); // 前端用这个 code 去回调子系统
}
}
Client 子系统的 Filter 拦截
public class SSOFilter extends OncePerRequestFilter {
@Autowired
private RestTemplate restTemplate; // 用于与 SSO Server 通信
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
// 1. 检查本地会话(业务系统的 Session)
HttpSession session = request.getSession();
if (session.getAttribute("LOGIN_USER") != null) {
chain.doFilter(request, response);
return;
}
// 2. 检查是否有 callback 参数(SSO Server 跳转回来时携带的 ticket)
String ticket = request.getParameter("ticket");
if (StringUtils.hasText(ticket)) {
// 2.1 拿着 ticket 去 SSO Server 验证并获取用户信息
String verifyUrl = "http://sso.com/sso/verify?ticket=" + ticket;
ResponseEntity<UserInfo> resp = restTemplate.getForEntity(verifyUrl, UserInfo.class);
if (resp.getStatusCode().is2xxSuccessful() && resp.getBody() != null) {
// 2.2 验证成功,创建本地会话
session.setAttribute("LOGIN_USER", resp.getBody());
// 2.3 跳回本系统首页(去除 URL 中的 ticket 参数)
response.sendRedirect("/index");
return;
}
}
// 3. 未登录,也没有 ticket,重定向到 SSO Server
String redirectUrl = URLEncoder.encode("http://www.business.com/current/path", "UTF-8");
String ssoLoginUrl = "http://sso.com/login?redirect=" + redirectUrl;
response.sendRedirect(ssoLoginUrl);
}
}
SSO Server 验证 Ticket 接口
@GetMapping("/sso/verify")
public Result verifyTicket(@RequestParam("ticket") String ticket) {
String token = redisTemplate.opsForValue().get("SSO_CODE:" + ticket);
if (token == null) {
return Result.error("无效ticket");
}
// 解析 JWT,获取用户信息
UserInfo user = JwtUtils.parseToken(token);
if (user == null) {
return Result.error("token已过期");
}
// 删除已使用的 ticket(一次性)
redisTemplate.delete("SSO_CODE:" + ticket);
return Result.success(user);
}
- 优点: 无状态、跨域、适合分布式。
- 缺点: 未登录时重定向流程较复杂,需要维护 JWT 刷新机制。
基于 OAuth2 / CAS(企业级最强)
适用场景: 大型企业、需要对接第三方应用、要求标准协议。
推荐框架:
- CAS Server(Java 经典): Jedis 社区版,功能完善,官方提供了
cas-server-webapp,直接打包部署即可。 - Spring Security + OAuth2 Client: Spring Boot 官方支持,配置即可集成 SSO。
Spring Security + OAuth2 Client 配置示例(作为 Client 接入):
spring:
security:
oauth2:
client:
registration:
my-sso:
client-id: your-client-id
client-secret: your-client-secret
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid, profile
provider:
my-sso:
issuer-uri: https://your-sso-server.com/auth/realms/your-realm
- 优点: 标准化(OAuth2 / SAML2),支持社交登录、身份联合。
- 缺点: 配置复杂,需要维护认证中心(如 Keycloak、CAS Server)。
如何选型?
| 需求场景 | 推荐方案 | 复杂度 | 安全性 |
|---|---|---|---|
| 同域名下多个子系统 | Cookie + 共享Session | ⭐ | 中(需HTTPS) |
| 跨域名、前端分离、小型项目 | JWT + 中央认证 | ⭐⭐ | 高 |
| 企业级、多协议、对接第三方 | OAuth2 / CAS / Keycloak | ⭐⭐⭐⭐ | 极高 |
关键安全提示
- Cookie 必须设置
HttpOnly(防止 JS 盗取)。 - 生产环境强制 HTTPS(防止 Token 被中间人截获)。
- Ticket 必须一次性(防止 Replay Attack)。
- JWT 过期时间不宜过长(建议 15-30 分钟,配合 Refresh Token 使用)。
- SSO Server 与 Client 之间应使用内网或签名(防止伪造请求)。
如果你需要一个完整的可运行 Demo(Spring Boot + JWT + Redis),可以告诉我,我可以帮你写出更详细的代码结构(包括 JJWT 工具类、登录页面跳转逻辑等)。