Spring Security整合KeyCloak保護Rest API實現詳解
正文
今天我們嘗試Spring Security整合Keycloak,並決定建立一個非常簡單的Spring Boot微服務,使用Keycloak作為我的身份驗證源,使用Spring Security處理身份驗證和授權。
設置Keycloak
- 首先我們需要一個Keycloak實例,讓我們啟動Jboss提供的Docker容器:
docker run -d \ --name springboot-security-keycloak-integration \ -e KEYCLOAK_USER=admin \ -e KEYCLOAK_PASSWORD=admin \ -p 9001:8080 \ jboss/keycloak
- 在此之後,我們隻需登錄到容器並導航到
bin
文件夾。
docker exec -it springboot-security-keycloak-integration /bin/bash cd keycloak/bin
- 首先,我們需要從CLI客戶端登錄keycloak服務器,之後我們不再需要身份驗證:
./kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin --password admin
配置realm
- 首先,我們需要創建一個realm:
./kcadm.sh create realms -s realm=springboot-security-keycloak-integration -s enabled=true Created new realm with id 'springboot-security-keycloak-integration'
- 之後,我們需要創建2個客戶端,這將為我們的應用程序提供身份驗證。首先我們創建一個cURL客戶端,這樣我們就可以通過命令行命令登錄:
./kcadm.sh create clients -r springboot-security-keycloak-integration -s clientId=curl -s enabled=true -s publicClient=true -s baseUrl=http://localhost:8080 -s adminUrl=http://localhost:8080 -s directAccessGrantsEnabled=true Created new client with id '8f0481cd-3bbb-4659-850f-6088466a4d89'
重要的是要註意2個選項:publicClient=true
和 directAccessGrantsEnabled=true
。第一個使這個客戶端公開,這意味著我們的cURL客戶端可以在不提供任何秘密的情況下啟動登錄。第二個使我們能夠使用用戶名和密碼直接登錄。
- 其次,我們創建瞭一個由REST服務使用的客戶端:
./kcadm.sh create clients -r springboot-security-keycloak-integration -s clientId=springboot-security-keycloak-integration-client -s enabled=true -s baseUrl=http://localhost:8080 -s bearerOnly=true Created new client with id 'ab9d404e-6d5b-40ac-9bc3-9e2e26b68213'
這裡的重要配置是bearerOnly=true
。這告訴Keycloak客戶端永遠不會啟動登錄過程,但是當它收到Bearer令牌時,它將檢查所述令牌的有效性。
我們應該註意保留這些ID,因為我們將在接下來的步驟中使用它們。
- 我們有兩個客戶端,接下來是為spring-security-keycloak-example-app客戶創建角色
Admin Role:
./kcadm.sh create clients/ab9d404e-6d5b-40ac-9bc3-9e2e26b68213/roles -r springboot-security-keycloak-integration -s name=admin -s 'description=Admin role' Created new role with id 'admin'
User Role:
./kcadm.sh create clients/ab9d404e-6d5b-40ac-9bc3-9e2e26b68213/roles -r springboot-security-keycloak-integration -s name=user -s 'description=User role' Created new role with id 'user'
註意client後的id是我們創建客戶端輸出的id
- 最後,我們應該獲取客戶端的配置,以便稍後提供給我們的應用程序:
./kcadm.sh get clients/ab9d404e-6d5b-40ac-9bc3-9e2e26b68213/installation/providers/keycloak-oidc-keycloak-json -r springboot-security-keycloak-integration
註意client後的id是我們創建客戶端輸出的id
應該返回類似於此的內容:
{ "realm" : "springboot-security-keycloak-integration", "bearer-only" : true, "auth-server-url" : "http://localhost:8080/auth", "ssl-required" : "external", "resource" : "springboot-security-keycloak-integration-client", "verify-token-audience" : true, "use-resource-role-mappings" : true, "confidential-port" : 0 }
配置用戶
出於演示目的,我們創建2個具有2個不同角色的用戶,以便我們驗證授權是否有效。
- 首先,讓我們創建一個具有admin角色的用戶:
創建admin用戶:
./kcadm.sh create users -r springboot-security-keycloak-integration -s username=admin -s enabled=true Created new user with id '50c11a76-a8ff-42b1-80cb-d82cb3e7616d'
設置admin密碼:
./kcadm.sh update users/50c11a76-a8ff-42b1-80cb-d82cb3e7616d/reset-password -r springboot-security-keycloak-integration -s type=password -s value=admin -s temporary=false -n
value: 用戶密碼
追加到admin角色中
./kcadm.sh add-roles -r springboot-security-keycloak-integration --uusername=admin --cclientid springboot-security-keycloak-integration-client --rolename admin
註意:從不在生產中使用此方法,它僅用於演示目的!
- 然後我們創建另一個用戶,這次有角色user:
創建user用戶:
./kcadm.sh create users -r springboot-security-keycloak-integration -s username=user -s enabled=true Created new user with id '624434c8-bce4-4b5b-b81f-e77304785803'
設置user密碼:
./kcadm.sh update users/624434c8-bce4-4b5b-b81f-e77304785803/reset-password -r springboot-security-keycloak-integration -s type=password -s value=admin -s temporary=false -n
追加到user角色中:
./kcadm.sh add-roles -r springboot-security-keycloak-integration --uusername=user --cclientid springboot-security-keycloak-integration-client --rolename user
Rest服務
我們已經配置瞭Keycloak並準備使用,我們隻需要一個應用程序來使用它!所以我們創建一個簡單的Spring Boot應用程序。我會在這裡使用maven構建項目:
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.edurt.sski</groupId> <artifactId>springboot-security-keycloak-integration</artifactId> <packaging>jar</packaging> <version>1.0.0</version> <name>springboot security keycloak integration</name> <description>SpringBoot Security KeyCloak Integration is a open source springboot, spring security, keycloak integration example. </description> <properties> <!-- dependency config --> <dependency.lombox.version>1.16.16</dependency.lombox.version> <dependency.springboot.common.version>1.5.6.RELEASE</dependency.springboot.common.version> <dependency.keycloak.version>3.1.0.Final</dependency.keycloak.version> <!-- plugin config --> <plugin.maven.compiler.version>3.3</plugin.maven.compiler.version> <plugin.maven.javadoc.version>2.10.4</plugin.maven.javadoc.version> <!-- environment config --> <environment.compile.java.version>1.8</environment.compile.java.version> <!-- reporting config --> <reporting.maven.jxr.version>2.5</reporting.maven.jxr.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${dependency.springboot.common.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${dependency.lombox.version}</version> </dependency> <!-- springboot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- keycloak --> <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-spring-boot-starter</artifactId> <version>${dependency.keycloak.version}</version> </dependency> <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-spring-security-adapter</artifactId> <version>${dependency.keycloak.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${plugin.maven.compiler.version}</version> <configuration> <source>${environment.compile.java.version}</source> <target>${environment.compile.java.version}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>${plugin.maven.javadoc.version}</version> <configuration> <aggregate>true</aggregate> <!-- custom tags --> <tags> <tag> <name>Description</name> <placement>test</placement> <head>description</head> </tag> </tags> <!-- close jdoclint check document --> <additionalparam>-Xdoclint:none</additionalparam> </configuration> </plugin> </plugins> </build> <reporting> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jxr-plugin</artifactId> <version>${reporting.maven.jxr.version}</version> </plugin> </plugins> </reporting> </project>
添加所有必需的依賴項:
- spring-security 用於保護應用程序
- keycloak-spring-boot-starter 使用Keycloak和Spring Boot
- keycloak-spring-security-adapter 與Spring Security集成
一個簡單的應用類:
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.edurt.sski; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * <p> SpringBootSecurityKeyCloakIntegration </p> * <p> Description : SpringBootSecurityKeyCloakIntegration </p> * <p> Author : qianmoQ </p> * <p> Version : 1.0 </p> * <p> Create Time : 2019-02-18 14:45 </p> * <p> Author Email: <a href="mailTo:[email protected]" rel="external nofollow" rel="external nofollow" rel="external nofollow" >qianmoQ</a> </p> */ @SpringBootApplication public class SpringBootSecurityKeyCloakIntegration { public static void main(String[] args) { SpringApplication.run(SpringBootSecurityKeyCloakIntegration.class, args); } }
Rest API接口:
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.edurt.sski.controller; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * <p> HelloController </p> * <p> Description : HelloController </p> * <p> Author : qianmoQ </p> * <p> Version : 1.0 </p> * <p> Create Time : 2019-02-18 14:50 </p> * <p> Author Email: <a href="mailTo:[email protected]" rel="external nofollow" rel="external nofollow" rel="external nofollow" >qianmoQ</a> </p> */ @RestController public class HelloController { @GetMapping(value = "/admin") @Secured("ROLE_ADMIN") public String admin() { return "Admin"; } @GetMapping("/user") @Secured("ROLE_USER") public String user() { return "User"; } }
最後是keycloak配置:
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.edurt.sski.config; import org.keycloak.adapters.KeycloakConfigResolver; import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter; import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; /** * <p> KeycloakSecurityConfigurer </p> * <p> Description : KeycloakSecurityConfigurer </p> * <p> Author : qianmoQ </p> * <p> Version : 1.0 </p> * <p> Create Time : 2019-02-18 14:51 </p> * <p> Author Email: <a href="mailTo:[email protected]" rel="external nofollow" rel="external nofollow" rel="external nofollow" >qianmoQ</a> </p> */ @Configuration @EnableWebSecurity public class KeycloakSecurityConfigurer extends KeycloakWebSecurityConfigurerAdapter { @Bean public GrantedAuthoritiesMapper grantedAuthoritiesMapper() { SimpleAuthorityMapper mapper = new SimpleAuthorityMapper(); mapper.setConvertToUpperCase(true); return mapper; } @Override protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() { final KeycloakAuthenticationProvider provider = super.keycloakAuthenticationProvider(); provider.setGrantedAuthoritiesMapper(grantedAuthoritiesMapper()); return provider; } @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(keycloakAuthenticationProvider()); } @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new NullAuthenticatedSessionStrategy(); } @Override protected void configure(final HttpSecurity http) throws Exception { super.configure(http); http .authorizeRequests() .antMatchers("/admin").hasRole("ADMIN") .antMatchers("/user").hasRole("USER") .anyRequest().permitAll(); } @Bean KeycloakConfigResolver keycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); } @Bean public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean( final KeycloakAuthenticationProcessingFilter filter) { final FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter); registrationBean.setEnabled(false); return registrationBean; } @Bean public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean( final KeycloakPreAuthActionsFilter filter) { final FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter); registrationBean.setEnabled(false); return registrationBean; } }
KeycloakSecurityConfigurer類擴展 KeycloakWebSecurityConfigurerAdapter,這是Keycloak提供的類,它提供與Spring Security的集成。
然後我們通過添加SimpleAuthorityMapper配置身份驗證管理器,它負責轉換來自Keycloak的角色名稱以匹配Spring Security的約定。基本上Spring Security期望以ROLE_
前綴開頭的角色,ROLE_ADMIN可以像Keycloak一樣命名我們的角色,或者我們可以將它們命名為admin,然後使用此映射器將其轉換為大寫並添加必要的ROLE_
前綴:
@Bean public GrantedAuthoritiesMapper grantedAuthoritiesMapper() { SimpleAuthorityMapper mapper = new SimpleAuthorityMapper(); mapper.setConvertToUpperCase(true); return mapper; } @Override protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() { final KeycloakAuthenticationProvider provider = super.keycloakAuthenticationProvider(); provider.setGrantedAuthoritiesMapper(grantedAuthoritiesMapper()); return provider; } @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(keycloakAuthenticationProvider()); }
我們還需要為Keycloak設置會話策略,但是當我們創建無狀態REST服務時,我們並不真的想要有會話,因此我們使用NullAuthenticatedSessionStrategy:
@Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new NullAuthenticatedSessionStrategy(); }
通常,Keycloak Spring Security集成從keycloak.json文件中解析keycloak配置,但是我們希望有適當的Spring Boot配置,因此我們使用Spring Boot覆蓋配置解析器:
@Bean KeycloakConfigResolver keycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); }
然後我們配置Spring Security來授權所有請求:
@Override protected void configure(final HttpSecurity http) throws Exception { super.configure(http); http .authorizeRequests() .anyRequest().permitAll(); }
最後,根據文檔,我們阻止雙重註冊Keycloak的過濾器:
@Bean public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean( final KeycloakAuthenticationProcessingFilter filter) { final FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter); registrationBean.setEnabled(false); return registrationBean; } @Bean public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean( final KeycloakPreAuthActionsFilter filter) { final FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter); registrationBean.setEnabled(false); return registrationBean; }
最後,我們需要application.properties使用之前下載的值配置我們的應用程序 :
server.port=9002 keycloak.realm=springboot-security-keycloak-integration keycloak.bearer-only=true keycloak.auth-server-url=http://localhost:9001/auth keycloak.ssl-required=external keycloak.resource=springboot-security-keycloak-integration-client keycloak.use-resource-role-mappings=true keycloak.principal-attribute=preferred_username
使用應用程序
- 使用curl我們創建的客戶端進行身份驗證,以獲取訪問令牌:
export TOKEN=`curl -ss --data "grant_type=password&client_id=curl&username=admin&password=admin" http://localhost:9001/auth/realms/springboot-security-keycloak-integration/protocol/openid-connect/token | jq -r .access_token`
這將收到的訪問令牌存儲在TOKEN變量中。
現在我們可以檢查我們的管理員是否可以訪問自己的/admin接口
curl -H "Authorization: bearer $TOKEN" http://localhost:9002/admin Admin
但它無法訪問/user接口:
$ curl -H "Authorization: bearer $TOKEN" http://localhost:9002/user {"timestamp":1498728302626,"status":403,"error":"Forbidden","message":"Access is denied","path":"/user"}
對於user用戶也是如此,user用戶無法訪問admin接口。
源碼地址:GitHub
以上就是Spring Security整合KeyCloak保護Rest API實現詳解的詳細內容,更多關於Spring Security整合KeyCloak的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Spring Boot Admin的使用詳解(Actuator監控接口)
- 五分鐘解鎖springboot admin監控新技巧
- Spring Cloud服務安全連接方式
- Spring Boot Admin 快速入門詳解
- Java SpringBoot快速集成SpringBootAdmin管控臺監控服務詳解