I am building a Spring OAuth2 Authorization Server for users and essentially started by using 1 of the example projects on the official Spring Authorization Server github.
With that, I’m trying to use my Spring Auth. Server as an IDP in Keycloak.
I have successfully added it as an IDP and Keycloak redirects me to the Spring Auth Server’s login form, but when I login, Keycloak throws the below exception:
2024-08-07 17:57:36,179 WARN [org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProvider] (executor-thread-11) PublicKey wasn't found in the storage. Requested kid: 'd2ff780e-5b42-4f7a-83dc-e7bd23d838de' . Available kids: '[]'
2024-08-07 17:57:36,180 ERROR [org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (executor-thread-11) Failed to make identity provider oauth callback: org.keycloak.broker.provider.IdentityBrokerException: token signature validation failed
However, when I visit the JWK URI of my Spring Auth. Server, it shows the exact kid for which Keycloak is looking for, and it is clearly not an empty set as Keycloak claims, see below:
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "d2ff780e-5b42-4f7a-83dc-e7bd23d838de",
"alg": "RS256",
"n": "4wURNXcZ7g54Bl92Zw9l8Vr02D1u_e9ddW2E56DmMUfDdKwwGkpf4yyqC4HYl8V9yBLA1XG6VGKSn4b0sdXjJQ7kEBxUdGz-JNNynU_E9bypeJQARxWHV7-U73gsWeXS0HNIsoo6DryZ4d9cDS3wsF_saKzFXWKILGcSjnE5jJHEoqHVyqgQTL17jSbypGJ6y5fSoOLc0USI_XLNqiZw8lwEJzhWIbnigxnbrNo97oEwKzADa98wfsF_Re_NaZ_TIynjoZwZAKVz4bK3ZAulvlUItWhVj0ySGHMh9WcjlZlowZd94eaAkS9J7NHvxiC4gV3Iv0Ij4c_2zq5NhMdWMw"
}
]
}
I HAVE performed authorization_code grant flow with my Spring Auth. Server DIRECTLY (without Keycloak using it as IDP) successfully, using my in memory user.
I took the JWT generated by the Spring Auth. Server and validated both the header and the signature using jwt.io. There’s nothing wrong with it.
Now that the high level issue has been described, I will post some additional info below:
Here are the openId urls returned by my “.well-known/openid-configuration” endpoint of my Spring Authorization Server (yes, I have https enabled, as Keycloak requires this for IDP’s)
{"issuer":"https://auth-server:8443","authorization_endpoint":"https://auth-server:8443/oauth2/authorize","token_endpoint":"https://auth-server:8443/oauth2/token","token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt"],"jwks_uri":"https://auth-server:8443/oauth2/jwks","userinfo_endpoint":"https://auth-server:8443/userinfo","response_types_supported":["code"],"grant_types_supported":["authorization_code","client_credentials","refresh_token"],"revocation_endpoint":"https://auth-server:8443/oauth2/revoke","revocation_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt"],"introspection_endpoint":"https://auth-server:8443/oauth2/introspect","introspection_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","client_secret_jwt","private_key_jwt"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"scopes_supported":["openid"]}
And here are some of the configurations in Keycloak including these same URLs, so I don’t see how any of the URL’s should be incorrect. We can see that RS256 is also the cryptographic signing algorithm selected, which is correct per what my Spring Auth. Server uses to generate the JWT:
And lastly, here are most of my configurations for the Spring Auth server. Here we can further confirm that yes, RS256 is what is used to generate the JWT for users, and, for OAuth2 client connection (such as Keycloak -> Spring Auth Server) the selection in Keycloak IDP settings of “Client Secret sent as Basic Auth” is indeed correct as well, as are the clientId/secret credentials:
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client1")
.clientSecret("{noop}myClientSecretValue")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("https://127.0.0.1:8080/login/oauth2/code/users-client-oidc")
.redirectUri("https://127.0.0.1:8080/authorized")
.redirectUri("http://localhost:8080/realms/ApiRealm/broker/oidc/endpoint")
.scope(OidcScopes.OPENID)
.scope("read")
//.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults())
;
http.cors(cors -> cors.disable());
http.requiresChannel( channel -> channel.anyRequest().requiresSecure());
return http.formLogin(Customizer.withDefaults()).build();
}
@Bean
public ClientSettings clientSettings() {
return ClientSettings.builder()
.requireAuthorizationConsent(false)
.requireProofKey(false)
.build();
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder()
.issuer("https://auth-server:8443")
.build();
}
I have indeed looked at other posts, all that I’ve found are either unanswered and/or too contextually different. Thanks a lot in advance.