Spring Security 中使用Keycloak作為認(rèn)證授權(quán)服務(wù)器

Keycloak對(duì)流行的Java應(yīng)用提供了適配器。在系列文章的上一篇我們演示了針對(duì)Spring Boot的安全保護(hù),用的就是適配器的一種。Keycloak同樣提供Spring Security的適配器,后續(xù)的幾篇文章我們就來共同學(xué)習(xí)Spring Security適配器的使用。
?
Keycloak的安裝可參考前面的系列教程。
適配器集成
在Spring 應(yīng)用中我們集成keycloak-spring-security-adapter:
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
<version>15.0.0</version>
</dependency>
在Spring Boot中可以這樣集成:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>15.0.0</version>
</dependency>
然后就能利用Spring Security的特性來集成Keycloak。Keycloak 提供了一個(gè) KeycloakWebSecurityConfigurerAdapter 作為創(chuàng)建WebSecurityConfigurer 實(shí)例的方便基類。我們可以編寫了一個(gè)配置類來定制我們的安全策略,就像這樣:
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
/**
* 注冊了一個(gè)Keycloak的AuthenticationProvider
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
/**
* 定義會(huì)話策略
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* 常見的Spring Security安全策略
*/
@Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http
.authorizeRequests()
.antMatchers("/customers*").hasRole("USER")
.antMatchers("/admin/**").hasRole("base_user")
.anyRequest().permitAll();
}
}
?
注意:上面的配置并不能成功。
java.io.FileNotFoundException: Unable to locate Keycloak configuration file: keycloak.json
拋出找不到 keycloak.json文件的異常。Keycloak支持的每個(gè)Java適配器都可以通過一個(gè)簡單的JSON文件進(jìn)行配置,我們?nèi)笔У木褪沁@個(gè)文件。
{
"realm" : "demo",
"resource" : "customer-portal",
"realm-public-key" : "MIGfMA0GCSqGSIb3D...31LwIDAQAB",
"auth-server-url" : "https://localhost:8443/auth",
"ssl-required" : "external",
"use-resource-role-mappings" : false,
"enable-cors" : true,
"cors-max-age" : 1000,
"cors-allowed-methods" : "POST, PUT, DELETE, GET",
"cors-exposed-headers" : "WWW-Authenticate, My-custom-exposed-Header",
"bearer-only" : false,
"enable-basic-auth" : false,
"expose-token" : true,
"verify-token-audience" : true,
"credentials" : {
"secret" : "234234-234234-234234"
},
"connection-pool-size" : 20,
"socket-timeout-millis": 5000,
"connection-timeout-millis": 6000,
"connection-ttl-millis": 500,
"disable-trust-manager": false,
"allow-any-hostname" : false,
"truststore" : "path/to/truststore.jks",
"truststore-password" : "geheim",
"client-keystore" : "path/to/client-keystore.jks",
"client-keystore-password" : "geheim",
"client-key-password" : "geheim",
"token-minimum-time-to-live" : 10,
"min-time-between-jwks-requests" : 10,
"public-key-cache-ttl": 86400,
"redirect-rewrite-rules" : {
"^/wsmaster/api/(.*)$" : "/api/$1"
}
}
上面包含的客戶端配置屬性都可以在Keycloak控制臺(tái)進(jìn)行配置,見下圖:
配置Keycloak客戶端屬性
也就是說我們需要的json文件和圖中的配置項(xiàng)是對(duì)應(yīng)的。比較人性化的是我們不需要自行編寫這個(gè)json文件,Keycloak提供了下載客戶端配置的方法,這里我只使用了必要的配置項(xiàng):
你可以下載客戶端json配置
引入客戶端配置
雖然順利拿到json文件,但是加載這個(gè)json配置卻不太順利,經(jīng)過我的摸索需要實(shí)現(xiàn)一個(gè)KeycloakConfigResolver并注入Spring IoC,有下面兩種實(shí)現(xiàn)方式。
復(fù)用Spring Boot Adapter配置
直接復(fù)用Spring Boot的配置形式,先聲明Spring Boot的KeycloakConfigResolver實(shí)現(xiàn):
/**
* 復(fù)用spring boot 的方法
*
* @return the keycloak config resolver
*/
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
然后復(fù)用Spring Boot的application.yaml的配置項(xiàng):
復(fù)用Spring Boot配置項(xiàng)
?
原來的角色資源映射約束失效。
自定義實(shí)現(xiàn)
你也可以自定義寫解析,這個(gè)時(shí)候json形式已經(jīng)不重要了,你可以將json文件的內(nèi)容存儲(chǔ)到任何你擅長的地方。
/**
* 自己寫解析
*
* @return the keycloak config resolver
*/
@Bean
public KeycloakConfigResolver fileKeycloakConfigResolver() {
return new KeycloakConfigResolver() {
@SneakyThrows
@Override
public KeycloakDeployment resolve(HttpFacade.Request request) {
// json 文件放到resources 文件夾下
ClassPathResource classPathResource = new ClassPathResource("./keycloak.json");
AdapterConfig adapterConfig = new ObjectMapper().readValue(classPathResource.getFile(), AdapterConfig.class);
return KeycloakDeploymentBuilder.build(adapterConfig);
}
};
}
角色命名策略
Spring Security會(huì)為每個(gè)角色添加ROLE_前綴,這需要我們聲明GrantedAuthoritiesMapper的實(shí)現(xiàn)SimpleAuthorityMapper來完成這一功能。Keycloak在KeycloakAuthenticationProvider中配置該功能:
KeycloakAuthenticationProvider authenticationProvider = keycloakAuthenticationProvider();
authenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
完整的配置
applicaiton.yaml:
keycloak:
# 聲明客戶端所在的realm
realm: felord.cn
# keycloak授權(quán)服務(wù)器的地址
auth-server-url: http://localhost:8011/auth
# 客戶端名稱
resource: springboot-client
# 聲明這是一個(gè)公開的客戶端,否則不能在keycloak外部環(huán)境使用,會(huì)403
public-client: true
?
這里要結(jié)合Keycloak導(dǎo)出的json文件配置。
Spring Security配置:
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* 復(fù)用spring boot 的方法
*
* @return the keycloak config resolver
*/
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
/**
* 自己寫解析
*
* @return the keycloak config resolver
*/
// @Bean
public KeycloakConfigResolver fileKeycloakConfigResolver() {
return request -> {
// json 文件放到resources 文件夾下
ClassPathResource classPathResource = new ClassPathResource("./keycloak.json");
AdapterConfig adapterConfig = null;
try {
adapterConfig = new ObjectMapper().readValue(classPathResource.getFile(),
AdapterConfig.class);
} catch (IOException e) {
e.printStackTrace();
}
return KeycloakDeploymentBuilder.build(adapterConfig);
};
}
/**
* 配置{@link AuthenticationManager}
* 這里會(huì)引入Keycloak的{@link AuthenticationProvider}實(shí)現(xiàn)
*
* @param auth the auth
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider authenticationProvider = keycloakAuthenticationProvider();
authenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(authenticationProvider);
}
/**
* 會(huì)話身份驗(yàn)證策略
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* 配置 session 監(jiān)聽器 保證單點(diǎn)退出生效
*
* @return the servlet listener registration bean
*/
@Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/customers*").hasRole("USER")
.antMatchers("/admin/**").hasRole("base_user")
.anyRequest().permitAll();
}
}
調(diào)用流程
資源客戶端springboot-client有一個(gè)接口/admin/foo,當(dāng)未登錄調(diào)用該接口時(shí)會(huì)轉(zhuǎn)發(fā)到:
http://localhost:8011/auth/realms/felord.cn/protocol/openid-connect/auth?response_type=code&client_id=springboot-client&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fsso%2Flogin&state=ec00d608-5ce7-47a0-acc8-8a20a2bfadfd&login=true&scope=openid
輸入正確的用戶密碼后才能得到期望的結(jié)果。
?
典型的
authorazation code flow。
總結(jié)
Keycloak整合Spring Security的要點(diǎn)這里需要再梳理一下。在原生情況下,客戶端的配置、用戶的信息、角色信息都由Keycloak負(fù)責(zé);客戶端只負(fù)責(zé)角色和資源的映射關(guān)系。后續(xù)會(huì)深入并定制Keycloak和Spring Security以滿足實(shí)際場景需要。
往期推薦
