Home > Mobile >  spring boot rest app secured by keycloak 18 return always error 401 Unauthorized
spring boot rest app secured by keycloak 18 return always error 401 Unauthorized

Time:08-08

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

Text

What is wrong?

CodePudding user response:

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.

CodePudding user response:

I have found the cause of problem: reading "WWW-Authenticate" header of Postman there is present the following message "Bearer realm="realmname", error="invalid_token", error_description="Invalid token issuer. Expected 'http://localhost:8280/auth/realms/realmname', but was 'http://127.0.0.1:8280/auth/realms/realmname'" " .... so write http://localhost:8280 is different that write http://127.0.0.1:8280. Finally I have a last question: what access type between Public, Bearer only or confidential should I use?

  • Related