Issue
I'm developing microservices some of them with spring boot web and other with spring boot data rest and I want secure them with keycloak server 18 through OpenId protocol. The microservices endpoint can be accessed from frontend adding in the "Authorization header" the bearer token obtained from post request to the url http://localhost:8280/auth/realms/--realm_name--/.well-known/openid-configuration inserting in the body request the key client_id, username, password, grant_type, client_secret.
I have create 1.a realm, 2.a client named "springboot-mc-dev" (with Access Type = confidential;, "Root URL" and "Valid Redirect URIs" both setted to "http://localhost:8490", "Standard Flow Enabled", "Direct Access Grants Enabled", "Service Accounts Enabled" and "Authorization Enabled" setted to "ON"), 3.before a role inside client ("named springboot-mc-dev-role" composite False) and after a role inside realm (named always "springboot-mc-dev-role" composite true that is associated Client Roles "springboot-mc-dev-role" of client springboot-mc-dev), 4.user mapped to role "springboot-mc-dev-role" in "realm roles"
After I have imported the following dependency in parent pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java-version>1.8</java-version>
<keycloak.version>18.0.2</keycloak.version>
</properties>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>${keycloak.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Here there is the code of SecurityConfig.java class
package it.organization.project.microservice.datamart.config.security;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.SimpleAuthorityMapper;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
@KeycloakConfiguration
@Configuration
@EnableWebSecurity
@Import(KeycloakSpringBootConfigResolver.class)
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@ConditionalOnProperty(name = "ms-security-enable", havingValue = "true")
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setPrefix("ROLE_");
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
//keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
@Override
/*protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}*/
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/**").hasRole("springboot-mc-dev-role")
//.anyRequest().hasRole("springboot-mc-dev-role")
//.anyRequest().//authenticated()
//permitAll()
;
http.csrf().disable();
}
@Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
//public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
this is the code of main class
package it.organization.project.microservice.datamart;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class MAINDataMartApplication {
public static void main(String[] args) {
Security.addProvider(new BouncyCastleProvider());
SpringApplication.run(MAINDataMartApplication.class, args);
}
}
and last there is the application.yml with keycloak settings
#KEYCLOAK CONFIGURATION
keycloak:
auth-server-url: http://localhost:8280/auth
#ssl-required: external
realm: realmname
bearer-only: true
#public-client: true
use-resource-role-mappings: true
resource: springboot-mc-dev
credentials:
secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
this is the result
What is wrong?
Solution
Keycloak spring adapter is deprecated. Don't use it.
Have look at those tutorials for alternatives.
We are missing important info to answer preecisely:
- Spring versions?
- reason for 401 (from Postman console, value of
WWW-Authenticate
response header)?
There can be few reasons for 401 Unauthorized:
- missing authorization header
- expired or not yet valid token (for instance because of timezone misconfiguration on either authorization or resource server)
- different issuer in access-token claim and Spring config: host, port, etc must be exactly the same
- Spring configured for a different type of authentication than what authorization server supports (i.e. opaque token with introspaction and one side and JWT on the other)
- ...
If you're using Keycloak 18 with Quarkus, it is likely that your conf should reference http://localhost:8280
as issuer (and not http://localhost:8280/auth
).
So, look at Postman console, open your access-token with a tool like https://jwt.io and compare values you find there with what you have in spring conf and logs.
Answered By - ch4mp
Answer Checked By - Mary Flores (JavaFixing Volunteer)