本节将介绍如何基于SpringSe使用Keycloak来保护rest api,网上相关的文章很少,几乎没有直接可参考的,这里附上示例代码。其中使用的Keycloak版本为8.0.1。
配置Keycloak
根据系列文章的前两篇已经对Keycloak有了大致的认识,基础配置就不再赘述,这里需要创建一个realm:springboot-integration,并在此realm中创建两个client,一个是spring-security,用于获取token;一个是springboot-rest-api,也就是我们要保护的api。
分别看一下它们的配置:
spring-security.png rest-api.png
为什么是两个client呢?若要保护的是rest api,需要在访问时带上token,这种行为需要Access Type设置为bearer-only,而设置为bearer-only的client是无法通过请求获取token的。
添加用户
在配置好client之后,在这个realm中添加两个角色(user, admin),并为这两个角色分别添加一个用户,添加的方式可以参考第二篇文章。
Tips: 一个角色可以包含另一个角色的权限,在添加角色时开启Composite Roles,再在下方选择其他角色即可。
获取token
获取token使用的是spring-security这个client,我们在Access Type中选择了credential,此时发送请求获取token时需要带一个client_secret字段,这个字段在此获取:
spring-security-credentials.png
此时便可以通过Postman来获取token了:
request token.png
Spring Boot Application
接下来就需要准备一个简单的springboot项目,源码可参考此连接,在pom文件中添加对Keycloak的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>keycloak-integration</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>keycloak-integration</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>8.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
和上篇中的pom相比,添加了keycloak-spring-boot-starter和keycloak-adapter-bom。在此添加了两个endpoint用于演示:
@RestController
public class HelloController {
@GetMapping(value = "/admin")
public String admin() {
return "Admin";
}
@GetMapping("/user")
public String user() {
return "User";
}
}
添加了Keycloak的依赖后,剩下的工作就是配置了,默认情况下,Keycloak会从keycloak.json文件中获取配置信息,但在springboot中通常是使用application.properties
来配置,所以需要声明一个resolver来覆盖原有的配置文件,添加后,启动后便会从application.properties
来获取Keycloak的配置:
@Configuration
public class KeycloakConfig {
@Bean
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
配置如下所示:
server.port=8099
keycloak.realm=springboot-integration
keycloak.bearer-only=true
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.ssl-required=external
keycloak.resource=springboot-rest-api
keycloak.use-resource-role-mappings=false
keycloak.principal-attribute=preferred_username
keycloak.credentials.secret=8e4c5bac-8dcb-4f68-a529-c998e19f0c4d
keycloak.confidential-port=0
其中的credentials.secret是从springboot-rest-api这个client中获得的:
rest-api-credentials.png
剩下的配置就是SpringSecurity的配置了:
@KeycloakConfiguration
public class KeycloakSecurityConfigurer extends KeycloakWebSecurityConfigurerAdapter {
@Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
KeycloakAuthenticatedActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
KeycloakSecurityContextRequestFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
@Override
@ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setConvertToUpperCase(true);
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors().and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user").hasRole("USER")
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().permitAll();
}
}
此处的父类KeycloakWebSecurityConfigurerAdapter,它继承了WebSecurityConfigurerAdapter,也就是我们之前配置SpringSecurity的配置类,而@KeycloakConfiguration注解也是对之前注解的一个复合:
@Configuration
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@EnableWebSecurity
public @interface KeycloakConfiguration {
}
- NullAuthenticatedSessionStrategy:因为保护的是rest api,而不是基于session的所以此时需要override sessionAuthenticationStrategy;
- configureGlobal:这段配置来自于Keycloak的官方文档,并添加了SimpleAuthorityMapper,它的作用是一个匹配Keycloak和SpringSecurity中声明的角色大小写不同
测试
配置完成后,便可以开始测试了,使用postman发送请求,其中token来自于之前的请求:
test.png
网友评论