0
votes

I'm setting up a microservice architecture using spring boot. Hence I have a microservice "poc-be", an "api-gateway" and a running instance of Keycloak for managing authentication and authorization.
I searched a lot and I've followed various examples, but I'm still getting a 404 error when calling the service through the api-gateway.
Keycloak
keycloak is running on localhost:8080, I created a realm, a client, an user and a role: enter image description here

Using Postman, I can obtain an access token calling http://localhost:8080/auth/realms/my_realm/protocol/openid-connect/token Postman call for getting access token

I would use this jwt token for making another call to the api-gateway for accessing protected service.
API gateway
The api gateway project has the following relevant dependencies:

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>11.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <dependencies>
      <!-- Gateway functionality -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- keycloak Spring Boot Adapter -->
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

I setup this configuration in application-dev.yml

server:
  port: 8082
cluster:
  uri-local-be: http://localhost:8083
keycloak:
  enabled: true
  realm: my_realm
  # The value we specify in keycloak.resource matches the client we named in the admin console
  resource: niuma-services
  auth-server-url: http://localhost:8080/auth
  bearer-only: true
  principal-attribute: preferred_username
  public-client: true
spring:
  ...
  cloud:
    gateway:
      routes:
        - id: protected
          uri: ${cluster.uri-local-be}
          predicates:
          - Path=/api/hello/*

You can see the route for addressing the service (no service discovery at the time). I implemented a SecurityConfig class, as found on tutorials:

@Configuration
@EnableWebSecurity(debug = true)
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@Slf4j
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        log.info("SecurityConfig.configureGlobal");
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        log.info("SecurityConfig.KeycloakConfigResolver");
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        log.info("SecurityConfig.sessionAuthenticationStrategy");
        return new NullAuthenticatedSessionStrategy();
    }

    @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;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        log.info("SecurityConfig.configure");
        super.configure(http);
        http.authorizeRequests().antMatchers("/spi*").hasRole("user").anyRequest().permitAll();
    }

    @Bean
    public ServerCodecConfigurer serverCodecConfigurer() {
        return ServerCodecConfigurer.create();
    }

The microservice it's exposing an endpoint at localhost:8083/api/hello/secure returning a simple string. I achieved the desired behavior when calling directly localhost:8083/api/hello/secure/ : - unhauthorized when calling without access token - successfully respond when calling with access token

But I can't get the api gateway working with both the service and keycloak. If I call localhost:8082/api/hello/secure passing the access token as Authorization header, I get a 404 and I can't understand why: 404 api gateway

Reading the logs I see the authentication process occurs, but I can't explain the 404:

o.s.security.web.FilterChainProxy        : /error at position 1 of 15 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
o.s.security.web.FilterChainProxy        : /error at position 2 of 15 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@7102dd3c: Authentication: org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken@7102dd3c: Principal: ciro; Credentials: [PROTECTED]; Authenticated: true; Details: org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount@42a60ed1; Granted Authorities: ROLE_offline_access, ROLE_user, ROLE_uma_authorization'
...
o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/sso/login']
o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/sso/login'
...
f.KeycloakAuthenticationProcessingFilter : Attempting Keycloak authentication
o.k.a.BearerTokenRequestAuthenticator    : Found [1] values in authorization header, selecting the first value for Bearer.
...
o.k.a.BearerTokenRequestAuthenticator    : successful authorized
o.k.adapters.RequestAuthenticator        : User 'ciro' invoking 'http://localhost:8082/error' on client 'niuma-services'
...
o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
...
o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 404, headers={masked}
...

Hoping I reported all information needed, I would ask some help getting authentication working using access token obtained from keycloak, calling the api gateway.

1
Did you resolved this issue ? - Vivek Runwal

1 Answers

0
votes

Apparently, as shown on application start log, spring MVC is not compatible with Spring Cloud Gateway right now (version Hoxton.SR8), this is the message on log:

Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.

Your dependency keycloak-spring-boot-starter includes spring-boot-starter-web