全面解析 Keycloak:如何優雅集成到 Spring Boot 3 應用中?
Keycloak 是一種開源身份和訪問管理工具,可以幫助用戶以最小的努力為應用程序添加身份驗證功能。
Keycloak 的一些主要特性
- 單點登錄與單點注銷 (Single Sign-On and Single Sign-Out):用戶不需要為不同的應用程序設置不同的登錄賬號。
- 社交登錄與身份代理 (Social Login and Identity Brokering):支持使用 Google、Facebook 等社交登錄功能,并可以輕松配置已有的身份提供商。
- 用戶聯合 (User Federation):內置支持連接現有的 LDAP 和 Active Directory 服務器。
- 管理控制臺 (Admin Console)。
- 授權服務 (Authorisation Services):幫助管理所有客戶服務的權限,支持細粒度的權限控制。
了解 Keycloak 的幾個重要術語
- Realm (領域):一個安全域,用于管理用戶、應用程序、組和權限,便于資源、權限和配置的隔離與組織。
- Client (客戶端):能夠請求用戶身份驗證的應用程序或服務。
- Client Scopes (客戶端范圍):多個客戶端之間共享的通用規則和權限。
- Realm Roles (領域角色):在當前領域范圍內定義的角色。
Keycloak 入門
我們可以通過 Docker 啟動 Keycloak 服務器。使用以下命令啟動 Keycloak 服務器:
docker run -p8081:8080 -eKEYCLOAK_ADMIN=admin -eKEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:25.0.4 start-dev
服務器將啟動在 8081 端口。默認用戶名和密碼是 admin。可以通過瀏覽器訪問 localhost:8081/admin。
以下是管理控制臺的界面:
圖片
創建領域 (Realm)
首先,我們需要創建一個領域來管理用戶和應用程序。創建領域的方法如下:
- 點擊左側的 Realm。
- 然后點擊 Create。
創建用戶
創建用戶的步驟如下:
- 點擊左側的 Users。
- 點擊 Add User 按鈕。接著會出現一個如下的表單:
添加客戶端 (Client)
可以按以下步驟添加我們的應用程序或服務:
- 點擊左側的 Clients。
- 點擊 Create Client。
- 接下來會出現一個表單,如下圖 Screenshot_1 所示。在連接 Spring Boot 應用程序和 Keycloak 時,ClientId 非常重要。
- 在 Root URL 中填寫 Spring Boot 應用的基礎 URL (參考下圖 Screenshot_2)。
- 完成后的表單看起來如 Screenshot_3 所示。
圖片
圖片
圖片
創建客戶端后,可以為該客戶端創建 Roles (角色)。
這些角色可以分配給用戶,用戶將根據權限訪問不同的端點。
假設我們創建了兩個用戶 testadmin 和 testuser,同時創建了兩個角色 client_admin 和 client_user。
圖片
如何使用 Keycloak 生成 JWT Token?
您需要通過 HTTP POST 方法向以下 URL 發送請求:http://localhost:8081/realms/master/protocol/openid-connect/token (假設 Keycloak 服務器運行在 localhost:8081)
請求體
請求體應為 x-www-form-urlencoded 類型,并包含以下參數:
- grant_type(文本)— 表示請求所用的授權類型。
- client_id(文本)— 請求的客戶端標識符。
- username(文本)— 用于身份驗證的用戶名。
- password(文本)— 用于身份驗證的密碼。
示例請求體
grant_type:password
client_id:keycloak-integration-app
username:testuser
password:testuser
如果請求成功,API 將返回一個包含 access token 的 JSON 響應。此 Token 可用于調用我們 Spring Boot 應用的各種接口。
將 Keycloak 集成到 Spring Boot 中
創建一個簡單的 Spring Boot 應用,并添加以下依賴
- Spring Web
- Spring Security
- Lombok
- OAuth2 Authorization Server
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
創建 SecurityConfig 類:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtConvertor jwtConvertor;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
httpSecurity
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtConvertor)));
httpSecurity
.sessionManagement(sessMngmt ->
sessMngmt.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return httpSecurity.build();
}
}
創建 JwtConvertor 類以提取角色。
以下是解碼 JWT 時的角色 JSON 示例:
"resource_access": {
"keycloak-integration-app": {
"roles": [
"client_admin"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
}
為了獲取角色,我們需要創建一個轉換器類:
public class JwtConvertor implements Converter<Jwt, AbstractAuthenticationToken> {
private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter =
new JwtGrantedAuthoritiesConverter();
private String principleAttribute;
@Override
public AbstractAuthenticationToken convert(Jwt source) {
Collection<GrantedAuthority> authorities = Stream.concat(
jwtGrantedAuthoritiesConverter.convert(source).stream(),
extractResourceRoles(source).stream()
).collect(Collectors.toSet());
return new JwtAuthenticationToken(
source,
authorities,
getPrincipleClaimName(source)
);
}
private String getPrincipleClaimName(Jwt source) {
String claimName = JwtClaimNames.SUB;
if (principleAttribute != null) {
claimName = principleAttribute;
}
return source.getClaim(claimName);
}
private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
Map<String, Object> resourceAccess;
Map<String, Object> resource;
Collection<String> resourceRoles;
if (jwt.getClaim("resource_access") == null) {
return Set.of();
}
resourceAccess = jwt.getClaim("resource_access");
if (resourceAccess.get("keycloak-integration-app") == null) {
return Set.of();
}
resource = (Map<String, Object>) resourceAccess.get("keycloak-integration-app");
resourceRoles = (Collection<String>) resource.get("roles");
return resourceRoles
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
}
}
在 application.yml 中添加配置:
spring:
application:
name: keycloak-integration
security:
oauth2:
resourceserver:
jwt:
issuer-uri: sample_issuer_uri
jwk-set-uri: sample_cert
可以通過以下 GET API 獲取 issuer-uri 和 jwk-set-uri:
http://localhost:8081/realms/{realm_name}/.well-known/openid-configuration
## 將 {realm_name} 替換為您的 Realm 名稱
創建 REST Controller
@RestController
@RequestMapping(value = "/data")
public class DataController {
@GetMapping(value = "/user")
@PreAuthorize("hasRole('client_user')")
public String userApi(){
return "I am a user";
}
@GetMapping(value = "/admin")
@PreAuthorize("hasRole('client_admin')")
public String adminApi(){
return "I am an Admin";
}
}
我們的集成現在已經完成,可以通過生成 Keycloak 的 access token 來訪問 API(如上所述)。
使用 testuser 的 JWT 調用 /data/admin 接口時,會返回 403,但可以正常訪問 /data/user。對于 testadmin 用戶也是如此。
資源
- Keycloak 入門文檔請參閱 https://www.keycloak.org/guides#getting-started。
- JWT 官網https://jwt.io/
總結
本文詳細介紹了如何通過 Keycloak 生成 JWT 令牌,并將其與 Spring Boot 集成,實現基于角色的權限控制。
通過配置 Keycloak 生成令牌,設置 Spring Security 的 JWT 解析邏輯,以及定義基于角色的訪問控制,我們構建了一個安全、高效的認證與授權機制。
這套流程為應用的安全性和擴展性提供了保障,適用于多角色分布式系統開發。