Spring Security整合KeyCloak保護(hù)Rest API實現(xiàn)詳解
正文
今天我們嘗試Spring Security整合Keycloak,并決定建立一個非常簡單的Spring Boot微服務(wù),使用Keycloak作為我的身份驗證源,使用Spring Security處理身份驗證和授權(quán)。
設(shè)置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
- 在此之后,我們只需登錄到容器并導(dǎo)航到
bin文件夾。
docker exec -it springboot-security-keycloak-integration /bin/bash cd keycloak/bin
- 首先,我們需要從CLI客戶端登錄keycloak服務(wù)器,之后我們不再需要身份驗證:
./kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin --password admin
配置realm
- 首先,我們需要創(chuàng)建一個realm:
./kcadm.sh create realms -s realm=springboot-security-keycloak-integration -s enabled=true Created new realm with id 'springboot-security-keycloak-integration'
- 之后,我們需要創(chuàng)建2個客戶端,這將為我們的應(yīng)用程序提供身份驗證。首先我們創(chuàng)建一個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客戶端可以在不提供任何秘密的情況下啟動登錄。第二個使我們能夠使用用戶名和密碼直接登錄。
- 其次,我們創(chuàng)建了一個由REST服務(wù)使用的客戶端:
./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客戶端永遠(yuǎn)不會啟動登錄過程,但是當(dāng)它收到Bearer令牌時,它將檢查所述令牌的有效性。
我們應(yīng)該注意保留這些ID,因為我們將在接下來的步驟中使用它們。
- 我們有兩個客戶端,接下來是為spring-security-keycloak-example-app客戶創(chuàng)建角色
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是我們創(chuàng)建客戶端輸出的id
- 最后,我們應(yīng)該獲取客戶端的配置,以便稍后提供給我們的應(yīng)用程序:
./kcadm.sh get clients/ab9d404e-6d5b-40ac-9bc3-9e2e26b68213/installation/providers/keycloak-oidc-keycloak-json -r springboot-security-keycloak-integration
注意client后的id是我們創(chuàng)建客戶端輸出的id
應(yīng)該返回類似于此的內(nèi)容:
{
"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
}
配置用戶
出于演示目的,我們創(chuàng)建2個具有2個不同角色的用戶,以便我們驗證授權(quán)是否有效。
- 首先,讓我們創(chuàng)建一個具有admin角色的用戶:
創(chuàng)建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'
設(shè)置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
注意:從不在生產(chǎn)中使用此方法,它僅用于演示目的!
- 然后我們創(chuàng)建另一個用戶,這次有角色user:
創(chuàng)建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'
設(shè)置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服務(wù)
我們已經(jīng)配置了Keycloak并準(zhǔn)備使用,我們只需要一個應(yīng)用程序來使用它!所以我們創(chuàng)建一個簡單的Spring Boot應(yīng)用程序。我會在這里使用maven構(gòu)建項目:
<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 用于保護(hù)應(yīng)用程序
- keycloak-spring-boot-starter 使用Keycloak和Spring Boot
- keycloak-spring-security-adapter 與Spring Security集成
一個簡單的應(yīng)用類:
/**
* 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:shichengoooo@163.com" 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:shichengoooo@163.com" 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:shichengoooo@163.com" 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類擴(kuò)展 KeycloakWebSecurityConfigurerAdapter,這是Keycloak提供的類,它提供與Spring Security的集成。
然后我們通過添加SimpleAuthorityMapper配置身份驗證管理器,它負(fù)責(zé)轉(zhuǎn)換來自Keycloak的角色名稱以匹配Spring Security的約定?;旧蟂pring Security期望以ROLE_前綴開頭的角色,ROLE_ADMIN可以像Keycloak一樣命名我們的角色,或者我們可以將它們命名為admin,然后使用此映射器將其轉(zhuǎn)換為大寫并添加必要的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設(shè)置會話策略,但是當(dāng)我們創(chuàng)建無狀態(tài)REST服務(wù)時,我們并不真的想要有會話,因此我們使用NullAuthenticatedSessionStrategy:
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
通常,Keycloak Spring Security集成從keycloak.json文件中解析keycloak配置,但是我們希望有適當(dāng)?shù)腟pring Boot配置,因此我們使用Spring Boot覆蓋配置解析器:
@Bean
KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
然后我們配置Spring Security來授權(quán)所有請求:
@Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.anyRequest().permitAll();
}
最后,根據(jù)文檔,我們阻止雙重注冊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使用之前下載的值配置我們的應(yīng)用程序 :
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
使用應(yīng)用程序
- 使用curl我們創(chuàng)建的客戶端進(jìn)行身份驗證,以獲取訪問令牌:
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變量中。
現(xiàn)在我們可以檢查我們的管理員是否可以訪問自己的/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保護(hù)Rest API實現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring Security整合KeyCloak的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot3結(jié)合Vue3實現(xiàn)用戶登錄功能
最近項目需求搭建一個結(jié)合Vue.js前端框架和Spring Boot后端框架的登錄系統(tǒng),本文主要介紹了SpringBoot3結(jié)合Vue3實現(xiàn)用戶登錄功能,具有一定的參考價值,感興趣的可以了解一下2024-03-03
MyBatis-Puls插入或修改時某些字段自動填充操作示例
這篇文章主要為大家介紹了MyBatis-Puls插入或修改時某些字段自動填充操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Java ExecutorServic線程池異步實現(xiàn)流程
這篇文章主要介紹了Java ExecutorServic線程池異步實現(xiàn)流程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-12-12
spring無法引入注解及import org.springframework.web.bind.annota
本文主要介紹了spring無法引入注解及import org.springframework.web.bind.annotation.*報錯的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
SpringBoot項目執(zhí)行腳本 自動拉取最新代碼并重啟的實例內(nèi)容
在本篇文章里小編給大家整理的是一篇關(guān)于SpringBoot項目執(zhí)行腳本 自動拉取最新代碼并重啟的實例內(nèi)容,有需要的朋友們參考下。2019-12-12
java代碼實現(xiàn)mysql分表操作(用戶行為記錄)
這篇文章主要介紹了java代碼實現(xiàn)mysql分表操作(用戶行為記錄),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02

