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 tokenno 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;
}