
在企业级应用或多系统架构中,“重复登录” 是用户体验的一大痛点 —— 例如,用户登录电商平台后,访问关联的会员中心、订单系统、支付系统时,还需再次输入账号密码,不仅繁琐,还增加了密码泄露风险。而单点登录(Single Sign-On,简称 SSO) 作为解决这一问题的核心方案,能实现 “一次登录,多系统互通”,大幅提升用户体验与系统安全性。本文将从 SSO 的核心原理出发,拆解主流实现方案,结合实战案例演示落地过程,并分析安全风险与防护措施,帮你彻底掌握 SSO 的设计与应用。
在未引入 SSO 的多系统架构中,登录流程存在三大核心问题,这也是 SSO 诞生的初衷:
SSO 的本质是 “在多个独立系统间,共享用户的认证状态”,核心价值体现在三点:
要理解 SSO,需先明确其核心组件与通用流程 —— 无论采用哪种实现方案,SSO 的底层逻辑都围绕 “中央认证 + 跨系统会话共享” 展开。
SSO 架构通常包含三个关键角色,各角色职责明确:
组件名称 | 核心职责 | 示例 |
|---|---|---|
身份提供商(IdP,Identity Provider) | 中央认证服务器,负责用户身份校验(账号密码验证、短信验证等)、生成认证凭证(如 Token)、管理全局会话 | 微信开放平台(提供微信登录)、企业内部的 SSO 认证中心 |
服务提供商(SP,Service Provider) | 各业务系统(如 OA、CRM、电商订单系统),依赖 IdP 完成认证,无需存储用户密码,仅需验证 IdP 颁发的凭证 | 企业 OA 系统、电商平台的订单系统、第三方应用(如用微信登录的小游戏) |
用户(User) | 终端使用者,通过 IdP 完成一次登录后,可访问所有信任的 SP 系统 | 企业员工、电商平台用户 |
以 “企业员工登录 OA 系统后,访问 CRM 系统” 为例,SSO 的通用流程如下(适用于大多数实现方案):
SSO 的实现方案因 “凭证类型”“跨域处理方式” 不同而分为多种,以下是企业级应用中最常用的 4 种方案,需根据业务场景(如是否跨域、是否对接第三方、安全性要求)选择:
优点 | 缺点 |
|---|---|
实现简单(依赖传统 Session 机制) | 跨域限制严格:Cookie 仅能在 IdP 域名下生效,若 SP 与 IdP 不在同一主域(如idp.com和sp1.cn),Cookie 无法携带,需额外处理(如 URL 参数传递 SessionId,安全性低) |
服务器端管理会话,易于控制登出 | 扩展性差:全局 Session 存储在 IdP 服务器,高并发下需考虑 Session 集群(如 Redis 共享 Session),增加复杂度 |
JWT(JSON Web Token)是一种 “无状态” 的认证凭证,由 IdP 生成并签名,包含用户身份信息(如用户 ID、角色),SP 无需向 IdP 请求校验,可直接通过签名验证凭证有效性。核心流程:
优点 | 缺点 |
|---|---|
无状态:SP 无需向 IdP 校验,减少网络请求,适合分布式系统 | 无法主动销毁:JWT 一旦生成,在过期前始终有效,若用户需 “强制登出”,需额外维护 “黑名单”(如 Redis 存储失效的 JWT) |
跨域友好:JWT 通过请求头携带,不受 Cookie 域名限制 | payload 不宜过大:JWT 会随每次请求传递,过大导致请求体积增加,影响性能 |
实现简单:无需维护服务器端 Session,适合中小规模系统 | 签名验证需公钥 / 私钥管理:若私钥泄露,攻击者可伪造 JWT,需妥善保管密钥 |
// 1. 引入JWT依赖(Maven)<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>// 2. JWT工具类@Componentpublic class JwtUtil { // 私钥(生产环境需放在配置中心,避免硬编码) @Value("${jwt.secret}") private String secret; // 过期时间(2小时) @Value("${jwt.expire}") private long expire; // 生成JWT public String generateToken(Long userId, String role) { Date now = new Date(); Date expireDate = new Date(now.getTime() + expire * 1000); return Jwts.builder() .setHeaderParam("typ", "JWT") // Header部分 .setSubject(userId.toString()) // Payload:用户ID .claim("role", role) // Payload:自定义字段(角色) .setIssuedAt(now) // 签发时间 .setExpiration(expireDate) // 过期时间 .signWith(SignatureAlgorithm.HS256, secret) // 签名(用私钥) .compact(); } // 解析JWT,获取用户ID public Long getUserIdFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return Long.parseLong(claims.getSubject()); } // 验证JWT有效性(是否过期、签名是否正确) public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(token); return true; } catch (Exception e) { // 过期、签名错误等均返回false return false; } }}// 3. 登录接口(IdP端)@RestController@RequestMapping("/idp")public class LoginController { @Autowired private UserService userService; @Autowired private JwtUtil jwtUtil; @PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { // 1. 校验账号密码(实际场景需加密校验,如BCrypt) User user = userService.findByUsernameAndPassword(loginDTO.getUsername(), loginDTO.getPassword()); if (user == null) { return Result.fail("账号或密码错误"); } // 2. 生成JWT String token = jwtUtil.generateToken(user.getId(), user.getRole()); // 3. 返回JWT(前端存储到localStorage或Cookie) return Result.success("登录成功").put("token", token); }}// 1. JWT拦截器:验证请求头中的JWT@Componentpublic class JwtInterceptor implements HandlerInterceptor { @Autowired private JwtUtil jwtUtil; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 获取请求头中的JWT String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { response.setStatus(401); response.getWriter().write("未登录或Token无效"); return false; } token = token.substring(7); // 去掉"Bearer "前缀 // 2. 验证JWT有效性 if (!jwtUtil.validateToken(token)) { response.setStatus(401); response.getWriter().write("Token已过期或无效"); return false; } // 3. 解析用户信息,存入请求属性(后续接口可获取) Long userId = jwtUtil.getUserIdFromToken(token); request.setAttribute("userId", userId); return true; }}// 2. 配置拦截器(拦截OA系统的所有接口,排除登录页)@Configurationpublic class WebConfig implements WebMvcConfigurer { @Autowired private JwtInterceptor jwtInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/oa/**") // 拦截OA系统所有接口 .excludePathPatterns("/oa/loginPage"); // 排除登录页(未登录时跳转) }}// 3. OA系统业务接口(需登录才能访问)@RestController@RequestMapping("/oa")public class OaController { @GetMapping("/user/info") public Result getUserInfo(HttpServletRequest request) { // 从请求属性中获取用户ID(JWT拦截器已解析) Long userId = (Long) request.getAttribute("userId"); // 查询用户信息(实际场景需从数据库或缓存获取) UserInfoDTO userInfo = userService.getUserInfoById(userId); return Result.success(userInfo); }}OAuth2.0 是一种 “授权框架”,并非专为 SSO 设计,但常被用于 “第三方登录” 场景(如用微信、QQ、GitHub 登录第三方应用),本质是通过 “授权码” 实现 SP 与第三方 IdP 的认证互通。核心角色除了 IdP(如微信开放平台)、SP(如第三方小游戏),还增加了 “资源所有者”(用户)和 “资源服务器”(如微信的用户信息接口)。
以 “用微信登录小游戏” 为例,OAuth2.0 的授权码模式(最常用、最安全)流程如下:
优点 | 缺点 |
|---|---|
支持第三方登录:无需用户注册新账号,直接用现有账号(如微信、QQ)登录 | 流程复杂:需理解授权码、access_token、refresh_token 等概念,开发成本高 |
安全性高:授权码仅短期有效,且通过后端交换 access_token,避免令牌泄露 | 依赖第三方 IdP:若 IdP 服务不可用(如微信开放平台故障),SP 的登录功能会受影响 |
灵活度高:支持多种授权模式(如授权码、密码、客户端凭证),适配不同场景 | 权限控制复杂:需管理 access_token 的权限范围(如仅获取用户昵称,不获取手机号) |
CAS(Central Authentication Service)是专为 SSO 设计的企业级框架,基于 “票据(Ticket)” 机制,支持单点登录与单点登出,安全性高、功能完善(如多因素认证、权限管理)。核心流程与 JWT 类似,但引入了 “TGT(Ticket Granting Ticket,票据授予票据)” 和 “ST(Service Ticket,服务票据)”:
优点 | 缺点 |
|---|---|
安全性极高:支持 HTTPS、票据加密、防 CSRF/XSS,适合企业级核心系统 | 部署复杂:需搭建 CAS Server 集群,维护成本高 |
支持单点登出:用户退出任一 SP,CAS Server 通知所有 SP 销毁会话 | 性能依赖 CAS Server:高并发下需优化 CAS Server(如 Redis 缓存 TGT) |
功能完善:内置多因素认证、用户管理、日志审计等功能 | 重量级:对小规模系统而言,过于复杂 |
SSO 虽提升了用户体验,但也集中了认证入口 —— 一旦 IdP 被攻击或凭证泄露,所有关联 SP 都会面临风险。需针对性解决以下安全问题:
业务场景 | 推荐方案 | 核心原因 |
|---|---|---|
企业内部系统,同主域、小规模 | Cookie+Session | 实现简单,无需额外依赖,适合内部信任环境 |
跨域系统,轻量级需求 | JWT | 跨域友好,无状态,部署成本低 |
第三方登录(如微信 / QQ 登录) | OAuth2.0 | 支持第三方授权,用户体验好,安全性高 |
企业级核心系统,高安全需求 | CAS | 支持单点登出、多因素认证,适合大规模部署 |
单点登录(SSO)的核心是 “统一认证入口,共享登录状态”,不同实现方案(Cookie+Session、JWT、OAuth2.0、CAS)各有优劣,需根据业务场景(跨域、安全性、是否第三方登录)选择。落地时需重点关注安全风险(凭证泄露、CSRF、伪造),通过 HTTPS、HttpOnly Cookie、签名验证、黑名单等措施防护,同时保证 IdP 高可用与性能优化。
SSO 不仅是技术方案,更是用户体验与系统安全的平衡艺术 —— 好的 SSO 设计能让用户 “无感登录”,让开发者 “少重复开发”,让系统 “更安全可控”,这也是其在企业级应用中广泛普及的核心原因。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。