基于Keycloak实现第三方应用SSO的方案咨询及最佳实践建议
基于Keycloak实现我方应用与第三方REST接口的SSO方案(Spring Boot + Next.js + Docker栈)
核心流程说明
用户在我方应用(Spring Boot后端+Next.js前端)通过Keycloak完成登录后,我方应用获取Keycloak颁发的Access Token,调用第三方REST接口时将该Token放在请求头中,第三方应用验证Token有效性后即可授权访问,无需用户再次注册登录。
实现步骤
1. Keycloak配置
1.1 配置第三方应用的资源服务器客户端
- 在Keycloak控制台创建新客户端,客户端类型选择
bearer-only(该类型仅负责验证Token,不处理登录跳转)。 - 设置
客户端ID(如third-party-api),并在设置中配置受众(Audience)为该客户端ID,确保Token的aud字段匹配。 - 保存后,该客户端将用于第三方应用验证Token的合法性。
1.2 配置我方应用的客户端
- Next.js前端客户端:
- 创建类型为
public的客户端,配置有效重定向URI(如http://your-nextjs-domain/*,适配Docker部署的实际域名)、Web Origins(允许跨域的地址,如http://your-nextjs-domain)。 - 为客户端添加必要的客户端范围(Client Scopes):至少包含
openid、profile,若第三方需要自定义用户信息,可添加自定义范围并配置对应的声明。
- 创建类型为
- Spring Boot后端客户端:
- 创建类型为
confidential的客户端,生成并保存客户端密钥。 - 配置
有效重定向URI(如http://your-springboot-domain/login/oauth2/code/keycloak),同样添加必要的客户端范围。
- 创建类型为
1.3 Docker部署下的Keycloak额外配置
- 启动Keycloak容器时,设置
KEYCLOAK_FRONTEND_URL为外部可访问的地址(如https://your-keycloak-domain/auth),避免容器内部地址导致前端登录跳转失败。 - 确保Keycloak与我方应用容器在同一Docker网络内,或配置正确的端口映射与域名解析。
2. 我方应用改造
2.1 Next.js前端实现
使用keycloak-js库完成登录与Token获取,调用第三方接口时携带Token:
import Keycloak from 'keycloak-js'; // 初始化Keycloak实例 const keycloak = new Keycloak({ url: 'https://your-keycloak-domain/auth', realm: 'your-realm-name', clientId: 'your-nextjs-client-id' }); // 页面加载时自动完成登录 (async () => { const authenticated = await keycloak.init({ onLoad: 'login-required' }); if (authenticated) { console.log('用户已登录,Token:', keycloak.token); } })(); // 调用第三方REST接口 const fetchThirdPartyData = async () => { try { const response = await fetch('https://third-party-api-domain/your-endpoint', { method: 'GET', headers: { 'Authorization': `Bearer ${keycloak.token}` } }); return await response.json(); } catch (error) { console.error('调用第三方接口失败:', error); } };
2.2 Spring Boot后端实现
通过Spring Security OAuth2客户端获取Token,转发给第三方接口:
- 引入依赖(pom.xml):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
- 配置application.yml:
spring: security: oauth2: client: provider: keycloak: issuer-uri: https://your-keycloak-domain/auth/realms/your-realm-name registration: keycloak: client-id: your-springboot-client-id client-secret: your-springboot-client-secret scope: openid,profile
- 调用第三方接口的代码示例:
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpEntity; import org.springframework.http.ResponseEntity; @RestController public class ThirdPartyApiController { private final OAuth2AuthorizedClientService authorizedClientService; private final RestTemplate restTemplate; public ThirdPartyApiController(OAuth2AuthorizedClientService authorizedClientService, RestTemplate restTemplate) { this.authorizedClientService = authorizedClientService; this.restTemplate = restTemplate; } @GetMapping("/call-third-party") public ResponseEntity<String> callThirdPartyApi(OAuth2AuthenticationToken authentication) { // 获取当前用户的Keycloak授权客户端 OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient( "keycloak", authentication.getName()); String accessToken = client.getAccessToken().getTokenValue(); // 设置请求头携带Token HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); HttpEntity<String> entity = new HttpEntity<>(headers); // 调用第三方接口 return restTemplate.exchange( "https://third-party-api-domain/your-endpoint", org.springframework.http.HttpMethod.GET, entity, String.class); } }
3. 第三方应用对接
第三方应用需要验证Keycloak颁发的Token合法性,以下是两种常见场景:
3.1 第三方为Spring Boot应用
直接配置为OAuth2资源服务器:
spring: security: oauth2: resource-server: jwt: issuer-uri: https://your-keycloak-domain/auth/realms/your-realm-name audience: third-party-api # 需与Keycloak中第三方客户端的ID一致
Spring Security会自动验证Token的签名、issuer、audience、过期时间等。
3.2 第三方为非Java栈应用
手动实现JWT验证逻辑:
- 从Keycloak的
https://your-keycloak-domain/auth/realms/your-realm-name/protocol/openid-connect/certs端点获取公钥。 - 验证Token的签名是否有效,检查
iss字段是否为我方Keycloak的Realm地址,aud字段是否匹配第三方的客户端ID,同时检查Token是否过期。
最佳实践建议
- 强制HTTPS通信:Keycloak、我方应用、第三方接口全部使用HTTPS,防止Token在传输过程中被窃取。
- 最小权限原则:为我方客户端分配仅满足第三方接口需求的Scope和角色,避免在Token中携带多余的用户敏感信息。
- 缓存Keycloak公钥:第三方应用不要每次验证Token都请求公钥,缓存公钥并定期更新,提升验证性能。
- 合理设置Token生命周期:将Access Token过期时间设为15-30分钟,使用Refresh Token自动刷新,平衡安全性与用户体验。
- Docker网络隔离:将Keycloak容器放在内部私有网络,仅暴露必要端口,避免直接暴露到公网。
- 角色映射与权限控制:若第三方应用需要特定权限,在Keycloak中配置角色映射,将我方用户的角色转换为第三方认可的角色,通过Token传递。
- 开启审计日志:启用Keycloak的审计日志功能,记录Token发放、验证等操作,便于后续问题排查与安全审计。
内容的提问来源于stack exchange,提问作者user2779840
相关产品推荐
相关产品推荐

