Dan
Dan

Reputation: 295

Spring app with Keycloak returns 401 error

I'm trying to access a Spring App via Keycloak, but I always get a 401 Unauthorized error. Basically I have a chat module that works fine on its own, but once I add Keycloak I'm unable to access the app due to that 401 error. I've followed about 3 tutorials that showed similar things to what I've done, and I still have no idea what I've done wrong.

Here's my app's config :

keycloak: 
    enabled: true
    realm: myReal
    resource: myReal-api
    public-client: true
    bearer-only: true
    auth-server-url: http://localhost:8080/auth
    credentials:
      secret: 82eXXXXX-3XXX-4XXX-XXX7-287aXXXXXXXX
    principal-attribute: preferred_username
    cors: true

From localhost:port/ I have a first interface (with no Keycloak safety) that has a link to my service, which is localhost:port/index/{topicName} . Now when I click on that link, I'm supposed to get the Keycloak authentication screen, but I get a 401 error instead.

I've checked the header of my request, adding a HttpServletRequest as a parameter to my displayMessage method, and I actually could display the access_token and the X-Auth-Token in my IDE's console. But it seems like when I follow that link, it sends the request without the token.

Here are my controller methods (my Controller class is annotated with @Controller:

@GetMapping(path = "/")
    public String index() {
        return "external";
    }

    @GetMapping(path = "/index/{topicName}",
            produces = MediaType.APPLICATION_JSON_VALUE)
    public String displayMessages(Model model, 
            @PathVariable String topicName) {       

            //HttpHeaders headers = new HttpHeaders();

            //headers.set("Authorization", request.getHeader("Authorization"));
            //header = request.getHeader("Authorization");
            //System.out.println(" T O K E N "+request.getHeader("X-Auth-Token"));

            projectServiceImpl.findByName(topicName);
            List<Message> messages = messageServiceImpl.findAllMessagesByProjectName(topicName);
            model.addAttribute("topic", topicName);
            model.addAttribute("message",messages);

            return "index";
    }

My Keycloak config file is inspired from the tuto's I've read, so there might be a mistake in there that I don't know about (not sure what the difference between methods access and hasRole is) :

@Configuration
@ComponentScan(
        basePackageClasses = KeycloakSecurityComponents.class,
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.REGEX, 
                pattern = "org.keycloak.adapters.springsecurity.management.HttpSessionManager"))
@EnableWebSecurity
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {


    private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    @Bean
    public HttpSessionIdResolver httpSessionIdResolver() { //replace HttpSessionStrategy
        return HeaderHttpSessionIdResolver.xAuthToken();
    }

    //Registers the KeycloakAuthenticationProvider with the authentication manager.
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        try {
            SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
            grantedAuthorityMapper.setPrefix("ROLE_");
            grantedAuthorityMapper.setConvertToUpperCase(true);

            KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
            keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);

            auth.authenticationProvider(keycloakAuthenticationProvider());

        } catch(Exception ex)  {
            logger.error("SecurityConfig.configureGlobal: " + ex);

        }

         /*try {
                KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
                keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
                auth.authenticationProvider(keycloakAuthenticationProvider);
            }catch(Exception ex){
                logger.error("SecurityConfig.configureGlobal: " +ex);
            }*/
    }

    //Load Keycloak properties from service config-file
    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    //Defines the session authentication strategy.
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        //Public or Confidential application keycloak/OpenID Connect client
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
        //Bearer mode only keycloak/OpenID Connect client without keycloak session -> stateless behavior
        //return new NullAuthenticatedSessionStrategy();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        super.configure(http);
        http.authorizeRequests()
        //BEGIN
            //USER -done to be tested

            .antMatchers(HttpMethod.GET,"/index**").access("hasAuthority('ADMIN')")
            .antMatchers(HttpMethod.GET,"/").access("hasAuthority('ADMIN')")
            .antMatchers(HttpMethod.GET,"/").access("hasAnyAuthority('ADMIN','MANAGER','EXPERT','STANDARD')")



            .anyRequest().authenticated() 

            .and()
            .cors()
            .and()
            .csrf().disable()
            //BEGIN Login/Logout
            .formLogin()
                .permitAll()//.successHandler(authenticationSuccessHandler) //
            .and()
            .logout()//.clearAuthentication(true) //Add .clearAuthentication(true) to logout()
                //.logoutUrl("/custom-logout")
                .addLogoutHandler(keycloakLogoutHandler())
                //.addLogoutHandler(new LogoutHandlerImpl())
                .clearAuthentication(true)
                .invalidateHttpSession(true)
                .permitAll();
            //END Login/Logout

        //BEGIN Session
        http
        .sessionManagement()
            //.sessionCreationPolicy(SessionCreationPolicy.ALWAYS) //BY default IF_REQUIRED
            .maximumSessions(1)
                .maxSessionsPreventsLogin(false) // if true generate an error when user login after reaching maximumSession (SessionAuthenticationStrategy rejected the authentication object / SessionAuthenticationException: Maximum sessions of 1 for this principal exceeded)
                //.expiredUrl("/auth/login")
                .sessionRegistry(sessionRegistry());   



    }

     @Bean
     @Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
     public AccessToken accessToken() {
         HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
         return ((KeycloakSecurityContext) ((KeycloakAuthenticationToken) request.getUserPrincipal()).getCredentials()).getToken();
     }

    ///BEGIN session     
     @Bean
     public SessionRegistry sessionRegistry() {
        SessionRegistry sessionRegistry = new SessionRegistryImpl();
        return sessionRegistry;
     }


    @Bean
    public RegisterSessionAuthenticationStrategy registerSessionAuthStr( ) {
        return new RegisterSessionAuthenticationStrategy( sessionRegistry( ) );
    }

    // Register HttpSessionEventPublisher
    @Bean
    public static ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
        return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
    }

I don't really know what else I should change to make it work, but I believe there has to be something wrong in there. But I think if I can have the Keycloak authentication screen when trying to access my service, this would be alright.

Upvotes: 4

Views: 16354

Answers (2)

Fosol
Fosol

Reputation: 147

This solved my issue. I was using within a docker container and had to match both to host.docker.internal

Upvotes: 3

manyways
manyways

Reputation: 4756

I received the same error, one thing to double check is that auth-server-url is the same for the server, and the client getting the token.

I.e. if one is the dns name, and one is the IP address, it will not authorize. (in my case I had localhost and 127.0.0.1 so authorization failed)

Server, src/main/resources/application.yml

enter image description here

Postman/client:

enter image description here

Upvotes: 7

Related Questions