SpringBoot整合Keycloak的項(xiàng)目實(shí)踐
1、概覽
本文將帶你了解如何設(shè)置 Keycloak 服務(wù)器,以及如何使用 Spring Security OAuth2.0 將Spring Boot應(yīng)用連接到 Keycloak 服務(wù)器。
2、Keycloak 是什么?
Keycloak是針對(duì)現(xiàn)代應(yīng)用和服務(wù)的開(kāi)源身份和訪問(wèn)管理解決方案。
Keycloak 提供了諸如單點(diǎn)登錄(SSO)、身份代理和社交登錄、用戶聯(lián)盟、客戶端適配器、管理控制臺(tái)和賬戶管理等功能。
本文使用 Keycloak 的管理控制臺(tái),使用 Spring Security OAuth2.0 設(shè)置和連接 Spring Boot。
3、設(shè)置 Keycloak 服務(wù)器
設(shè)置和配置 Keycloak 服務(wù)器。
3.1、下載和安裝 Keycloak
有多種發(fā)行版可供選擇,本文使 Keycloak-22.0.3 獨(dú)立服務(wù)器發(fā)行版。點(diǎn)擊這里從官方下載。
下載完后,解壓縮并從終端啟動(dòng) Keycloak:
unzip keycloak-22.0.3.zip cd keycloak-22.0.3 bin/kc.sh start-dev
運(yùn)行這些命令后,Keycloak 會(huì)啟動(dòng)服務(wù)。如果你看到一行類(lèi)似于Keycloak 22.0.3 [...] started的內(nèi)容,就表示服務(wù)器啟動(dòng)成功。
打開(kāi)瀏覽器,訪問(wèn)http://localhost:8080,會(huì)被重定向到http://localhost:8080/auth以創(chuàng)建管理員進(jìn)行登錄:

創(chuàng)建一個(gè)名為initial1的初始管理員用戶,密碼為zaq1!QAZ。點(diǎn)擊 “Create”后,可以看到 “User Created” 的提示信息。
現(xiàn)在進(jìn)入管理控制臺(tái)。在登錄頁(yè)面,輸入initial管理員用戶憑證:

3.2、創(chuàng)建 Realm
登錄成功后,進(jìn)入控制臺(tái),默認(rèn)為MasterRealm。
導(dǎo)航到左上角,找到 “Create realm” 按鈕:

點(diǎn)擊它,添加一個(gè)名為SpringBootKeycloak的新 Realm:

單擊 “Create” 按鈕,創(chuàng)建一個(gè)新的 Realm。會(huì)被重定向到該 Realm。接下來(lái)的所有操作都將在這個(gè)新的SpringBootKeycloakRealm 中執(zhí)行。
3.3、創(chuàng)建客戶端
現(xiàn)在進(jìn)入 “Clients” 頁(yè)面。如下圖所示,Keycloak 已經(jīng)內(nèi)置了客戶端:

我們需要在應(yīng)用中添加一個(gè)新客戶端,點(diǎn)擊 “Create”,將新客戶端命名為login-app:

在下一步的設(shè)置中,除了 “Valid Redirect URIs” 字段外,其他字段保留所有默認(rèn)值。該字段包含將使用此客戶端進(jìn)行身份驗(yàn)證的應(yīng)用 URL:

稍后,我們會(huì)創(chuàng)建一個(gè)運(yùn)行于 8081 端口的 Spring Boot 應(yīng)用,該應(yīng)用將使用該客戶端。因此,在上面使用了http://localhost:8081/的重定向 URL。
3.4、創(chuàng)建角色和用戶
Keycloak 使用基于角色的訪問(wèn);因此,每個(gè)用戶都必須有一個(gè)角色。
進(jìn)入 “Realm Roles” 頁(yè)面:

然后添加用戶角色:

現(xiàn)在有了一個(gè)可以分配給用戶的角色,但由于還沒(méi)有用戶,讓我們?nèi)?“Users” 頁(yè)面添加一個(gè):

添加一個(gè)名為user1的用戶:

用戶創(chuàng)建后,會(huì)顯示一個(gè)包含其詳細(xì)信息的頁(yè)面:

現(xiàn)在進(jìn)入 “Credentials” 選項(xiàng)卡。把初始密碼設(shè)置為xsw2@WS:

最后,進(jìn)入 “Role Mappings” 選項(xiàng)卡。為user1分配用戶角色:

4、使用 Keycloak API 生成 Access Token
Keycloak 提供了用于生成和刷新 Access Token 的 REST API,可用于創(chuàng)建自己的登錄頁(yè)面。
首先,向如下 URL 發(fā)送 POST 請(qǐng)求,從 Keycloak 獲取 Access Token:
http://localhost:8080/realms/SpringBootKeycloak/protocol/openid-connect/token
請(qǐng)求體應(yīng)包含x-www-form-urlencoded格式的參數(shù):
client_id:<your_client_id> username:<your_username> password:<your_password> grant_type:password
這會(huì)得到一個(gè)access_token和一個(gè)refresh_token。
每次請(qǐng)求受 Keycloak 保護(hù)的資源時(shí),都應(yīng)使用 Access Token,只需將其放在Authorization頭中即可:
headers: {
'Authorization': 'Bearer' + access_token
}
Access Token 過(guò)期后,可以通過(guò)向上述相同的 URL 發(fā)送 POST 請(qǐng)求來(lái)刷新 Access Token,但請(qǐng)求中應(yīng)包含 Refresh Token,而不是用戶名和密碼:
{
'client_id': 'your_client_id',
'refresh_token': refresh_token_from_previous_request,
'grant_type': 'refresh_token'
}
Keycloak 會(huì)響應(yīng)新的access_token和refresh_token。
5、創(chuàng)建和配置 Spring Boot 應(yīng)用
創(chuàng)建一個(gè) Spring Boot 應(yīng)用,并將其配置為 OAuth 客戶端,與 Keycloak 服務(wù)器進(jìn)行交互。
5.1、依賴(lài)
使用 Spring Security OAuth2.0 客戶端連接到 Keycloak 服務(wù)器。
首先,在pom.xml中聲明spring-boot-starter-oauth2-client和spring-boot-starter-security依賴(lài):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
使用spring-boot-starter-oauth2-resource-server將身份驗(yàn)證控制委托給 Keycloak 服務(wù)器。它允許我們使用 Keycloak 服務(wù)器驗(yàn)證 JWT Token:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
現(xiàn)在,Spring Boot 應(yīng)用可以與 Keycloak 交互了。
5.2、Keycloak 配置
將 Keycloak 客戶端視為 OAuth 客戶端。因此,需要配置 Spring Boot 應(yīng)用以使用 OAuth 客戶端。
ClientRegistration類(lèi)保存客戶端的所有基本信息。Spring 自動(dòng)配置會(huì)查找模式為spring.security.oauth2.client.registration.[registrationId]的屬性,并使用 OAuth 2.0 或 OpenID Connect(OIDC) 注冊(cè)客戶端。
客戶端注冊(cè)配置:
spring.security.oauth2.client.registration.keycloak.client-id=login-app spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code spring.security.oauth2.client.registration.keycloak.scope=openid
在client-id中指定的值與我們?cè)诠芾砜刂婆_(tái)中命名的客戶端相匹配。
Spring Boot 應(yīng)用需要與 OAuth 2.0 或 OIDC Provider 交互,以處理不同授權(quán)方式的實(shí)際請(qǐng)求邏輯。因此,需要配置 OIDC Provider。它可以根據(jù) Schemaspring.security.oauth2.client.provider.[provider name]的屬性值自動(dòng)配置。
OIDC Provider 配置:
spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8080/realms/SpringBootKeycloak spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username
在issuer-uri中指定路徑(我們是在 8080 端口啟動(dòng) Keycloak 的)。該屬性標(biāo)識(shí)了授權(quán)服務(wù)器的基本 URI,輸入在 Keycloak 管理控制臺(tái)中創(chuàng)建的 Realm 名稱(chēng)。此外,還可以將user-name-attribute定義為preferred_username,以便在 Controller 的Principal中填充合適的用戶。
最后,添加針對(duì) Keycloak 服務(wù)器驗(yàn)證 JWT Token 所需的配置:
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/SpringBootKeycloak
5.3、配置類(lèi)
創(chuàng)建SecurityFilterChainBean 來(lái)配置HttpSecurity。使用http.oauth2Login()啟用 OAuth2 登錄。
創(chuàng)建 Security 配置:
@Configuration
@EnableWebSecurity
class SecurityConfig {
private final KeycloakLogoutHandler keycloakLogoutHandler;
SecurityConfig(KeycloakLogoutHandler keycloakLogoutHandler) {
this.keycloakLogoutHandler = keycloakLogoutHandler;
}
@Bean
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Order(1)
@Bean
public SecurityFilterChain clientFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(new AntPathRequestMatcher("/"))
.permitAll()
.anyRequest()
.authenticated();
http.oauth2Login()
.and()
.logout()
.addLogoutHandler(keycloakLogoutHandler)
.logoutSuccessUrl("/");
return http.build();
}
@Order(2)
@Bean
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(new AntPathRequestMatcher("/customers*"))
.hasRole("USER")
.anyRequest()
.authenticated();
http.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.build();
}
}
在上面的代碼中,oauth2Login()方法將OAuth2LoginAuthenticationFilter添加到過(guò)濾器鏈中。該過(guò)濾器會(huì)攔截請(qǐng)求并應(yīng)用 OAuth 2 身份驗(yàn)證所需的邏輯。oauth2ResourceServer方法將根據(jù) Keycloak 服務(wù)器驗(yàn)證綁定的 JWT Token。
在configure()方法中根據(jù)權(quán)限和角色配置訪問(wèn)權(quán)限。這些約束條件可確保對(duì)/customers/*的每個(gè)請(qǐng)求只有在請(qǐng)求者是具有USER角色的經(jīng)過(guò)身份驗(yàn)證的用戶時(shí)才會(huì)獲得授權(quán)。
最后,添加了KeycloakLogoutHandler類(lèi)來(lái)處理 Keycloak 注銷(xiāo):
@Component
public class KeycloakLogoutHandler implements LogoutHandler {
private static final Logger logger = LoggerFactory.getLogger(KeycloakLogoutHandler.class);
private final RestTemplate restTemplate;
public KeycloakLogoutHandler(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication auth) {
logoutFromKeycloak((OidcUser) auth.getPrincipal());
}
private void logoutFromKeycloak(OidcUser user) {
String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout";
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(endSessionEndpoint)
.queryParam("id_token_hint", user.getIdToken().getTokenValue());
ResponseEntity<String> logoutResponse = restTemplate.getForEntity(
builder.toUriString(), String.class);
if (logoutResponse.getStatusCode().is2xxSuccessful()) {
logger.info("Successfulley logged out from Keycloak");
} else {
logger.error("Could not propagate logout to Keycloak");
}
}
}
KeycloakLogoutHandler類(lèi)實(shí)現(xiàn)了LogoutHandler,并向 Keycloak 發(fā)送注銷(xiāo)請(qǐng)求。
現(xiàn)在,通過(guò)身份驗(yàn)證后,就可以訪問(wèn)內(nèi)部 customers 頁(yè)面了。
5.4、Thymeleaf Web 頁(yè)面
使用 Thymeleaf 渲染頁(yè)面。
有三個(gè)頁(yè)面:
external.html- 面向外部的頁(yè)面customers.html- 面向內(nèi)部的頁(yè)面,其訪問(wèn)權(quán)限僅限于具有user角色的認(rèn)證用戶layout.html- 一個(gè)簡(jiǎn)單的布局,由兩個(gè)片段組成,分別用于面向外部的頁(yè)面和面向內(nèi)部的頁(yè)面
Thymeleaf 模板的代碼可在Github上獲取。
5.5、Controller
Web Controller 會(huì)將內(nèi)部和外部 URL 映射到相應(yīng)的 Thymeleaf 模板:
@GetMapping(path = "/")
public String index() {
return "external";
}
@GetMapping(path = "/customers")
public String customers(Principal principal, Model model) {
addCustomers();
model.addAttribute("customers", customerDAO.findAll());
model.addAttribute("username", principal.getName());
return "customers";
}
/customers會(huì)從 Repository 中檢索所有客戶,并將結(jié)果作為屬性添加到 Model 中。之后,在 Thymeleaf 中遍歷結(jié)果。
為了能夠顯示用戶名,還注入了Principal。
注意,這里只是將客戶(customers)作為原始數(shù)據(jù)來(lái)顯示,僅此而已。
6、演示
現(xiàn)在,測(cè)試應(yīng)用。通過(guò)集成開(kāi)發(fā)環(huán)境(如 Spring Tool Suite - STS)運(yùn)行 Spring Boot 應(yīng)用,或者在終端運(yùn)行如下命令:
mvn clean spring-boot:run
訪問(wèn)http://localhost:8081,如下:

現(xiàn)在,點(diǎn)擊 “customers” 戶進(jìn)入內(nèi)部頁(yè)面,這是敏感信息的位置。
然后會(huì)被重定向到通過(guò) Keycloak 進(jìn)行身份驗(yàn)證,以檢查我們是否被授權(quán)查看此內(nèi)容:

用user1的憑證登錄,Keycloak 會(huì)驗(yàn)證我們的授權(quán),確認(rèn)我們擁有用戶角色,然后會(huì)被重定向到受限的 “customers” 頁(yè)面:

現(xiàn)在,整個(gè)流程已經(jīng)完畢了。你可以看到,Spring Boot 無(wú)縫地處理了調(diào)用 Keycloak 授權(quán)服務(wù)器的整個(gè)過(guò)程。我們無(wú)需調(diào)用 Keycloak API 自己生成 Access Token,甚至無(wú)需在請(qǐng)求受保護(hù)資源時(shí)明確發(fā)送Authorization頭。
7、總結(jié)
本文介紹了如何如何設(shè)置了 Keycloak 服務(wù)器,以及如何在 Spring Boot 中使用 Spring Security OAuth2.0 結(jié)合 Keycloak 實(shí)現(xiàn)認(rèn)證和授權(quán)。
到此這篇關(guān)于SpringBoot整合Keycloak的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)SpringBoot整合Keycloak內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Collection中的size()和isEmpty()區(qū)別說(shuō)明
這篇文章主要介紹了Collection中的size()和isEmpty()區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
java結(jié)合keytool如何實(shí)現(xiàn)非對(duì)稱(chēng)簽名和驗(yàn)證詳解
這篇文章主要給大家介紹了關(guān)于java結(jié)合keytool如何實(shí)現(xiàn)非對(duì)稱(chēng)簽名和驗(yàn)證的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
Spring Boot 集成 Quartz 使用Cron 表達(dá)式實(shí)現(xiàn)定
本文介紹了如何在SpringBoot項(xiàng)目中集成Quartz并使用Cron表達(dá)式進(jìn)行任務(wù)調(diào)度,通過(guò)添加Quartz依賴(lài)、創(chuàng)建Quartz任務(wù)、配置任務(wù)調(diào)度以及啟動(dòng)項(xiàng)目,可以實(shí)現(xiàn)定時(shí)任務(wù)的執(zhí)行,Cron表達(dá)式提供了靈活的任務(wù)調(diào)度方式,適用于各種復(fù)雜的定時(shí)任務(wù)需求,感興趣的朋友一起看看吧2025-03-03
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(43)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07
SpringBoot實(shí)用小技巧之如何動(dòng)態(tài)設(shè)置日志級(jí)別
這篇文章主要給大家介紹了關(guān)于SpringBoot實(shí)用小技巧之如何動(dòng)態(tài)設(shè)置日志級(jí)別的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
如何解決Maven無(wú)法拉取SNAPSHOT依賴(lài)問(wèn)題
在使用Maven管理項(xiàng)目時(shí),可能會(huì)遇到無(wú)法拉取SNAPSHOT版本依賴(lài)的問(wèn)題,這通常是因?yàn)镸aven默認(rèn)不支持直接拉取SNAPSHOT版本,遇到這樣的問(wèn)題,可以通過(guò)在項(xiàng)目的pom.xml文件中添加<repositories>標(biāo)簽,并配置啟用SNAPSHOT的倉(cāng)庫(kù)地址來(lái)解決2024-10-10
Spring 中使用Quartz實(shí)現(xiàn)任務(wù)調(diào)度
這篇文章主要介紹了Spring 中使用Quartz實(shí)現(xiàn)任務(wù)調(diào)度,Spring中使用Quartz 有兩種方式,感興趣的小伙伴們可以參考一下。2017-02-02

