腾讯技术一面常问:单点登录的实现原理 你能回答出吗?

178 阅读7分钟

腾讯技术一面常问:单点登录的实现原理 你能回答出吗?

(八年 Java 开发亲授:从业务落地到源码级解析)

开篇:面试官的灵魂拷问

面鹅厂某事业群时,技术负责人突然抛出问题:“如果让你设计微信和 QQ 的统一登录系统,如何保证亿级用户的秒级响应?”

作为写了八年 Java 的老开发,我本能地想讲 CAS 和 OAuth2,但对方随即展示的 PPT 让我冷汗直冒 —— 腾讯自研的 TSSO 系统通过分布式令牌验证 + 多级缓存,实现了单集群支撑 10 亿 QPS,这让我意识到:SSO 的核心不仅是协议,更是高并发场景下的工程化能力

今天从 业务场景、技术本质、核心代码 三个维度,结合 Spring Security 和 Redis 实战,聊聊如何从原理到落地彻底吃透 SSO。

一、业务场景:SSO 解决的三个核心痛点

先看三个典型业务场景,你会发现 SSO 的价值远不止 “免密登录”。

场景 1:企业多系统集成(如 OA+CRM+HR)

传统方案痛点

  • 用户需记忆多套账号密码,忘记密码导致运维成本增加 30%。

  • 系统间数据孤岛,如 OA 登录后无法自动同步用户权限到 CRM。

SSO 解决方案

  • 统一认证中心:用户在 OA 登录后,认证中心生成全局令牌(如 JWT),通过 HTTP 头传递到 CRM 系统。

  • 权限同步机制:利用 Spring Security 的SecurityContextHolder,在 CRM 系统中解析令牌获取用户角色。

核心代码(Spring Security 整合 JWT)

// JWT令牌解析过滤器
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = getTokenFromRequest(request);
        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }

    private String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

场景 2:电商平台多端统一登录(APP + 小程序 + H5)

传统方案痛点

  • 各端独立维护会话,导致用户在 APP 登录后,H5 仍需重新登录。

  • 移动端 Token 频繁过期,影响转化率(数据显示,频繁登录会导致 5% 用户流失)。

SSO 解决方案

  • 跨域 Cookie 共享:通过设置 Cookie 的Domain为顶级域名(如.example.com),实现 APP 和 H5 共享会话。

  • Token 长效机制:使用 Refresh Token 自动续期,有效期设置为 30 天,减少用户登录频率。

核心代码(Spring Session+Redis)

// 配置跨域会话存储
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 2592000) // 30天有效期
public class SessionConfig {
    @Bean
    public RedisConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory("localhost", 6379);
    }
}

场景 3:金融级系统安全要求(如支付核心)

传统方案痛点

  • 单点故障风险:认证中心宕机导致所有系统无法登录。

  • 安全审计缺失:无法追踪用户在各系统的操作记录。

SSO 解决方案

  • 多活架构:认证中心采用主从复制 + 负载均衡(如 Nginx+Keepalived),保证 99.999% 可用性。

  • 审计日志:通过 AOP 拦截认证请求,记录用户 IP、设备指纹等信息到 Elasticsearch。

核心代码(Spring AOP 审计日志)

@Aspect
@Component
public class AuditAspect {
    @Pointcut("execution(* com.example.security.AuthenticationController.login(..))")
    public void loginPointcut() {}

    @Around("loginPointcut()")
    public Object logLogin(ProceedingJoinPoint joinPoint) throws Throwable {
        // 记录用户IP和设备指纹
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = request.getRemoteAddr();
        String deviceFingerprint = request.getHeader("Device-Fingerprint");
        auditService.logLogin(ip, deviceFingerprint);
        return joinPoint.proceed();
    }
}

二、技术本质:SSO 的三大核心机制

从 认证协议、令牌管理、跨域通信 三个维度,深挖 SSO 的底层逻辑。

1. 认证协议:CAS vs OAuth2 vs SAML

CAS(Central Authentication Service)

原理

  • 用户访问系统 A,未登录则重定向到 CAS 服务器。

  • 用户输入账号密码,CAS 生成 Ticket Granting Ticket(TGT),并重定向回系统 A,携带 Service Ticket(ST)。

  • 系统 A 用 ST 向 CAS 验证,通过后获取用户信息。

代码示例(CAS 客户端配置)

<!-- pom.xml -->
<dependency>
    <groupId>org.jasig.cas.client</groupId>
    <artifactId>cas-client-core</artifactId>
    <version>3.6.2</version>
</dependency>

<!-- web.xml -->
<filter>
    <filter-name>CASFilter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>https://sso.example.com/login</param-value>
    </init-param>
</filter>
OAuth2

原理

  • 用户访问系统 A,A 引导用户到认证服务器授权。

  • 用户授权后,认证服务器返回 Authorization Code。

  • 系统 A 用 Code 换取 Access Token,访问用户资源。

代码示例(Spring Security OAuth2 客户端)

// 配置授权服务器
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-id")
                .secret("{noop}client-secret")
                .authorizedGrantTypes("authorization_code")
                .scopes("read", "write");
    }
}
SAML

原理

  • 身份提供者(IdP)生成 SAML 断言,包含用户身份信息。

  • 服务提供者(SP)验证断言,完成认证。

代码示例(Spring SAML2 配置)

// 配置SAML2服务提供者
@Configuration
@EnableWebSecurity
public class SamlSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public SAML2MetadataFilter saml2MetadataFilter() {
        return new SAML2MetadataFilter();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .saml2Login();
    }
}

2. 令牌管理:JWT vs Redis 会话

JWT(JSON Web Token)

优势

  • 无状态:服务器无需存储会话,减轻内存压力。

  • 跨语言支持:可被任何支持 JSON 的语言解析。

缺点

  • 无法主动失效:Token 过期前无法强制注销。

解决方案

  • 使用 Redis 黑名单:用户注销时将 Token 加入黑名单,有效期设置为 Token 剩余时间。
Redis 会话

优势

  • 灵活控制:可随时删除会话,实现单点登出。

  • 支持分布式:通过 Redis 集群实现高可用。

缺点

  • 网络开销:每次请求需查询 Redis,增加延迟。

性能优化

  • 本地缓存:使用 Caffeine 缓存最近活跃的 Token,减少 Redis 访问。

3. 跨域通信:Cookie 共享 vs 令牌传递

Cookie 共享

实现方式

  • 设置 Cookie 的Domain为顶级域名,如.example.com

  • 使用HttpOnlySecure属性增强安全性。

局限性

  • 受浏览器同源策略限制,无法跨不同顶级域名。
令牌传递

实现方式

  • 通过请求头(如Authorization: Bearer <token>)传递令牌。

  • 前端使用withCredentials: true发送跨域请求。

代码示例(Axios 跨域配置)

axios.defaults.withCredentials = true;
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('token');

三、大厂实战:亿级用户的 SSO 架构设计

1. 高并发优化

  • 多级缓存

    • 一级缓存:JVM 本地缓存(如 Caffeine),存储高频访问的 Token。
    • 二级缓存:Redis 集群,存储全量 Token,设置合理过期时间。
    • 三级存储:数据库(如 MySQL),作为冷数据备份。
  • 异步验证
    使用 Spring WebFlux 的响应式编程,将 Token 验证异步化,提升吞吐量。

2. 安全增强

  • 多因素认证
    结合 Google Authenticator 或短信验证码,关键操作(如支付)强制二次验证。
  • 动态令牌加密
    使用 AES 算法对 Token 进行动态加密,密钥每小时更新一次。

3. 灰度发布

  • 金丝雀发布
    先在 1% 用户中测试新 SSO 版本,监控成功率和延迟指标。
  • 流量染色
    通过请求头标记(如x-user-type: canary),精准控制灰度范围。

四、面试必问:SSO 的八大高频问题

1. 单点登录与 OAuth2 的区别?

  • SSO 是目标,OAuth2 是实现手段。OAuth2 更侧重授权,SSO 更侧重认证。

2. 如何实现单点登出?

  • 认证中心通知所有子系统清除会话,同时将 Token 加入黑名单。

3. 跨域场景下如何传递 Token?

  • 通过请求头或 URL 参数传递,避免依赖 Cookie。

4. JWT 的优缺点是什么?

  • 优点:无状态、跨语言支持。
  • 缺点:无法主动失效、签名泄露风险。

5. 如何防止 Token 被劫持?

  • 使用 HTTPS 加密传输,设置SameSite=Strict防止 CSRF 攻击。

6. 高并发下如何优化 Token 验证性能?

  • 使用本地缓存 + Redis 分片,减少数据库查询。

7. 如何处理认证中心宕机?

  • 采用多活架构,主从节点实时同步会话数据。

8. 如何实现 SSO 的审计日志?

  • 通过 AOP 拦截认证请求,记录关键信息到 Elasticsearch。

五、总结:从八股到实战的跃迁

回到开篇的问题:腾讯为什么爱问 SSO?

  • 技术深度:SSO 涉及认证协议、分布式系统、安全攻防等多个领域。

  • 工程能力:能否在亿级用户场景下实现高可用、低延迟,是衡量架构师水平的关键。

作为 Java 开发者,建议:

  1. 吃透核心协议:CAS、OAuth2、SAML 的原理和适用场景。
  2. 掌握框架集成:Spring Security、Spring Session 的实战配置。
  3. 关注大厂实践:如腾讯的 TSSO、阿里的 AliSSO,学习其高并发解决方案。