1. 概述
本教程將介紹如何使用 Spring Boot 和 Keycloak 配置後端,並使用 OAuth2。
我們將使用 Keycloak 作為 OpenID 供應商。可以將其視為一個 user-service,負責身份驗證和用户數據(角色、個人資料、聯繫信息等)。它是最完整且功能最全面的 OpenID Connect (OIDC) 實現之一,具有以下功能:
- 單點登錄 (SSO) 和單點註銷 (背傳註銷)
- 身份中介、社交登錄和用户聯邦
- 用於服務器管理和用户賬户管理的 UI
- 一個管理 REST API,用於程序化控制所有內容
在審查 Spring Security 中 OAuth2 的配置選項後,我們將配置兩個不同的 Spring Boot 應用程序:
- 一個狀態感知客户端,使用 oauth2Login
- 一個無狀態 oauth2ResourceServer
2. 使用 Docker 快速啓動 Keycloak
在本節中,我們將使用預先配置的 Realm 啓動 Keycloak 服務器。有關如何創建此類 Realm 的信息,請參閲第 6 節。
2.1. Docker Compose 文件
使用 Keycloak Docker 鏡像隔離授權服務器的最簡單方法是拉取鏡像。為了配置它,我們將使用 Docker Compose 文件:
services:
keycloak:
container_name: baeldung-keycloak.openid-provider
image: quay.io/keycloak/keycloak:25.0.1
command:
- start-dev
- --import-realm
ports:
- 8080:8080
volumes:
- ./keycloak/:/opt/keycloak/data/import/
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
KC_HTTP_PORT: 8080
KC_HOSTNAME_URL: http://localhost:8080
KC_HOSTNAME_ADMIN_URL: http://localhost:8080
KC_HOSTNAME_STRICT_BACKCHANNEL: true
KC_HTTP_RELATIVE_PATH: /
KC_HTTP_ENABLED: true
KC_HEALTH_ENABLED: true
KC_METRICS_ENABLED: true
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ['CMD-SHELL', '[ -f /tmp/HealthCheck.java ] || echo "public class HealthCheck { public static void main(String[] args) throws java.lang.Throwable { System.exit(java.net.HttpURLConnection.HTTP_OK == ((java.net.HttpURLConnection)new java.net.URL(args[0]).openConnection()).getResponseCode() ? 0 : 1); } }" > /tmp/HealthCheck.java && java /tmp/HealthCheck.java http://localhost:8080/auth/health/live']
interval: 5s
timeout: 5s
retries: 202.2. 使用伴侶項目 Realm
伴侶項目包含一個 JSON 文件,其中定義了我們需要的某些 Keycloak 對象:
- 一個 baeldung-keycloak Realm
- 一個 baeldung-keycloak-confidential 客户端,其 secret 為 secret
- 一個 Mapper,用於為訪問和向 baeldung-keycloak-confidential 客户端頒發的 ID 令牌添加 Realm 角色(默認情況下,僅訪問令牌包含 Realm 角色)
- 一個 NICE Realm 角色,該角色在 Realm 級別定義(Keycloak 還支持客户端級別的角色定義)
- 兩個用户:brice(擁有 NICE 角色)和 igor(不擁有 NICE 角色)。 兩個用户都具有 secret 作為 secret
—import-realm 參數在 compose 文件中指示 Keycloak 從其 data/import/ 文件夾中的 JSON 文件加載對象。
鑑於 compose 文件中定義的體積,我們應該將 keycloak/baeldung-keycloak-realm.json 文件從伴侶項目複製到與 compose 文件相關的 ./keycloak/ 文件夾。
2.3. 啓動 Docker 容器
為了簡化初始配置,上述 Docker Compose 文件使用了 KEYCLOAK_ADMIN_PASSWORD 環境變量,我們應該在啓動容器之前將其設置好:
export KEYCLOAK_ADMIN_PASSWORD=admin
docker compose up -d運行完這些命令後,Keycloak 啓動。我們知道啓動完成的標誌是看到包含 Keycloak 25.0.1 […] started 的行。
現在,我們可以通過訪問 http://localhost:8080 使用 admin/admin 作為憑據,使用 Keycloak 管理控制枱。
3. Spring Security 與 OAuth2
通常人們對 OAuth2 以及 Spring Security 提供的不同配置選項存在很多混淆。
3.1 OAuth2 參與者
首先,讓我們回顧一下 OAuth2 的三個關鍵參與者。
Authorization 服務器負責資源擁有者的身份驗證,並向客户端頒發令牌——在本教程中,我們使用 Keycloak。
客户端驅動流程以從授權服務器獲取令牌,存儲令牌,並使用有效令牌授權對資源服務器的請求。當資源擁有者是用户時,客户端使用 授權碼流程(或者,當用户設備具有有限的輸入能力時,使用 設備流程)登錄用户並獲取令牌,以便以他們的名義向資源服務器發送請求。
在本教程中,我們的客户端是一個使用 oauth2Login 的 Spring 應用以及 Postman。
資源服務器提供對 REST 資源的受保護訪問。它們檢查令牌的有效性(發行者、過期時間、受眾等)並控制客户端的訪問(客户端範圍、資源擁有者角色、資源擁有者與訪問的資源之間的關係等)。我們將配置一個 REST API 作為資源服務器。
值得注意的是,從授權服務器獲取令牌的流程由 OAuth2 客户端負責。因此,用户登錄和註銷從來不是資源服務器的關注點。
3.2. <em>oauth2Login</em>
Spring Security 的 <em>oauth2Login</em> 配置支持 <em>authorization-code</em> 和 <em>refresh-token</em> 流。它還配置了一個授權客户端倉庫用於存儲令牌(默認情況下在會話中),以及一個授權客户端管理器,以便在需要時刷新令牌後再返回。
請求指向 Spring 客户端時,如果啓用了 <em>oauth2Login</em>,則會使用會話 Cookie 進行授權。因此,在帶有 <em>oauth2Login</em> 的 <em>Security(Web)FilterChain</em> 實例中,必須始終啓用 CSRF 攻擊防護。
請求成功授權後,安全上下文中使用的 <em>Authentication</em> 類型為 <em>OAuth2AuthenticationToken</em>。
如果提供者使用 OpenID 自動配置,則 <em>OAuth2AuthenticationToken principal</em> 是從 ID 令牌構建的 <em>OidcUser</em> 對象,否則,需要調用 <em>userinfo</em> 端點以設置 <em>OAuth2User</em> 對象作為 principal。
Spring Security 的權限映射使用 <em>GrantedAuthoritiesMapper</em> 或自定義 <em>OAuth2UserService</em>。
3.3. oauth2資源服務器
Spring Security 配置了 oauth2資源服務器 的 Bearer 令牌安全機制。它提供了使用內省(也稱為非明確令牌)和 JWT 解碼之間的選擇。
在資源服務器的情況下,用户狀態存儲在令牌聲明和會話中。這帶來了兩個主要優勢:
- CSRF 保護可以禁用——CSRF 攻擊依賴於會話,此處未使用
- 資源服務器易於擴展——無論請求路由到哪個實例,客户端和資源所有者的狀態都包含在聲明中
請求成功授權後,可以自定義安全上下文中的 身份驗證 類型,使用身份驗證轉換器。但默認身份驗證類型的詳細信息、如何從令牌轉換以及包含其中的權限取決於資源服務器的類型:
- 使用 JWT 解碼器,默認身份驗證類型為 JwtAuthenticationToken,權限與 JWT 身份驗證轉換器 配合使用,利用訪問令牌聲明。
- 使用訪問令牌內省,默認身份驗證類型為 BearerTokenAuthentication,權限通過 自定義內省器 解決,利用內省端點響應。
3.4. 選擇使用 oauth2Login 和 oauth2ResourceServer
我們可以從以上推斷得出,oauth2Login 和 oauth2ResourceServer 具有不同的用途。Spring Security 為兩者提供了不同的 Spring Boot starter,因為 兩者不應在同一個 Security(Web)FilterChain bean 中存在:
- oauth2Login 授權基於會話,而 oauth2ResourceServer 基於 Bearer 令牌
- 由於其基於會話的特性,oauth2Login 需要防止 CSRF 攻擊。但由於其無狀態的特性,資源服務器不需要它
- oauth2Login、oauth2ResourceServer 以及 oauth2ResourceServer 中的 Authentication 類型和配置的權限映射在 JWT 解碼器方面存在差異
由於無狀態應用程序的可擴展性,我們通常更傾向於使用 REST 端點配置 oauth2ResourceServer 。但這需要能夠將請求(或路由)發送到 REST API 的方能夠從授權服務器獲取令牌,將其存儲在某個狀態中(例如,會話或任何其他方式),並在令牌過期時刷新它們,並使用訪問令牌進行授權。
許多 REST 客户端可以做到這一點(例如 Spring 的 RestClient 和 WebClient,或具有 UI 的那些,如 Postman),但瀏覽器如果沒有像 Angular、React 或 Vue 這樣的框架的幫助,就無法做到這一點(並且現在由於安全原因,這種做法已被不推薦)。
oauth2Login 的主要用例是服務器端渲染的 UI,以及作為前端到 OAuth2 後端 Spring Cloud Gateway。但由於其狀態化特性,在水平擴展以實現高可用性和負載均衡時,我們需要使用像 Spring Session 或智能代理這樣的東西。
3.5. 異構請求授權機制組合
為了授權來自不同異構消費者的請求,您可能需要配置應用程序,使用多個 oauth2Login, oauth2ResourceServer, x509, formLogin, Basic 認證等。 這通常發生在 OAuth2 BFF 中,前端請求使用 oauth2Login 進行授權,而 actuator 端點則使用 oauth2ResourceServer 或 Basic 認證。
在這種情況下,應為每個請求授權機制使用不同的 Security(Web)FilterChain 豆,所有豆都帶有不同的 @Order 標記,並且除了最後一個之外,所有豆都按順序使用 securityMatcher 定義應應用於哪個請求。 如果沒有 securityMatcher,則過濾器鏈將作為所有尚未匹配的請求的默認過濾器。
4. Thymeleaf 與登錄
我們使用 OAuth2 與 Spring Boot 和 Keycloak 的第一個用例是一個 Thymeleaf 應用程序,它使用 OpenID 供應商對用户進行身份驗證。 儘管簡單,但它展示了 Keycloak 在狀態化應用程序中的 基於角色的訪問控制 (RBAC)。
值得注意的是:
- 在具有單頁或移動應用程序的系統中使用,我們會使用 OAuth2 BFF 及其相似的 OAuth2 客户端配置。
- 我們的 Thymeleaf 應用程序是一個 OAuth2 客户端,因為它使用 oauth2Login 和 ID 令牌來構建用户身份驗證,但 它不使用訪問令牌 (它不向資源服務器發送請求)。 如前所述,瀏覽器與我們的 Spring 後端之間的請求使用會話 cookie 進行授權。
- 為了水平擴展,我們需要在實例之間共享會話 (Spring Session) 或使用智能代理將來自同一用户代理的所有請求路由到同一實例。
4.1. 依賴關係
我們的最重要依賴項,用於啓用 OAuth2 用户登錄,是 spring-boot-starter-oauth2-client。 此外,由於我們創建 Servlet 應用程序並渲染 Thymeleaf 模板,我們還需要 spring-boot-starter-web 和 spring-boot-starter-thymeleaf。
請將它們添加到 pom.xml 中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.5.7</version>
</dependency>這段代碼用於運行時足夠,但如果我們想模擬認證以在 Spring OAuth 2 應用程序中測試訪問控制,還需要 spring-security-test 及其 test 範圍。
4.2. 提供者 & 註冊配置
由於 Keycloak 通過 ${issuer-uri}/.well-known/openid-configuration 暴露其 OpenID 配置,並且 Spring Security 可以從其 OpenID 配置中自動配置提供者,因此定義 issuer-uri 即可。
spring.security.oauth2.client.provider.baeldung-keycloak.issuer-uri=http://localhost:8080/realms/baeldung-keycloak現在讓我們配置客户端 註冊,使用上述 提供商:
spring.security.oauth2.client.registration.keycloak.provider=baeldung-keycloak
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.client-id=baeldung-keycloak-confidential
spring.security.oauth2.client.registration.keycloak.client-secret=secret
spring.security.oauth2.client.registration.keycloak.scope=openid我們指定在client-id中的值與 Keycloak 管理控制枱中聲明的機密客户端相匹配。
我們可以從憑據選項卡中獲取client-secret的值。 導入的域中,密鑰為secret。
4.3. 將 Keycloak Realm 角色映射到 Spring Security 權限
有幾種方法可以在過濾器鏈中使用 <em oauth2Login</em>> 來映射權限,但最簡單的方法可能是暴露一個 <em GrantedAuthoritiesMapper</em>> Bean,並且我們將在這裏採用這種方法。
讓我們首先定義一個 Bean,負責從 Keycloak 聲明集中提取權限。該聲明集中可能包含 ID 或訪問令牌負載,以及 <em userinfo</em>> 或反思響應主體:
interface AuthoritiesConverter extends Converter<Map<String, Object>, Collection<GrantedAuthority>> {}
@Bean
AuthoritiesConverter realmRolesAuthoritiesConverter() {
return claims -> {
var realmAccess = Optional.ofNullable((Map<String, Object>) claims.get("realm_access"));
var roles = realmAccess.flatMap(map -> Optional.ofNullable((List<String>) map.get("roles")));
return roles.map(List::stream)
.orElse(Stream.empty())
.map(SimpleGrantedAuthority::new)
.map(GrantedAuthority.class::cast)
.toList();
};
}由於 JVM 中的泛型類型擦除以及應用程序上下文可能存在大量不同的 Converter Bean 及其不同的輸入和輸出,該 AuthoritiesConverter 接口可作為 BeanFactory 在搜索 Converter<Map<String, Object>, Collection<GrantedAuthority>> Bean 時的一個有用的提示。
由於我們使用其 issuer-uri 自動配置了 OIDC 提供程序,我們收到的 GrantedAuthoritiesMapper 中的輸入是 OidcUserAuthority 實例:
@Bean
GrantedAuthoritiesMapper authenticationConverter(AuthoritiesConverter authoritiesConverter) {
return (authorities) -> authorities.stream()
.filter(authority -> authority instanceof OidcUserAuthority)
.map(OidcUserAuthority.class::cast)
.map(OidcUserAuthority::getIdToken)
.map(OidcIdToken::getClaims)
.map(authoritiesConverter::convert)
.flatMap(roles -> roles.stream())
.collect(Collectors.toSet());
}值得注意的是,我們如何注入並使用上面定義的 authorities 轉換 Bean。
4.4. 將 SecurityFilterChain Bean 組合在一起
以下是我們將使用的完整 SecurityFilterChain:
@Bean
SecurityFilterChain clientSecurityFilterChain(
HttpSecurity http,
ClientRegistrationRepository clientRegistrationRepository) throws Exception {
http.oauth2Login(Customizer.withDefaults());
http.logout((logout) -> {
var logoutSuccessHandler =
new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}/");
logout.logoutSuccessHandler(logoutSuccessHandler);
});
http.authorizeHttpRequests(requests -> {
requests.requestMatchers("/", "/favicon.ico").permitAll();
requests.requestMatchers("/nice").hasAuthority("NICE");
requests.anyRequest().denyAll();
});
return http.build();
}讓我們分解一下,以便更好地理解關鍵部分。
oauth2Login() 方法添加了 OAuth2LoginAuthenticationFilter 到過濾器鏈。 此過濾器攔截請求並應用必要的邏輯來處理授權碼和刷新令牌流程。 它還將在會話中存儲令牌。
要理解 OAuth2 註銷的工作原理,我們應該記住用户至少有兩項獨立的會話:一項在授權服務器(Keycloak 在我們的案例中)上,一項在每個客户端上,以及使用 oauth2Login。
對於完整的註銷,所有會話必須終止。
OpenID 標準定義了不同的實現方式,但 我們將重點關注 RP-Initiated Logout,我們使用 OidcClientInitiatedLogoutSuccessHandler 進行配置。
在這個流程中,用户代理(用户的瀏覽器)首先從 Relying Party(我們的 Spring 應用,使用 oaut2Login)中註銷,以關閉其會話。 RP 響應為到 OpenID Provider(OP)的重定向,其中包含與會話關聯的 ID 令牌,以及註銷後 URL(我們的 Thymeleaf index)。 OP 然後關閉其會話並將用户代理重定向到提供的 URL。
總結一下,我們定義了訪問控制規則:
- 對於用户從 index 登錄,它必須對匿名請求可訪問。
- 路徑 /nice 僅對具有 NICE 權限的已認證用户可訪問。
4.5. Thymeleaf Web 頁面
我們使用 Thymeleaf 為我們的兩個 Web 頁面:
- index.html 根據用户的狀態顯示 登錄 或 註銷 按鈕。 此外,為了提供良好的用户體驗,我們僅向擁有 NICE 權限的已認證用户顯示指向 /nice 的按鈕。
- nice.html 僅包含靜態內容。
我們的 index.html 具有條件邏輯,可以從用户的會話中獲取值:
<div class="container">
<h1 class="form-signin-heading">Baeldung: Keycloak & Spring Boot</h1>
<p>Welcome to a Thymeleaf UI served by Spring Boot and secured using Keycloak!</p>
<div th:if="${!isAuthenticated}">
<a href="/oauth2/authorization/keycloak"><button type="button"
class="btn btn-lg btn-primary btn-block">Login</button></a>
</div>
<div th:if="${isAuthenticated}">
<p>Hi <span th:utext="${name}">..!..</span>!</p>
<a href="/logout"><button type="button" class="btn btn-lg btn-primary">Logout</button></a>
<a th:if="${isNice}" href="/nice">
<button type="button" class="btn btn-lg btn-primary">Browse to NICE users page</button></a>
</div>
</div>4.6. 控制器
<em>UiController</em> 構建索引頁面的模型(包括用户<em>name</em>,以及<em>isAuthenticated</em>和<em>isNice</em>標誌),並解析 Thymeleaf 模板:
@Controller
public class UiController {
@GetMapping("/")
public String getIndex(Model model, Authentication auth) {
model.addAttribute("name",
auth instanceof OAuth2AuthenticationToken oauth && oauth.getPrincipal() instanceof OidcUser oidc
? oidc.getPreferredUsername()
: "");
model.addAttribute("isAuthenticated",
auth != null && auth.isAuthenticated());
model.addAttribute("isNice",
auth != null && auth.getAuthorities().stream().anyMatch(authority -> {
return Objects.equals("NICE", authority.getAuthority());
}));
return "index.html";
}
@GetMapping("/nice")
public String getNice(Model model, Authentication auth) {
return "nice.html";
}
}4.7. 使用 Thymeleaf 應用及 oauth2Login
我們可以現在使用我們最喜歡的 IDE 啓動應用。 否則,從 Maven 父上下文,我們可以運行:
sh ./mvnw -pl spring-boot-mvc-client spring-boot:run通過將瀏覽器指向 http://localhost:8081/,我們應該看到一個帶有登錄按鈕的頁面。
用於為 NICE 用户預留的頁面的按鈕僅在以 brice (而非 igor) 身份登錄時可見。
在 RP-Initiated Logout 後次次回滾登錄嘗試時,我們必須輸入憑據。
如果從 Java conf 中刪除 logout 部分,則從 Spring 應用程序註銷時不會結束 Keycloak 會話,新的登錄嘗試將靜默完成。從 Keycloak 的角度來看,我們仍然登錄狀態。
5. 使用 JWT 解碼器構建 REST API
現在,讓我們繼續介紹 Spring Boot 和 Keycloak 與 OAuth2 的結合,重點構建一個 無狀態 REST API,它期望在 JWT 格式中接收 Bearer 訪問令牌。
5.1. 依賴關係
目前,我們最重要的依賴項是 spring-boot-starter-oauth2-resource-server。 在創建 Servlet 應用時,我們還需要添加 spring-boot-starter-web。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.5.7</version>
</dependency>這段代碼用於運行時足夠,但如果要在測試中模擬身份驗證,還需要使用 spring-security-test 及其 test 範圍。
5.2. 配置 JWT 解碼器
為了在資源服務器上驗證訪問令牌,我們有選擇 JWT 解碼器或進行內省兩種方式。前者要求授權服務器以 JWT 格式頒發訪問令牌。然而,內省方式效率更高,因為它需要資源服務器對每個請求都向授權服務器發起調用,這會引入延遲並可能導致授權服務器過載。
Keycloak 訪問令牌是 JWT,並且使用 Spring Boot 時,只需設置一個屬性即可配置資源服務器使用 JWT 解碼器。
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/baeldung-keycloak5.3. 將 Keycloak 域角色映射到 Spring Security 權限
對於具有 JWT 解碼器的資源服務器,我們應該 配置身份轉換器。
我們可以在此重用先前身份轉換器 Bean 的實現:
@Bean
JwtAuthenticationConverter authenticationConverter(AuthoritiesConverter authoritiesConverter) {
var authenticationConverter = new JwtAuthenticationConverter();
authenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
return authoritiesConverter.convert(jwt.getClaims());
});
return authenticationConverter;
}值得注意的是,這次我們傳遞的是訪問令牌聲明,而不是身份令牌聲明。
5.4. 將 SecurityFilterChain Bean 組合在一起
我們現在準備好定義一個資源服務器安全過濾器鏈:
@Bean
SecurityFilterChain resourceServerSecurityFilterChain(
HttpSecurity http,
Converter<Jwt, AbstractAuthenticationToken> authenticationConverter) throws Exception {
http.oauth2ResourceServer(resourceServer -> {
resourceServer.jwt(jwtDecoder -> {
jwtDecoder.jwtAuthenticationConverter(authenticationConverter);
});
});
http.sessionManagement(sessions -> {
sessions.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}).csrf(csrf -> {
csrf.disable();
});
http.authorizeHttpRequests(requests -> {
requests.requestMatchers("/me").authenticated();
requests.anyRequest().denyAll();
});
return http.build();
}在以下代碼中:
- 第一個區塊啓用資源服務器配置,使用 JWT 解碼器和自定義認證轉換器,將 Keycloak 角色轉換為 Spring Security 權限。
- 然後完全禁用會話和 CSRF 防禦。
- 最後,我們定義了一些訪問控制:僅有效的 Bearer 令牌請求才能訪問 /me 端點,並且無論請求的授權方式,都禁止訪問任何其他資源。
5.5. REST 控制器
為了演示 Keycloak 角色映射在資源服務器中的應用,我們將暴露一個端點,該端點會反映來自安全上下文中 JwtAuthenticationToken 的一些信息:
@RestController
public class MeController {
@GetMapping("/me")
public UserInfoDto getGretting(JwtAuthenticationToken auth) {
return new UserInfoDto(
auth.getToken().getClaimAsString(StandardClaimNames.PREFERRED_USERNAME),
auth.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList());
}
public static record UserInfoDto(String name, List roles) {}
}將 auth 轉換為 JwtAuthenticationToken 是安全的,因為訪問權限僅限於 isAuthenticated(),並且 JwtAuthenticationToken 是我們的身份驗證轉換器返回的內容。
如果路由是 permitAll(),我們還應該處理 AnonymousAuthenticationToken,以防未經授權的請求(即沒有 Bearer 令牌的請求)。
5.6. 嘗試 REST API
現在我們準備好嘗試我們的 REST API。 就像我們為 Thymeleaf 應用所做的那樣,我們可以使用我們最喜歡的 IDE 運行它,或者,從 Maven 父項目上下文執行:
sh ./mvnw -pl spring-boot-resource-server spring-boot:run然後,使用 Postman 從 Keycloak 獲取訪問令牌,我們應該在集合或請求的 Authorization 選項卡中打開它,選擇 OAuth2,並填寫我們之前在 Keycloak 中設置的值(重定向 URI)和 Spring 屬性,或者從 OpenID 配置 中獲取的值:
最後,我們向 http://localhost:8082/me 發送 GET 請求。響應應為包含我們 Keycloak 登錄和 Realm 角色 JSON 負載:
6. 創建 Keycloak 域
在本教程中,我們對 Keycloak 對象進行了隔離,並在創建 Docker 容器時導入了自定義域。這在團隊新成員入職或嘗試替代配置時非常有用。
讓我們詳細説明這些對象是如何初始創建的。
6.1. Realm
點擊右上角上的創建 Realm按鈕:
在下一個屏幕上,我們為 Realm 命名為baeldung-keycloak:
點擊“創建”按鈕後,我們將重定向到其詳細信息。
後續所有操作都將包含在名為baeldung-keycloak的 Realm 中。
<h3><strong>6.2. 密鑰客户端認證用户</strong></h3>
<p>現在,我們將導航到“客户端”頁面,並創建一個名為 <em >baeldung-keycloak-confidential</em> 的新客户端。</p>
<img src="/file/story/attachments/image/l/354be89e-30d8-4526-b92a-2ce129212d62"/>
<p>在下一個屏幕上,我們將確保 <em >客户端認證</em> 已啓用——這使客户端成為“密鑰客户端”,並且只有 <em >標準流程</em> 已選中,從而激活了授權碼和刷新令牌流程:</p>
<img src="/file/story/attachments/image/l/b27b6a3e-89bf-4a51-800f-8d4aa47c14cd"/>
<p>最後,我們需要允許我們的客户端的重定向 URI 和源:</p>
<img src="/file/story/attachments/image/l/58140734-dd63-4b97-bb46-e31c33fb65c8"/>
<p>由於我們的 Spring Boot 客户端應用程序,帶有 <em >oauth2Login</em> 配置,將在端口 <em >8081</em> 上運行,並且 <em >keycloak</em> 作為 registration-id,我們設置:</p>
<ul >
<li> <em >http://localhost:8081/login/oauth2/code/keycloak</em> 作為有效重定向 URI。</li>
<li> <em >http://localhost:8081/</em> 作為有效註銷 URI(Thymeleaf 首頁)</li>
<li> <em >+</em> 作為 Web 來源,允許來自有效重定向 URI 的所有來源</li>
</ul >
6.3. 配置哪些JSON負載包含 Realm 角色
為了確保 Keycloak 角色被添加到我們 Spring 應用中使用的各種負載中,我們:
導航到客户端詳情中的 客户端範圍 選項卡:
點擊 baeldung-keycloak-confidential-dedicated 範圍以訪問其配置詳情:
點擊 添加預定義映射器 按鈕,並選擇 realm 角色 映射器:
在編輯它時,我們可以選擇哪些 JSON 負載包含 realm 角色:
- 訪問令牌:對於具有 JWT 解碼器的資源服務器
- 內省:對於具有令牌內省功能的資源服務器(Spring Security 用語中的不透明令牌)
- 身份令牌:對於具有 oauth2Login 和 OpenID 的客户端(使用 issuer-uri 自動配置提供者)
- 用户信息令牌:對於僅具有 oauth2Login 但不具有 OpenID 的客户端(issuer-uri 留空,其他提供者屬性在 Spring 配置中設置)
因為我們在具有 oauth2Login (一個 Thymeleaf 應用程序) 的 OpenID 客户端和具有 JWT 解碼器的資源服務器中實現了某些基於角色的訪問控制 (RBAC),我們應該確保 realm 角色被添加到訪問和身份令牌中。
6.4. Realm 角色
在 Keycloak 中,我們可以定義角色並將其分配給用户,以應用於整個 Realm 或基於客户端進行分配。 本教程將重點介紹 Realm 角色。
請導航到 Realm 角色 頁面:
在那裏,我們 創建 NICE 角色:
6.5. 用户和 Realm 角色分配
請前往 用户 頁面添加兩個用户,一個名為 brice 並授予其 NICE 角色,另一個名為 igor 並未授予任何 Realm 角色:
首先,我們創建一個名為 brice 的新用户:
點擊 創建 按鈕後,我們可以查看用户詳細信息。 接下來,我們應該瀏覽到 憑據 選項卡,以設置其密碼:
最後,我們導航到 角色映射 選項卡,以分配 NICE 角色(請注意 按 Realm 角色過濾 下拉菜單):
我們可能可以重複本節中的説明,用於 igor,並跳過角色分配。
6.6. 導出領域
完成領域配置後,可以使用以下命令導出它(在 Docker 桌面中,在運行容器的 Exec 選項卡中進行操作):
cd /opt/keycloak/bin/
sh ./kc.sh export --dir /tmp/keycloak/ --users realm_file我們可以通過在 Files 選項卡中收集每個領域的文件 JSON 格式文件。
7. 結論
本文介紹了使用 Spring Boot 和 Keycloak 配置後端服務,並使用 OAuth2 進行了配置。
除了使用 Docker 配置一個最小的 Keycloak 環境,我們還學習瞭如何導入和導出 Realm。
此外,在對 Spring 應用中 OAuth2 的不同配置選項進行審查後,我們配置了 Spring 應用作為 Stateful OAuth2 客户端或 Stateless OAuth2 資源服務器,使用了 oauth2Login 或其他配置。