Invalid access token generated from Angular keycloak adapter for Spring Boot backend authorization

172 views Asked by At

Angular’s keycloak adapter generates access tokens, that are invalid for REST communication with Spring Boot backend application secured by ta same keycloak’s realm. Localhost addresses were replaced with example.com because of SPAM filters reasons.

Keycloak setup

Testing keycloak 22.0.3 instance on localhost run from bin/kc.sh script on default port Angular and Spring Boot application have different clients, but same realm.

Angular client

Angular client ID

Angular URIs

Angular flow config

Rest of settings are default.

Spring Boot

Spring client ID

Spring URIs

Spring flow config

Rest of settings are default.

Angular adapter

Library used in project: keycloak-angular 14.1.0

Setup in project:

Provider

    {
      provide: APP_INITIALIZER,
      useFactory: initializeKeycloak,
      multi: true,
      deps: [KeycloakService]
    }

Factory

function initializeKeycloak (keycloak: KeycloakService) {
  return () =>
    keycloak.init ({
      config: {
        url: 'example.com:8080',
        realm: 'SpringBootKeycloak',
        clientId: 'angular-client'
      },
      initOptions: {
        onLoad: 'check-sso',
        silentCheckSsoRedirectUri:
          window.location.origin + '/assets/silent-check-sso.html'
      }
    });
}

Keycloak service is used for logging and logout. Web authorization in browser works on Angular application. Generating valid tokens for backend doesn't.

Spring Boot Security

application.properties

spring.security.oauth2.client.registration.keycloak.client-id=spring-client
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid
spring.security.oauth2.client.provider.keycloak.issuer-uri=example.com:8080/realms/SpringBootKeycloak
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username
spring.security.oauth2.resourceserver.jwt.issuer-uri=example.com:8080/realms/SpringBootKeycloak
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=example.com:8880/auth/realms/SpringBootKeycloak/protocol/openid-connect/certs

Security config

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig {

    SecurityConfig() {
    }

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


    @Order(1)
    @Bean
    public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
        http.oauth2ResourceServer((oauth2) -> oauth2
                .jwt(Customizer.withDefaults()));
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .build();
    }

}

Sample http requests

request.http This requests give valid token to Spring Boot Application.

Obtaining token

POST example.com:8080/realms/SpringBootKeycloak/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

client_id=spring-client&username=user&password=password&grant_type=password

> {%
    client.global.set("token", response.body.access_token);
%}

Using the token in get request.

GET example.com:8081/api/resource
Authorization: Bearer {{token}}

Errors

Reponse in browser with token from adapter

401 Unauthorized
x-frame-options DENY

Stack trace in Spring Boot console

java.lang.StackOverflowError: null
    at org.springframework.aop.support.AopUtils.isEqualsMethod(AopUtils.java:151) ~[spring-aop-6.0.9.jar:6.0.9]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:175) ~[spring-aop-6.0.9.jar:6.0.9]
    at jdk.proxy2/jdk.proxy2.$Proxy175.authenticate(Unknown Source) ~[na:na]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201) ~[spring-security-core-6.1.0.jar:6.1.0]

Solutions that didn't work

I stumbled upon this answer and tried using this solution:

"I had the same problem with a Java Spring Backend, after a lot of google searches, I found out that there were incompatibilities between Signature Algorithm used by spring boot and keycloak.

As part of the initiation I specified SignatureAlgorithm.RS256 in the application and also ensured that same algorithm was used for the realm/client.

I really dont know the specifics on how to use keycloak with nodejs, but for Java Spring I created a @Bean in the WebSecurity class as follows"

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig {
@Value(“${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}”)
private String jwkSetUri;

@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).jwsAlgorithm(SignatureAlgorithm.RS256).build();
}
}

But it doesn't seem to help.

I expected to authenticate using token from angular adapter in REST request.

1

There are 1 answers

0
Slawomir On BEST ANSWER

Two things went wrong here:

  1. Code below does produces StackOverflowError exception in Spring application
@Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .build();
    }
  1. Because of SPAM filter addresses replacement I didn't have presented valid addresses. There was no consistency in keycloak addresses (127.0.0.1 in angular and localhost in spring). This caused invalid iss claim value. Spring config value must be same as iss value.