本节将介绍如何基于SpringSe使用Keycloak来保护rest api,网上相关的文章很少,几乎没有直接可参考的,这里附上示例代码。其中使用的Keycloak版本为8.0.1。
配置Keycloak
根据系列文章的前两篇已经对Keycloak有了大致的认识,基础配置就不再赘述,这里需要创建一个realm:springboot-integration,并在此realm中创建两个client,一个是spring-security,用于获取token;一个是springboot-rest-api,也就是我们要保护的api。
分别看一下它们的配置:
为什么是两个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字段,这个字段在此获取:
此时便可以通过Postman来获取token了:
Spring Boot Application
接下来就需要准备一个简单的springboot项目,源码可参考此连接,在pom文件中添加对Keycloak的依赖:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.2.RELEASE
com.example
keycloak-integration
0.0.1-SNAPSHOT
keycloak-integration
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-web
org.keycloak
keycloak-spring-boot-starter
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.security
spring-security-test
test
org.keycloak.bom
keycloak-adapter-bom
8.0.1
pom
import
org.springframework.boot
spring-boot-maven-plugin
和上篇中的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中获得的:
剩下的配置就是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来自于之前的请求: