SpringBoot 實現單點登錄:從傳統到現代的演進之路
前言
在企業級應用架構中,單點登錄(Single Sign-On,SSO)已成為不可或缺的關鍵組件。它允許用戶只需一次登錄,就能訪問多個相互信任的應用系統,大幅提升了用戶體驗與系統安全性。
本文將深入探討在SpringBoot環境下實現SSO的四種主流方案:Cookie-Session模式、JWT無狀態模式、OAuth 2.0授權框架以及Spring Session分布式方案。
什么是單點登錄?
單點登錄是一種身份認證機制,其核心思想是:用戶在認證中心完成一次登錄后,即可訪問所有信任該認證中心的應用系統,無需重復登錄。這種機制帶來了多重優勢:
- 提升用戶體驗:消除重復登錄的繁瑣過程
- 增強安全性:集中管理認證過程,便于實施統一的安全策略
- 簡化系統管理:集中式的用戶身份管理降低了運維成本
- 支持跨域認證:解決不同域名應用間的身份共享問題
實現
效果圖
圖片
基于 Cookie-Session 的傳統實現
Cookie-Session模式是最經典的SSO實現方式,依賴于服務器端存儲會話狀態和客戶端存儲標識信息。
實現原理
- 用戶訪問應用系統,發現未登錄,重定向到認證中心
- 用戶在認證中心輸入
credentials進行登錄 - 認證中心驗證通過后,創建
Session存儲用戶信息,生成SessionID - 認證中心將
SessionID寫入Cookie,并重定向回原應用系統 - 應用系統向認證中心驗證
SessionID的有效性 - 驗證通過后,應用系統可創建本地會話或直接信任該身份
示例代碼
認證中心
@RestController
public class AuthController {
@Autowired
private HttpSession session;
// 登錄接口
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpServletResponse response) {
// 驗證用戶名密碼(實際應用中應連接數據庫)
if ("admin".equals(username) && "admin123".equals(password)) {
// 存儲用戶信息到Session
session.setAttribute("user", username);
// 設置SessionID到Cookie(跨域場景需要特殊配置)
Cookie cookie = new Cookie("SESSIONID", session.getId());
cookie.setDomain("example.com"); // 設置主域名,實現跨子域共享
cookie.setPath("/");
response.addCookie(cookie);
return"登錄成功";
}
return"用戶名或密碼錯誤";
}
// 驗證Session有效性接口
@GetMapping("/verify")
public String verify(@CookieValue(value = "SESSIONID", required = false) String sessionId) {
if (sessionId == null) {
return"未登錄";
}
// 實際應用中應通過SessionRepository查找對應Session
if (session.getId().equals(sessionId) && session.getAttribute("user") != null) {
return session.getAttribute("user").toString();
}
return"無效會話";
}
}客戶端應用集成
@Configuration
public class SsoConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 檢查是否為公開路徑
String path = request.getRequestURI();
if (path.contains("/login") || path.contains("/public")) {
returntrue;
}
// 從Cookie獲取SESSIONID
String sessionId = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("SESSIONID".equals(cookie.getName())) {
sessionId = cookie.getValue();
break;
}
}
}
// 調用認證中心驗證
if (sessionId != null) {
RestTemplate restTemplate = new RestTemplate();
String username = restTemplate.getForObject(
"http://auth.example.com/verify?sessinotallow=" + sessionId,
String.class);
if (!"未登錄".equals(username) && !"無效會話".equals(username)) {
// 驗證通過,存儲用戶信息到本地
request.setAttribute("currentUser", username);
returntrue;
}
}
// 驗證失敗,重定向到認證中心
response.sendRedirect("http://auth.example.com/login?redirect=" +
URLEncoder.encode(request.getRequestURL().toString(), "UTF-8"));
returnfalse;
}
});
}
}優點:
- 實現簡單,易于理解和開發
- 會話狀態存儲在服務器,安全性較高
- 支持主動使會話失效
缺點:
- 分布式環境下需要解決
Session共享問題 - 跨域場景處理復雜,
Cookie存在跨域限制 - 服務器存儲會話狀態,增加了服務器負擔
- 不適合前后端分離和移動端應用
基于 JWT 的無狀態實現
JWT(JSON Web Token)是一種輕量級的認證令牌,它將用戶信息編碼到令牌中,實現了無狀態的認證機制,非常適合分布式系統。
實現原理
- 用戶在認證中心登錄成功后,服務器生成包含用戶信息的
JWT令牌 - 認證中心將
JWT返回給客戶端(通常存儲在localStorage或Cookie中) - 客戶端后續請求在
Authorization頭中攜帶JWT - 各應用系統接收到請求后,驗證
JWT的簽名有效性 - 驗證通過后,從
JWT中解析出用戶信息,無需與認證中心交互
JWT 結構解析
Header(頭部):指定令牌類型和簽名算法Payload(載荷):包含聲明信息,如用戶ID、角色、過期時間等Signature(簽名):使用服務器密鑰對前兩部分進行簽名,確保令牌未被篡改
示例代碼
JWT 工具類
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration; // 單位:毫秒
// 生成JWT令牌
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
// 從JWT令牌中獲取用戶名
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
// 驗證JWT令牌
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
returntrue;
} catch (Exception e) {
// 令牌無效或已過期
returnfalse;
}
}
}認證中心
@RestController
public class AuthController {
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestParam String username,
@RequestParam String password) {
// 驗證用戶名密碼
if ("admin".equals(username) && "admin123".equals(password)) {
// 生成JWT令牌
String token = jwtUtils.generateToken(username);
return ResponseEntity.ok(new JwtResponse(token));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("認證失敗");
}
// JWT響應實體
static class JwtResponse {
private String token;
public JwtResponse(String token) {
this.token = token;
}
// getter and setter
}
}客戶端應用
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtils jwtUtils;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 獲取Authorization頭
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
String token = authorizationHeader.substring(7);
// 驗證令牌
if (jwtUtils.validateToken(token)) {
String username = jwtUtils.getUsernameFromToken(token);
// 將用戶信息存入請求
request.setAttribute("currentUser", username);
returntrue;
}
}
// 令牌無效,返回401
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Unauthorized");
returnfalse;
}
}優點:
- 無狀態設計,服務器無需存儲會話信息,易于水平擴展
- 適合分布式系統和微服務架構
- 支持跨域認證,適用于前后端分離和移動端應用
- 減少了服務間的通信開銷
缺點:
- 令牌一旦生成無法直接修改或撤銷,除非維護黑名單
- 令牌包含用戶信息,雖有簽名但不宜存儲敏感數據
- 令牌過長可能增加網絡傳輸負擔
- 續簽機制相對復雜
基于 OAuth 2.0 的授權框架
OAuth 2.0是一個開放標準的授權框架,不僅用于單點登錄,更廣泛應用于第三方應用授權場景(如使用微信、QQ登錄其他應用)。
核心概念
- 資源所有者:通常指用戶,擁有可訪問的資源
- 客戶端:請求訪問資源的應用程序
- 授權服務器:負責認證用戶并頒發令牌
- 資源服務器:存儲受保護資源的服務器,驗證令牌有效性
授權流程(授權碼模式)
- 客戶端引導用戶到授權服務器
- 用戶在授權服務器進行認證并授予權限
- 授權服務器返回授權碼給客戶端
- 客戶端使用授權碼向授權服務器請求訪問令牌
- 授權服務器驗證授權碼,頒發訪問令牌(可能包含刷新令牌)
- 客戶端使用訪問令牌訪問資源服務器
- 資源服務器驗證令牌,返回受保護資源
示例代碼
授權服務器配置
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 客戶端ID和密鑰
.withClient("client-app")
.secret(passwordEncoder().encode("client-secret"))
// 授權類型
.authorizedGrantTypes("authorization_code", "refresh_token")
// 授權范圍
.scopes("read", "write")
// 回調地址
.redirectUris("http://client.example.com/callback")
// 訪問令牌有效期
.accessTokenValiditySeconds(3600)
// 刷新令牌有效期
.refreshTokenValiditySeconds(86400);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}資源服務器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated();
}
}客戶端應用配置
@Configuration
@EnableOAuth2Sso
public class ClientConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl("http://auth.example.com/logout")
.permitAll();
}
}客戶端屬性配置
# OAuth2客戶端注冊配置(針對具體客戶端)
spring.security.oauth2.client.registration.my-client.client-id=client-app
spring.security.oauth2.client.registration.my-client.client-secret=client-secret
# 授權類型(根據實際場景選擇,如authorization_code、password等)
spring.security.oauth2.client.registration.my-client.authorization-grant-type=authorization_code
# 回調地址(需與認證服務器配置的一致)
spring.security.oauth2.client.registration.my-client.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
# 認證服務器(Provider)配置
spring.security.oauth2.client.provider.my-provider.access-token-uri=http://auth.example.com/oauth/token
spring.security.oauth2.client.provider.my-provider.authorization-uri=http://auth.example.com/oauth/authorize
# 用戶信息端點(用于獲取登錄用戶詳情)
spring.security.oauth2.client.provider.my-provider.user-info-uri=http://auth.example.com/user
# 從用戶信息響應中提取用戶名的字段(默認是username,根據實際響應調整)
spring.security.oauth2.client.provider.my-provider.user-name-attribute=username優點:
- 標準化協議,生態完善,支持多種授權模式
- 安全性高,支持精細的權限控制
- 非常適合第三方應用授權場景
- 支持令牌刷新機制
缺點:
- 實現相對復雜,學習成本較高
- 流程相對繁瑣,增加了網絡請求次數
- 不適合對性能要求極高的內部系統
基于 Spring Session 的分布式實現
Spring Session提供了一種簡化的方式來管理用戶會話,支持將會話數據存儲在分布式環境中(如 Redis、Mongo 等),非常適合集群部署的SSO系統。
實現原理
- 擴展了
HttpSession,將會話數據存儲在外部數據源 - 各應用節點通過統一的
SessionID訪問共享的會話數據 - 支持跨域會話共享,解決了傳統
Cookie-Session的分布式問題
示例代碼
配置屬性
# application.properties
spring.session.store-type=redis
spring.session.redis.namespace=spring:session:sso
server.servlet.session.cookie.name=SSOSESSION
server.servlet.session.cookie.domain=example.com
server.servlet.session.timeout=30m認證中心登錄
@RestController
public class AuthController {
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session) {
// 驗證用戶名密碼
if ("admin".equals(username) && "admin123".equals(password)) {
// 存儲用戶信息到Session
session.setAttribute("user", username);
return"登錄成功,SessionID: " + session.getId();
}
return"認證失敗";
}
@GetMapping("/user")
public String getUser(HttpSession session) {
return session.getAttribute("user") != null ?
session.getAttribute("user").toString() : "未登錄";
}
}客戶端應用
@RestController
public class ClientController {
@GetMapping("/hello")
public String hello(HttpSession session) {
String user = (String) session.getAttribute("user");
if (user != null) {
return "Hello, " + user + "! 這是客戶端應用";
}
return "請先登錄";
}
}優點:
- 透明集成
Spring生態,無需大量修改現有代碼 - 完美解決分布式系統的
Session共享問題 - 支持多種存儲方式,易于擴展
- 保留了傳統
Session的使用習慣,學習成本低
缺點:
- 需要額外的存儲服務(如
Redis) - 仍依賴
Cookie傳遞SessionID,跨域存在一定限制 - 相比
JWT增加了存儲訪問開銷
方案對比與選擇建議
方案 | 優勢場景 | 缺點 | 適用系統 |
Cookie-Session | 簡單應用、內部系統 | 分布式支持差、跨域限制 | 小型單體應用 |
JWT | 前后端分離、移動端、微服務 | 無法即時吊銷、存儲限制 | 分布式系統、API 服務 |
OAuth 2.0 | 第三方授權、開放平臺 | 實現復雜、流程長 | 開放平臺、多客戶端場景 |
Spring Session | 分布式集群、Session 共享 | 依賴外部存儲 | 集群部署的 Web 應用 |


































