← 返回首页
🔒

单点登录架构:SAML/OIDC/CAS

📂 architecture ⏱ 2 min 341 words

单点登录架构:SAML/OIDC/CAS

SAML协议实现

SAML(Security Assertion Markup Language)是企业级SSO的标准协议,基于XML交换身份断言。

// SAML服务提供商实现
@Component
public class SAMLServiceProvider {
    
    private final SAMLConfig config;
    private final IdentityProviderRepository idpRepository;
    
    public AuthnRequest createAuthnRequest(String idpId) {
        IDPConfig idp = idpRepository.findById(idpId);
        
        AuthnRequest request = new AuthnRequest();
        request.setID(generateRequestId());
        request.setIssueInstant(new DateTime());
        request.setVersion("2.0");
        request.setDestination(idp.getSSOUrl());
        request.setAssertionConsumerServiceURL(config.getAcsUrl());
        request.setProtocolBinding(SAMLConstants.SAML2_HTTP_POST_BINDING);
        
        // 签名请求
        return signRequest(request);
    }
    
    public Assertion processResponse(String samlResponse) {
        // 解码响应
        String decoded = Base64.getDecoder().decodeToString(samlResponse);
        
        // 验证签名
        Response response = unmarshallAndVerify(decoded);
        
        // 验证断言
        Assertion assertion = response.getAssertions().get(0);
        validateAssertion(assertion);
        
        return assertion;
    }
}

OpenID Connect实现

OIDC在OAuth2基础上增加了身份层,提供标准化的身份认证功能。

// OIDC身份提供商
@Component
public class OIDCIdentityProvider {
    
    private final OIDCConfig config;
    private final KeyManager keyManager;
    
    @GetMapping("/.well-known/openid-configuration")
    public ResponseEntity<Map<String, Object>> getDiscovery() {
        Map<String, Object> discovery = new HashMap<>();
        discovery.put("issuer", config.getIssuer());
        discovery.put("authorization_endpoint", config.getIssuer() + "/authorize");
        discovery.put("token_endpoint", config.getIssuer() + "/token");
        discovery.put("userinfo_endpoint", config.getIssuer() + "/userinfo");
        discovery.put("jwks_uri", config.getIssuer() + "/.well-known/jwks.json");
        discovery.put("supported_signing_algorithms", Arrays.asList("RS256", "RS512"));
        discovery.put("supported_grant_types", Arrays.asList("authorization_code", "refresh_token"));
        
        return ResponseEntity.ok(discovery);
    }
    
    @GetMapping("/.well-known/jwks.json")
    public ResponseEntity<Map<String, Object>> getJWKS() {
        JWKS jwks = keyManager.getPublicKeys();
        return ResponseEntity.ok(jwks.toMap());
    }
    
    @PostMapping("/token")
    public ResponseEntity<TokenResponse> token(@RequestBody TokenRequest request) {
        // 验证授权码
        AuthorizationCode code = validateAuthorizationCode(request.getCode());
        
        // 生成ID Token
        String idToken = generateIDToken(code.getUser(), code.getNonce());
        
        // 生成访问令牌
        String accessToken = generateAccessToken(code.getUser(), code.getScope());
        
        return ResponseEntity.ok(TokenResponse.builder()
            .idToken(idToken)
            .accessToken(accessToken)
            .tokenType("Bearer")
            .expiresIn(3600)
            .build());
    }
    
    private String generateIDToken(User user, String nonce) {
        return Jwts.builder()
            .setSubject(user.getId())
            .setIssuer(config.getIssuer())
            .setAudience(user.getClientId())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 3600000))
            .claim("nonce", nonce)
            .claim("auth_time", user.getLastAuthTime())
            .signWith(keyManager.getSigningKey(), SignatureAlgorithm.RS256)
            .compact();
    }
}

CAS协议实现

CAS(Central Authentication Service)是一种简单高效的SSO协议。

// CAS服务票据验证
@Component
public class CASTicketValidator {
    
    private final TicketRegistry ticketRegistry;
    private final ServiceRegistry serviceRegistry;
    
    public CASResponse validateTicket(String ticketId, String serviceUrl) {
        // 获取服务票据
        ServiceTicket ticket = ticketRegistry.getServiceTicket(ticketId);
        
        if (ticket == null || ticket.isExpired()) {
            return CASResponse.failure("INVALID_TICKET", "票据无效或已过期");
        }
        
        // 验证服务URL
        if (!ticket.getServiceUrl().equals(serviceUrl)) {
            return CASResponse.failure("INVALID_SERVICE", "服务URL不匹配");
        }
        
        // 获取TGT(Ticket Granting Ticket)
        TicketGrantingTicket tgt = ticket.getTgt();
        
        // 创建PT(Proxy Ticket)用于代理
        ProxyTicket pt = createProxyTicket(tgt, serviceUrl);
        
        // 返回成功响应
        return CASResponse.success(
            tgt.getUser().getAttributes(),
            pt != null ? pt.getId() : null
        );
    }
}

SSO架构对比与选择

# SSO协议对比
sso_protocols:
  saml:
    description: "企业级SSO标准"
    use_cases:
      - 企业内部系统集成
      - 云服务身份联邦
      - 高安全性要求场景
    pros:
      - 成熟稳定
      - 功能完善
      - 支持复杂的属性传输
    cons:
      - 实现复杂
      - XML开销大
      - 移动端支持有限
  
  oidc:
    description: "现代身份认证标准"
    use_cases:
      - Web和移动应用
      - API认证
      - 社交登录集成
    pros:
      - 基于OAuth2
      - JSON格式轻量
      - 移动端友好
    cons:
      - 相对较新
      - 功能不如SAML完善
  
  cas:
    description: "简单SSO协议"
    use_cases:
      - 高校和教育机构
      - 中小企业
      - 快速部署需求
    pros:
      - 实现简单
      - 部署快速
      - 轻量级
    cons:
      - 功能相对有限
      - 扩展性一般

SSO架构选择需要根据组织规模、安全需求和技术栈综合考虑,不同协议各有适用场景。