Complete code: https://github.com/czetsuya/Spring-Keycloak-with-REST-API
I'm trying to implement a REST API in Spring secured by Keycloak (4.8.1) with a bearer-only client.
Problem: configure(HttpSecurity http) is not respected and as long as the user is authenticated the REST endpoints are accessible.
For example with .antMatchers("/admin*").hasRole("ADMIN"), /admin should only be accessible by a user with an ADMIN role, but I was able to access using a USER role.
I also tried setting the security-constraints in application.yml (but didn't help):
security-constraints:
- auth-roles:
- ADMIN
- security-collections:
- name: admin
- patterns:
- /admin*
Using @EnableGlobalMethodSecurity in combination with @PreAuthorize("hasRole('ADMIN')") can do the trick, but is there really no other way around?
Here's the application.xml.
keycloak:
enabled: true
realm: dev
auth-server-url: http://localhost:8083/auth
ssl-required: external
resource: dev-api
bearer-only: true
confidential-port: 0
use-resource-role-mappings: false
principal-attribute: preferred_username
Here are the dependencies on pom:
<dependencies>
<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>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
.....
And part of the SecurityConfig class:
@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
simpleAuthorityMapper.setConvertToUpperCase(true);
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(simpleAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Autowired
public KeycloakClientRequestFactory keycloakClientRequestFactory;
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate keycloakRestTemplate() {
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
/**
* Secure appropriate endpoints
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests() //
.antMatchers("/users*").hasRole("USER") //
.antMatchers("/admin*").hasRole("ADMIN") //
.anyRequest().authenticated() //
.and().csrf().disable() //
;
}
After enabling a verbose log. I found out that the security-constraints defined in application.yml are being applied, but not the constraints defined in the java class.
Question now is how to use the java constraints instead of the application.yml defined.
Problem was wrong yml configuration. Here's the correct version:
security-constraints:
- auth-roles:
- User
security-collections:
- name: unsecured
patterns:
- /users
- auth-roles:
- Admin
security-collections:
- name: secured
patterns:
- /admin
Note that when a KeycloakSpringBootConfigResolver is defined configure(httpSecurity) will also be invoked.
Enabling the log below in application.yml will show us the security constraints check:
logging:
level:
org.apache.catalina: DEBUG
Here's a more detailed explanation with code: http://czetsuya-tech.blogspot.com/2018/12/secure-spring-boot-rest-project-with.html