Spring boot resource server with JWT base64 encoded Failed to authenticate since the JWT was invalid - spring

I'm trying to use spring resource server starter with fusionauth.io. the fusion auth token is working just fine with postman and when I want to decode it in jwt.io I should check the secret base64 option to get the valid JWT.
application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://localhost:9011/oauth2/token
SecurityConfig
#Configuration
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Bean
public JwtDecoder jwtDecoder(){
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(
"http://localhost:9011/oauth2/token").build();
return jwtDecoder;
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authz -> authz
.antMatchers(HttpMethod.GET, "/user/**").permitAll()
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated()).csrf().disable()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
}
sample jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE1MDE1YWJiYyJ9.eyJhdWQiOiI1OTM4M2ViZS0zYjEzLTQ0YjktODM2MS0xZGQ0MWIxYzdlNDkiLCJleHAiOjE2MDgwODgwMjksImlhdCI6MTYwODA4NDQyOSwiaXNzIjoiYWNtZS5jb20iLCJzdWIiOiJiZGVhZDg5Yi1iNTQ3LTRlNDEtODJlMi1iMWIzNjkxZjA0Y2YiLCJqdGkiOiI3ZjZlYTgwMC1hZTgwLTQ0NzgtOWNmOC1mNzQ5ZTM3YjRlNzIiLCJhdXRoZW50aWNhdGlvblR5cGUiOiJQQVNTV09SRCIsImVtYWlsIjoidGVzdEBlbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImFwcGxpY2F0aW9uSWQiOiI1OTM4M2ViZS0zYjEzLTQ0YjktODM2MS0xZGQ0MWIxYzdlNDkiLCJyb2xlcyI6WyJ1c2VyIl19.o9Qtj7tbqo_imkpNn0eKsg-Fhbn91yu5no1oVaXogNY
the error im getting:
2020-12-16 05:37:56.934 DEBUG 26116 --- [nio-8500-exec-3] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2020-12-16 05:37:56.934 DEBUG 26116 --- [nio-8500-exec-3] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2020-12-16 05:38:00.012 DEBUG 26116 --- [nio-8500-exec-2] o.s.security.web.FilterChainProxy : Securing GET /user/me
2020-12-16 05:38:00.012 DEBUG 26116 --- [nio-8500-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2020-12-16 05:38:00.020 DEBUG 26116 --- [nio-8500-exec-2] o.s.s.o.s.r.a.JwtAuthenticationProvider : Failed to authenticate since the JWT was invalid
2020-12-16 05:38:00.022 DEBUG 26116 --- [nio-8500-exec-2] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2020-12-16 05:38:00.022 DEBUG 26116 --- [nio-8500-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

The JwtDecoders.fromIssuerLocation will attempt to resolve the jwks_uri from the OpenID Connect discovery document found using the issuer URI.
https://github.com/spring-projects/spring-security/blob/848bd448374156020210c329b886fca010a5f710/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java#L119
The FusionAuth JSON Web Key Set (JWKS) only publishes the public key from asymmetric key pairs. This means there are no public keys published and the Spring boot library cannot verify the token signature.
For example, if your issuerUri is https://example.com then the OpenID Discovery URL is https://example.com/.well-known/openid-configuration and the value for jwks_uri found in the JSON response from that URL will be https://example.com/.well-known/jwks.json. If you hit that URL you will see no public keys are being returned, this is the JSON that the library is consuming in an attempt to build the public key necessary to validate the JWT signature.
To use this strategy then you'll need to configure FusionAuth to sign the JWT using an RSA or ECDSA key pair instead of the default HMAC key which is symmetric.
Generate a new RSA or ECDA key pair in Key Master (Settings > Key Master) and then ensure you have your JWT signing configuration use that key. The primary JWT signing configuration will be found in the tenant, with optional application level overrides.
https://fusionauth.io/docs/v1/tech/core-concepts/tenants/#jwt
https://fusionauth.io/docs/v1/tech/core-concepts/applications/#jwt
Hope that helps. Once you modify your configuration so that public keys are returned in the JWKS response, and the library is still not validating the token, please re-open and we can go from there.

The reason may be that the JwtDecoder is not being referenced by the oauth2ResourceServer. Check this resource here to see the way they are setting up the ouath2ResourceSever: https://curity.io/resources/tutorials/howtos/writing-apis/spring-boot-api/
In general the token is probably failing the signature validation and so you need to make sure your trusted issuer is configured properly.

Related

Why does a Keycloak bearer token appear to be truncated during security filter chain processing

I am working with Keycloak 16.1.0, spring boot 2.6.2 and an external application client that sends a bearer token in to my server application to the endpoint http://romanmed-host:8888/actuator/health.
By cranking the debugging level up to maximum, I can see the access token before its processed. I can verify that its accurate by using the JWT Debug site JSON Web Tokens to verify that the signature is correct.
Yet several lines later in the output log the same bearer token appears to be somewhat truncated, its listed with an error saying that it failed to verify. When checked by using the JWT site indicates a signature error, but the token content is correct.
Naturally I would like to know why it appears to be truncated and what I can do about it.
I can match the output from the client program to the server and its not been changed, so truncation must occur within the server program.
The program is accepting the request by a get request, since the token can be checked by JWT as valid at this point, its not truncated by the get request input method.
I have not inserted a filter in the security filter chain, so I can see how any of my code could be doing anything to invalid the token.
Other than the Failed to verify token no other error messages are generated, suggesting that until this point everything is correct.
I can see that the WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter,KeycloakPreAuthActionsFilter and KeycloakAuthenticationProcessingFilter have all been invoked.
I am assuming that the problem is somewhere within the KeycloakAuthenticationProcessingFilter, but I don't understand why the token appears to have been truncated at this point.
The received bearer token is
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dUhKc1pvWnduelVsU1Zqc2JyTkxsbUNhR0ZIVkV0cTcyQkI5V0pORTVVIn0.eyJleHAiOjE2NDY0MDI3NTAsImlhdCI6MTY0NjQwMjQ1MCwianRpIjoiMjIyMjUxZDgtNDYxMy00OGQwLWEwNzAtMjU5YTYyY2NhZDkyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg1L2F1dGgvcmVhbG1zL0Jvb3RBZG1pbiIsImF1ZCI6WyJybS1jb25maWctc2VydmVyIiwiYXBwLXRvZG8iLCJhY2NvdW50Il0sInN1YiI6Ijc4ZTU1YjhiLWQ5MjAtNGQ0Yi1hNWQ5LWIyZDk3MDYzNDgyYiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcC1hZG1pbiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwMSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYm9vdGFkbWluIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicm0tY29uZmlnLXNlcnZlciI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYXBwLWFkbWluIjp7InJvbGVzIjpbImFjdHVhdG9yIl19LCJhcHAtdG9kbyI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJhcHAtYWRtaW4iLCJjbGllbnRIb3N0IjoiMTI3LjAuMC4xIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtYXBwLWFkbWluIiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSJ9.fwQPLiSIrUSjnRnTBrd1vvGic49OSf7aGDemc0TdmTshZzJ-eYhiEqnAh9-QU2rxDayPIhoIzA9CgBXmGPCnl1Qu4CujDddpBcLpnjszBoBdzwjDgpShgwFpGk0fGCM0fxtSZgMWRfeS_sRjBpRzZ42GelCYZ2E1kZX_E7o_LB3thpiv5oYqgTNucusNmzpm0-iFcEUe5rfnu2ZOHI_hLQvIYKlGURnNld4jov-KDLf2QTh2h3XqjbsGHG9PDq4MbFPhKY_9yF0jQkhF6F3oYrw9MIH4SbemrR-CHw6-aWqGmgucjJ7iKMY5o86HxLPu2tzM06NdaurQZX4ImLCBlQ
Its truncated format is
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dUhKc1pvWnduelVsU1Zqc2JyTkxsbUNhR0ZIVkV0cTcyQkI5V0pORTVVIn0.eyJleHAiOjE2NDY0MDI3NTAsImlhdCI6MTY0NjQwMjQ1MCwianRpIjoiMjIyMjUxZDgtNDYxMy00OGQwLWEwNzAtMjU5YTYyY2NhZDkyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg1L2F1dGgvcmVhbG1zL0Jvb3RBZG1pbiIsImF1ZCI6WyJybS1jb25maWctc2VydmVyIiwiYXBwLXRvZG8iLCJhY2NvdW50Il0sInN1YiI6Ijc4ZTU1YjhiLWQ5MjAtNGQ0Yi1hNWQ5LWIyZDk3MDYzNDgyYiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcC1hZG1pbiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwMSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYm9vdGFkbWluIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicm0tY29uZmlnLXNlcnZlciI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYXBwLWFkbWluIjp7InJvbGVzIjpbImFjdHVhdG9yIl19LCJhcHAtdG9kbyI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJhcHAtYWRtaW4iLCJjbGllbnRIb3N0IjoiMTI3LjAuMC4xIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtYXBwLWFkbWluIiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSJ9
The debug log is
servletPath:/actuator/health
pathInfo:null
headers:
accept-encoding: gzip
user-agent: ReactorNetty/1.0.13
host: romanmed-host:8888
authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dUhKc1pvWnduelVsU1Zqc2JyTkxsbUNhR0ZIVkV0cTcyQkI5V0pORTVVIn0.eyJleHAiOjE2NDY0MDI3NTAsImlhdCI6MTY0NjQwMjQ1MCwianRpIjoiMjIyMjUxZDgtNDYxMy00OGQwLWEwNzAtMjU5YTYyY2NhZDkyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg1L2F1dGgvcmVhbG1zL0Jvb3RBZG1pbiIsImF1ZCI6WyJybS1jb25maWctc2VydmVyIiwiYXBwLXRvZG8iLCJhY2NvdW50Il0sInN1YiI6Ijc4ZTU1YjhiLWQ5MjAtNGQ0Yi1hNWQ5LWIyZDk3MDYzNDgyYiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcC1hZG1pbiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwMSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYm9vdGFkbWluIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicm0tY29uZmlnLXNlcnZlciI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYXBwLWFkbWluIjp7InJvbGVzIjpbImFjdHVhdG9yIl19LCJhcHAtdG9kbyI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJhcHAtYWRtaW4iLCJjbGllbnRIb3N0IjoiMTI3LjAuMC4xIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtYXBwLWFkbWluIiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSJ9.fwQPLiSIrUSjnRnTBrd1vvGic49OSf7aGDemc0TdmTshZzJ-eYhiEqnAh9-QU2rxDayPIhoIzA9CgBXmGPCnl1Qu4CujDddpBcLpnjszBoBdzwjDgpShgwFpGk0fGCM0fxtSZgMWRfeS_sRjBpRzZ42GelCYZ2E1kZX_E7o_LB3thpiv5oYqgTNucusNmzpm0-iFcEUe5rfnu2ZOHI_hLQvIYKlGURnNld4jov-KDLf2QTh2h3XqjbsGHG9PDq4MbFPhKY_9yF0jQkhF6F3oYrw9MIH4SbemrR-CHw6-aWqGmgucjJ7iKMY5o86HxLPu2tzM06NdaurQZX4ImLCBlQ
accept: application/vnd.spring-boot.actuator.v2+json, application/vnd.spring-
boot.actuator.v1+json, application/json
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
KeycloakPreAuthActionsFilter
KeycloakAuthenticationProcessingFilter
LogoutFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
KeycloakSecurityContextRequestFilter
KeycloakAuthenticatedActionsFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************
2022-03-04 14:03:30.088 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#2caa9666, org.springframework.security.web.context.SecurityContextPersistenceFilter#67683210, org.springframework.security.web.header.HeaderWriterFilter#58a9e64d, org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter#3fecb076, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter#41d84abb, org.springframework.security.web.authentication.logout.LogoutFilter#3e563293, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#25511895, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#21202507, org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter#62159fd, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter#28e8dee7, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#263f6e96, org.springframework.security.web.session.SessionManagementFilter#d3b0397, org.springframework.security.web.access.ExceptionTranslationFilter#75d0cac6, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#2267b0bb]] (1/1)
2022-03-04 14:03:30.088 DEBUG 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Securing GET /actuator/health
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (1/14)
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking SecurityContextPersistenceFilter (2/14)
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] w.c.HttpSessionSecurityContextRepository : Created SecurityContextImpl [Null authentication]
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (3/14)
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking KeycloakPreAuthActionsFilter (4/14)
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] o.k.adapters.PreAuthActionsHandler : adminRequest http://romanmed-host:8888/actuator/health
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.s.security.web.FilterChainProxy : Invoking KeycloakAuthenticationProcessingFilter (5/14)
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] f.KeycloakAuthenticationProcessingFilter : Attempting Keycloak authentication
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.k.adapters.RequestAuthenticator : --> authenticate()
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.k.adapters.RequestAuthenticator : try bearer
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] o.k.a.BearerTokenRequestAuthenticator : Found [1] values in authorization header, selecting the first value for Bearer.
2022-03-04 14:03:30.089 DEBUG 99667 --- [.1-8888-exec-10] o.k.a.BearerTokenRequestAuthenticator : Verifying access_token
2022-03-04 14:03:30.089 TRACE 99667 --- [.1-8888-exec-10] o.k.a.BearerTokenRequestAuthenticator : access_token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3dUhKc1pvWnduelVsU1Zqc2JyTkxsbUNhR0ZIVkV0cTcyQkI5V0pORTVVIn0.eyJleHAiOjE2NDY0MDI3NTAsImlhdCI6MTY0NjQwMjQ1MCwianRpIjoiMjIyMjUxZDgtNDYxMy00OGQwLWEwNzAtMjU5YTYyY2NhZDkyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg1L2F1dGgvcmVhbG1zL0Jvb3RBZG1pbiIsImF1ZCI6WyJybS1jb25maWctc2VydmVyIiwiYXBwLXRvZG8iLCJhY2NvdW50Il0sInN1YiI6Ijc4ZTU1YjhiLWQ5MjAtNGQ0Yi1hNWQ5LWIyZDk3MDYzNDgyYiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwcC1hZG1pbiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwMSJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYm9vdGFkbWluIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicm0tY29uZmlnLXNlcnZlciI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYXBwLWFkbWluIjp7InJvbGVzIjpbImFjdHVhdG9yIl19LCJhcHAtdG9kbyI6eyJyb2xlcyI6WyJhY3R1YXRvciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SWQiOiJhcHAtYWRtaW4iLCJjbGllbnRIb3N0IjoiMTI3LjAuMC4xIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtYXBwLWFkbWluIiwiY2xpZW50QWRkcmVzcyI6IjEyNy4wLjAuMSJ9.signature
2022-03-04 14:03:30.091 DEBUG 99667 --- [.1-8888-exec-10] o.k.a.BearerTokenRequestAuthenticator : Failed to verify token
2022-03-04 14:03:30.091 DEBUG 99667 --- [.1-8888-exec-10] o.k.adapters.RequestAuthenticator : Bearer FAILED
2022-03-04 14:03:30.091 DEBUG 99667 --- [.1-8888-exec-10] f.KeycloakAuthenticationProcessingFilter : Auth outcome: FAILED
2022-03-04 14:03:30.092 TRACE 99667 --- [.1-8888-exec-10] f.KeycloakAuthenticationProcessingFilter : Failed to process authentication request
org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for detailsr code here
The code is taken from an example by Thomas Darimont at Securing Spring Boot Admin & actuator endpoints with Keycloak and assumed to be correct.
The code is as follows
import lombok.extern.slf4j.Slf4j;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import java.security.Principal;
#KeycloakConfiguration
#Slf4j
#EnableConfigurationProperties(KeycloakSpringBootProperties.class)
class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf().disable()
.authorizeRequests()
.requestMatchers(EndpointRequest.to(
InfoEndpoint.class,
HealthEndpoint.class
)).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint())
.hasRole("ACTUATOR")
.anyRequest().permitAll()
;
}
/**
* Use {#link KeycloakAuthenticationProvider}
*
* #param auth
* #throws Exception
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setPrefix("ROLE_");
grantedAuthorityMapper.setConvertToUpperCase(true);
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
}
#Bean
protected SessionRegistry buildSessionRegistry() {
return new SessionRegistryImpl();
}
/**
* Allows to inject requests scoped wrapper for {#link KeycloakSecurityContext}.
*
* Returns the {#link KeycloakSecurityContext} from the Spring
* {#link ServletRequestAttributes}'s {#link Principal}.
* <p>
* The principal must support retrieval of the KeycloakSecurityContext, so at
* this point, only {#link KeycloakPrincipal} values and
* {#link KeycloakAuthenticationToken} are supported.
*
* #return the current <code>KeycloakSecurityContext</code>
*/
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public KeycloakSecurityContext provideKeycloakSecurityContext() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
Principal principal = attributes.getRequest().getUserPrincipal();
if (principal == null) {
return null;
}
if (principal instanceof KeycloakAuthenticationToken) {
principal = Principal.class.cast(KeycloakAuthenticationToken.class.cast(principal).getPrincipal());
}
if (principal instanceof KeycloakPrincipal) {
return KeycloakPrincipal.class.cast(principal).getKeycloakSecurityContext();
}
return null;
}
The problem is the set up of the client and server programs.
The server used the value
auth-server-url: http://localhost:8085/auth
in its application.yml file to define the location of the Keycloak server, the client used the value
auth-server-url: http://romanmed-host:8085/auth
to define the location of Keycloak where the machine name romanmed-host is an alias for localhost. Having changed these values to be the same value, everything works as expected.
The diagnostics generated by the debugging/trace code are confusing, string described as truncated header seems to be truncated the bearer token, with out the signature. What the 'Keycloak` diagnostic is attempting to print is the part of the token which defines the tokens permissions and not the signature section.
Running the entire bearer token through the JWT site does show that the token is valid, because its a correctly encoded token and is legitimate.
The problem is not the token, but the way the token is being used! The client was expecting a legal signed token generated by them instance of Keycloak that it knew about, what it got was a legal signed token generated by Keycloak with a different address, which it correctly objected to.
The problem being the nature of the generated error message, it just claimed that the token signature was invalid, had it said something about an invalid/unexpected hostname, the nature of the problem would have been rather more obvious and resolved much faster. Keycloak is design to be flexible, so error messages tend to more vague to cover all situations, hence the message there is something wrong with your bearer token signature which is correct, but vague.
There seems to several schools of thought on how to resolve issues like this, one is to use an raw ip address which will always resolve to the same value. Thus avoid problems like this. This suffers from if the Keycloak server is moved to another machine there are lots of values to change.
My solution is to define an alias value in the hosts/dns server for the address of the eycloak server and always use that value in the support files. Hence if the Keycloak server is ever moved to another address, there is only one value to change.

Why does Spring Security reject my Keycloak auth token with "No AuthenticationProvider found"?

I'm trying to figure out why my Spring Boot application is rejecting my Keycloak JWT bearer token with a "No AuthenticationProvider found" error message.
I have a few services running in a docker compose environment:
ui (angular) -> proxy (nginx) -> rest api (spring boot) -> auth service (keycloak)
The angular ui pulls the correct keycloak client from the rest service, and then authenticates without issue. I get back a JWT token, and then turn around and hand that to follow on requests to the rest api in a header Authorization: bearer [token].
In the rest API, I can see the correct bearer token come in as a header:
2022-02-11 01:01:31.411 DEBUG 13 --- [nio-8080-exec-4] o.a.coyote.http11.Http11InputBuffer : Received [GET /api/v3/accounts HTTP/1.0
X-Real-IP: 192.168.80.1
X-Forwarded-For: 192.168.80.1
Host: rest-api.mylocal.com
Connection: close
Accept: application/json, text/plain, */*
Authorization: Bearer eyJhbGciO...
...
2022-02-11 01:01:31.421 DEBUG 13 --- [nio-8080-exec-4] o.k.adapters.PreAuthActionsHandler : adminRequest http://rest-api.mylocal.com/api/v3/accounts
...
So the bearer token is there, and with https://jwt.io/ I can verify it's what I would expect:
{
"exp": 1644515847,
...
"iss": "http://auth-service.mylocal.com/auth/realms/LocalTestRealm",
...
"typ": "Bearer",
"azp": "LocalTestClient",
...
"allowed-origins": [
"http://web-ui.mylocal.com"
],
"realm_access": {
"roles": [
"offline_access",
"default-roles-localtestrealm",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid email profile",
...
}
Processing continues by the rest api - it contacts the keycloak service and pulls the well known config:
...
2022-02-11 01:01:33.321 INFO 13 --- [nio-8080-exec-4] o.keycloak.adapters.KeycloakDeployment : Loaded URLs from http://auth-service.mylocal.com/auth/realms/LocalTestRealm/.well-known/openid-configuration
...
Finally it looks like it successfully parses the bearer token apart, grabs the user and authenticates them:
2022-02-11 01:01:33.521 DEBUG 13 --- [nio-8080-exec-4] o.a.h.impl.conn.tsccm.ConnPoolByRoute : Releasing connection [{}->http://auth-service.mylocal.com:80][null]
2022-02-11 01:01:33.521 DEBUG 13 --- [nio-8080-exec-4] o.a.h.impl.conn.tsccm.ConnPoolByRoute : Pooling connection [{}->http://auth-service.mylocal.com:80][null]; keep alive indefinitely
2022-02-11 01:01:33.521 DEBUG 13 --- [nio-8080-exec-4] o.a.h.impl.conn.tsccm.ConnPoolByRoute : Notifying no-one, there are no waiting threads
2022-02-11 01:01:33.530 DEBUG 13 --- [nio-8080-exec-4] o.k.a.rotation.JWKPublicKeyLocator : Realm public keys successfully retrieved for client LocalTestClient. New kids: [8a7dIQFASdC8BHa0mUWwZX7RBBJSeJItdmzah0Ybpcw]
2022-02-11 01:01:33.546 DEBUG 13 --- [nio-8080-exec-4] o.k.a.BearerTokenRequestAuthenticator : successful authorized
2022-02-11 01:01:33.550 TRACE 13 --- [nio-8080-exec-4] o.k.a.RefreshableKeycloakSecurityContext : checking whether to refresh.
2022-02-11 01:01:33.550 TRACE 13 --- [nio-8080-exec-4] org.keycloak.adapters.AdapterUtils : useResourceRoleMappings
2022-02-11 01:01:33.550 TRACE 13 --- [nio-8080-exec-4] org.keycloak.adapters.AdapterUtils : Setting roles:
2022-02-11 01:01:33.555 DEBUG 13 --- [nio-8080-exec-4] a.s.a.SpringSecurityRequestAuthenticator : Completing bearer authentication. Bearer roles: []
2022-02-11 01:01:33.556 DEBUG 13 --- [nio-8080-exec-4] o.k.adapters.RequestAuthenticator : User 'bf7307ca-9352-4a02-b288-0565e2b57292' invoking 'http://rest-api.mylocal.com/api/v3/accounts' on client 'LocalTestClient'
2022-02-11 01:01:33.556 DEBUG 13 --- [nio-8080-exec-4] o.k.adapters.RequestAuthenticator : Bearer AUTHENTICATED
2022-02-11 01:01:33.556 DEBUG 13 --- [nio-8080-exec-4] f.KeycloakAuthenticationProcessingFilter : Auth outcome: AUTHENTICATED
and then immediately after that fails with the No AuthenticationProvider found error:
2022-02-11 01:01:33.559 TRACE 13 --- [nio-8080-exec-4] f.KeycloakAuthenticationProcessingFilter : Failed to process authentication request
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:234) ~[spring-security-core-5.5.1.jar!/:5.5.1]
I'm at a loss how it can say Bearer AUTHENTICATED followed by Auth outcome: AUTHENTICATED followed by No AuthenticationProvider found... I'm assuming it somehow can't convert this bearer token into a Keycloak token, even though it definitely came from my Keycloak server.
My app config:
#ComponentScan({"com.mycompany"})
#Configuration
#EnableJpaRepositories(basePackages = "com.mycompany")
#EntityScan("com.mycompany")
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class ApplicationConfiguration
extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
// These paths (comma separated) are allowed to all
.antMatchers("/api/v3/auth/config").permitAll()
.and()
.authorizeRequests()
// Everything else should be authenticated
.anyRequest().authenticated()
.and()
.csrf().disable();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
// This just pulls the Keycloak config from a DB instead of the config file
return new CustomKeycloakConfigResolver();
// return new KeycloakSpringBootConfigResolver();
}
}
Missing the global config to autowire in a Keycloak auth provider:
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth)
throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider =
keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(
new SimpleAuthorityMapper()
);
auth.authenticationProvider(keycloakAuthenticationProvider);
}

spring webflux - don't create session for specific paths

My spring webflux service exposes a health-check endpoint, which is called every few seconds. spring-security is configured, and currently each health-check call creates a new session, which fills the SessionStore quickly.
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/actuator/*").permitAll() // disable security for health-check
.anyExchange().authenticated()
...
.and().build();
}
logs:
2020-07-23 21:58:03.805 DEBUG 4722 --- [ctor-http-nio-3] o.s.w.s.adapter.HttpWebHandlerAdapter : [b185e815-1] HTTP GET "/actuator/health"
2020-07-23 21:58:03.845 DEBUG 4722 --- [ctor-http-nio-3] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession.
Is it possible to configure spring-session or spring-security to not create sessions for specific paths?

AnonymousAuthenticationFilter intercepts request after successful login with Custom Authentication Provider

We have a CustomAuthenticationProvider(AuthenticationProvider) developed for Spring which works with CustomAuthenticationRequest(Authentication), CustomAuthentication(Authentication), a CustomUser.
Once we validate credentials when our Controller is invoked we create a CustomAuthenticationRequest based on the credentials.
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(new CustomAuthenticationRequest(new CustomUser(account.getUsername())));
Debug login confirms that the CustomAuthenticationRequest has been stored in the HTTPSession.
HttpSessionSecurityContextRepository - SecurityContext 'org.springframework.security.core.context.SecurityContextImpl#730db7d8: Authentication: pro.someplace.spring.CustomAuthenticationRequest#730db7d8' stored to HttpSession: 'org.apache.catalina.session.StandardSessionFacade#5da80010
The WebSecurityConfigurerAdapter registers our AuthenticationProvider:
#Override
public void configure(AuthenticationManagerBuilder builder)
throws Exception {
builder.authenticationProvider(new CustomAuthenticationProvider());
}
And establishes what can and cannot be seen by anonymous and authenticated users.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/registration").permitAll()
.anyRequest()
.authenticated();
}
The problem we have is that before the FilterSecurityInterceptor can consult which AuthenticationProvider is appropriate the AnonymousAuthenticationFilter steps in:
o.s.s.w.a.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#4cc1f847: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#fffd148a: RemoteIpAddress: 127.0.0.1; SessionId: 74DB809F1CB5CFB1F977EC20B37B218E; Granted Authorities: ROLE_ANONYMOUS'
If I remove the AnonymousAuthenticationFilter then I cannot access permitAll() in the configuration (different error).
Curiously, I notice this logging message at the end of request processing:
SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
Ok. So the SecurityContextPersistenceFilter should have persisted the context in the HttpSessionSecurityContextRepository.
But when the next request appears the SecurityContextPersistenceFilter has no such object. Was it saved at all? Was it removed?
o.s.security.web.FilterChainProxy - /ordervalidator at position 2 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
o.s.s.w.c.HttpSessionSecurityContextRepository - No HttpSession currently exists
o.s.s.w.c.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: null. A new one will be created.
How can I configure spring to allow authenticated users where I want them and use my CustomAuthenticationProvider when available in the HTTPSession? Where is the security object and why is it not being stored?
And so will be as dumb as it sounds. The front end is React and so we were returning a cookie to the front end however the front end was not being returned to us. As soon as I manually insert cookies into requests I have trapped in either (Postman or Burp) my Context can be found.
This is a cautionary tale as there is a generational difference between the frontend React and the backend (Spring) developers. The backend expect cookies and this is assumed, the front end "...have no idea why we would want them and its a bad idea".

Spring Boot Oauth2 token request - Bad client credentials from BasicAuthenticationFilter

Requesting help on a Spring Boot OAuth2 app's BadCredentialsException when , after user authentication and approval, my Oauth2 client app requests a token from the token endpoint of the OAuth2 AuthServer.
The Spring Boot OAuth2 applications are based off the now famous Dave Syer Spring Boot/ Oauth2 UI Client/Oauth2 AuthServer/JWT example https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/oauth2
This is in the Client Apps' debug:
DEBUG org.springframework.web.client.RestTemplate - Created POST request for "authserver/uaa/oauth/token"
DEBUG org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider - Encoding and sending form: {grant_type=[authorization_code], code=[xxxxx], redirect_uri=[oauthclientapp/login]}
DEBUG org.springframework.web.client.RestTemplate - POST request for "authserver/uaa/oauth/token" resulted in 200 (null)
DEBUG org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter - Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Could not obtain access token
This is the AuthServer's debug:
DEBUG org.springframework.security.web.FilterChainProxy - /oauth/token at position 9 of 15 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
DEBUG org.springframework.security.web.authentication.www.BasicAuthenticationFilter - Basic Authentication Authorization header found for user 'clientID'
DEBUG org.springframework.security.authentication.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
DEBUG org.springframework.security.authentication.dao.DaoAuthenticationProvider - User 'clientID' not found
DEBUG org.springframework.security.web.authentication.www.BasicAuthenticationFilter - Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
The Oauth2 client app is just like the one in the example, no customization in the token request process, just whatever #EnableOAuth2Sso gives us. The ClientDetails config on the AuthServer is also just like the example, sample below, so nothing special.
Any suggestions to better troubleshoot this are much appreciated. Thanks.
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("clientID")
.secret("acmesecret")
.authorizedGrantTypes("authorization_code", "refresh_token",
"password").scopes("openid");
}
I had the same error with your config when sending POST request to /oauth/token endpoint like this:
curl localhost:8080/oauth/token -d grant_type=authorization_code -d client_id=acme -d redirect_uri=http://localhost:4200/redirectUrl -d code=5chf5f
My intention was to use authorization code flow and I didn't want to provide the client_secret parameter which caused the error
-d client_sercret=acmesecret
If your situation is the same you may want to add another client with no secret for authorization code flow:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("clientWithSecret")
.secret("acmesecret")
.authorizedGrantTypes("password")
.scopes("openid")
.and()
.withClient("clientNoSecret")
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("openid");
}

Resources