Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP 401 Unauthorized response when getting a token #8

Closed
belgoros opened this issue Sep 21, 2020 · 4 comments
Closed

HTTP 401 Unauthorized response when getting a token #8

belgoros opened this issue Sep 21, 2020 · 4 comments

Comments

@belgoros
Copy link

belgoros commented Sep 21, 2020

I'm trying to run a test using the exported from Keycloack server JSON configuration file as follows:

package com.example.controllers;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
class TestControllerTest extends BaseKeycloakTest {

    @Autowired
    protected MockMvc mockMvc;

    @Test
    public void shouldRespondOKForAdminIfTokenPresent() throws Exception {
        this.mockMvc.perform(get("/test/admin")
                .header("Authorization", "Bearer "
                        + getAccessToken(
                                "employee2",
                                "mypassword",
                                  "springboot-microservice",
                                   "demo")))
                .andExpect(status().isOk());
    }
}

The BaseKeycloakTest class is defined as follows:

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BaseKeycloakTest {
    @Container
    protected static final KeycloakContainer keycloak = new KeycloakContainer()
            .withRealmImportFile("realm-export.json");

    @BeforeAll
    public static void setUp() {
        keycloak.start();
    }
    @AfterAll
    public static void tearDown() {
        keycloak.stop();
    }
...

I'm getting the token in BaseKeycloakTest as follows:

protected static String getAccessToken(String username, String password, String clientId, String realm) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("grant_type", "password");
        map.add("client_id", clientId);
        map.add("username", username);
        map.add("password", password);
        map.add("client_secret", "85a58c55-dd32-4205-a568-f82ae710edd1");

        KeyCloakToken token = restTemplate.postForObject(
                tokenUrlFor(realm),
                new HttpEntity<>(map, headers), KeyCloakToken.class);

        assert token != null;
        return token.getAccessToken();
    }

    private static String tokenUrlFor(String realm) {
        String url = keycloak.getAuthServerUrl()
                + "/realms/"
                + realm
                + "/protocol/openid-connect/token";
        return url;
    }

    private static class KeyCloakToken {

        private String accessToken;

        @JsonCreator
        KeyCloakToken(@JsonProperty("access_token") final String accessToken) {
            this.accessToken = accessToken;
        }

        public String getAccessToken() {
            return accessToken;
        }
    }

When running the test, it seems like realm-export.json file is not taken in consideration, - the generated token URL looks like that:

http://localhost:32791/auth/realms/demo/protocol/openid-connect/token

instead of:

http://localhost:8080/auth/realms/demo/protocol/openid-connect/token

what raises the error:

org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 Unauthorized: [no body]

	at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:105)
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:184)

...

The KeucloakConfig class looks like that:

package com.example.config;

import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class KeycloakConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
                .antMatchers("/test/anonymous").permitAll()
                .antMatchers("/test/user").hasAnyRole("user")
                .antMatchers("/test/admin").hasAnyRole("admin")
                .antMatchers("/test/all-user").hasAnyRole("user","admin")
                .anyRequest()
                .permitAll();
        http.csrf().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }
}

And application.yml for tests looks like that:

spring:
  application:
    name: spring-demo

server:
  port: 8000

keycloak:
  realm: demo
  auth-server-url: http://localhost:8080/auth
  ssl-required: external
  resource: springboot-microservice
  credentials:
    secret: 85a58c55-dd32-4205-a568-f82ae710edd1
  use-resource-role-mappings: true
  bearer-only: true

What is wrong with that?

@belgoros belgoros changed the title ProcessingException: javax.ws.rs.NotAuthorizedException: HTTP 401 Unauthorized HTTP 401 Unauthorized when getting a token Sep 21, 2020
@belgoros belgoros changed the title HTTP 401 Unauthorized when getting a token HTTP 401 Unauthorized response when getting a token Sep 21, 2020
@dasniko
Copy link
Owner

dasniko commented Sep 25, 2020

The Keycloak-Testcontainer won't configure itself base on some Spring config properties. Testcontainers have no relation to Spring. Generally, Testcontainers start on a random port on each run. That's the concept of Testcontainers.

You have to take care in your Spring test environment/configuration to set the appropriate url/port of Keycloak after Keycloak-Testcontainer has startet. Please consult the Spring docs on how to do that. I'm not a Spring guy, I don't know how this can be achieved, I only know that it is possible and also a well-known and wide-used practice.

@belgoros
Copy link
Author

I just wonder if:

  • there are some more concrete tests examples of use the testcontainers-keycloak
  • should I drop the declaration of auth-server-url: http://localhost:8080/auth property in the test/resources/application.yml file

@manedev79
Copy link

I'm facing the same issue with Spring. Would be great to have a pointer to the widely used practice.

@dasniko
Copy link
Owner

dasniko commented Oct 30, 2020

Please consult the Spring Boot docs for @ContextConfiguration and ApplicationContextInitializer<ConfigurableApplicationContext>. That‘s what you need.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants