Keycloak + Spring Boot + Spring Security does somehow token validation 2 times if the token is invalid

Viewed 2674

i'm using spring security with keycloak and if i do a request on a specific endpoint with an invalid token, it looks like the token validation is done 2 times, i've also tried to implement my own authentication provider which uses the logic of the keycloak authentication provider and overrided the BearerTokenRequestAuthenticator which does the token validation but it still does the same thing..i'm not sure if maybe the problem comes from some sort of bean definitions Here are same logs where you can see that the string "Verifying access_token" appears 2 times.

2021-04-16 16:35:18,220 DEBUG 16672 --- [org.keycloak.adapters.PreAuthActionsHandler]: adminRequest http://localhost:7006/gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.apache.catalina.authenticator.AuthenticatorBase]: Security checking request POST /gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.apache.catalina.realm.RealmBase]:   No applicable constraints defined
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.apache.catalina.authenticator.AuthenticatorBase]: Not subject to any constraint
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve]: AuthenticatedActionsValve.invoke /gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.keycloak.adapters.AuthenticatedActionsHandler]: AuthenticatedActionsValve.invoke http://localhost:7006/gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.keycloak.adapters.AuthenticatedActionsHandler]: Policy enforcement is disabled.
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 1 of 15 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 2 of 15 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 3 of 15 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 4 of 15 in additional filter chain; firing Filter: 'CorsFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 5 of 15 in additional filter chain; firing Filter: 'KeycloakPreAuthActionsFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.PreAuthActionsHandler]: adminRequest http://localhost:7006/gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 6 of 15 in additional filter chain; firing Filter: 'KeycloakAuthenticationProcessingFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: Trying to match using Ant [pattern='/sso/login']
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Checking match of request : '/core/rest/api/core/initPayment/100356'; against '/sso/login'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=Authorization, expectedHeaderValue=null]
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: matched
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Request is to process authentication
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Attempting Keycloak authentication
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Found [1] values in authorization header, selecting the first value for Bearer.
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Verifying access_token
2021-04-16 16:35:18,222 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Failed to verify token
2021-04-16 16:35:18,222 DEBUG 16672 --- [org.keycloak.adapters.RequestAuthenticator]: Bearer FAILED
2021-04-16 16:35:18,222 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Auth outcome: FAILED
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Authentication request failed: org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for details
org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for details
    at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter.attemptAuthentication(KeycloakAuthenticationProcessingFilter.java:162) ~[keycloak-spring-security-adapter-9.0.0.jar:9.0.0]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) ~[keycloak-spring-security-adapter-9.0.0.jar:9.0.0]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:92) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93) ~[spring-boot-actuator-2.3.9.RELEASE.jar:2.3.9.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve.invoke(AbstractAuthenticatedActionsValve.java:67) ~[spring-boot-container-bundle-9.0.0.jar:9.0.0]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve.invoke(AbstractKeycloakAuthenticatorValve.java:181) ~[spring-boot-container-bundle-9.0.0.jar:9.0.0]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Updated SecurityContextHolder to contain null Authentication
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Delegating to authentication failure handler org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler@331b3993
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.springframework.security.web.header.writers.HstsHeaderWriter]: Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@784a6674
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.springframework.security.web.context.SecurityContextPersistenceFilter]: SecurityContextHolder now cleared, as request processing completed
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.apache.catalina.core.ContainerBase.[Tomcat].[localhost]]: Processing ErrorPage[errorCode=0, location=/error]
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 1 of 15 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-04-16 16:35:18,224 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 2 of 15 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 3 of 15 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 4 of 15 in additional filter chain; firing Filter: 'CorsFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 5 of 15 in additional filter chain; firing Filter: 'KeycloakPreAuthActionsFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.PreAuthActionsHandler]: adminRequest http://localhost:7006/gateway/error
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 6 of 15 in additional filter chain; firing Filter: 'KeycloakAuthenticationProcessingFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: Trying to match using Ant [pattern='/sso/login']
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Checking match of request : '/error'; against '/sso/login'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=Authorization, expectedHeaderValue=null]
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: matched
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Request is to process authentication
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Attempting Keycloak authentication
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Found [1] values in authorization header, selecting the first value for Bearer.
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Verifying access_token
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Failed to verify token
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.RequestAuthenticator]: Bearer FAILED
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Auth outcome: FAILED
2021-04-16 16:35:18,226 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Authentication request failed: org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for details
org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for details
    at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter.attemptAuthentication(KeycloakAuthenticationProcessingFilter.java:162) ~[keycloak-spring-security-adapter-9.0.0.jar:9.0.0]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) ~[keycloak-spring-security-adapter-9.0.0.jar:9.0.0]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:710) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:459) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:384) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:398) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:257) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:179) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
2021-04-16 16:35:18,226 DEBUG 16672 ---

Here is my security config class

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    private static final String[] SWAGGER_RESOURCES_WHITELIST = {

            "/authenticate/rest/api/authenticate/v2/api-docs",
            "/core/rest/api/core/v2/api-docs",
            "/v2/api-docs",
            "/swagger-resources",
            "/swagger-resources/**",
            "/configuration/ui",
            "/configuration/security",
            "/swagger-ui.html",
            "/webjars/**",
            "/v3/api-docs/**",
            "/swagger-ui/**",
            "/swagger-ui.html"
    };


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);

        http.cors().and().csrf().disable().sessionManagement().

                sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
                .antMatchers("/authenticate/rest/api/authenticate/token").permitAll()
                .antMatchers("/authenticate/rest/api/authenticate/refreshToken").permitAll()
                .anyRequest().authenticated();

    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return source;
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new NullAuthenticatedSessionStrategy();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        /* remove default spring "ROLE_" prefix appending to keycloak's roles*/
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    // *************************** Avoid Bean redefinition ********************************

    @Bean
    public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
            KeycloakAuthenticationProcessingFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
            KeycloakPreAuthActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
            KeycloakAuthenticatedActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
            KeycloakSecurityContextRequestFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    @Override
    @ConditionalOnMissingBean(HttpSessionManager.class)
    protected HttpSessionManager httpSessionManager() {
        return new HttpSessionManager();
    }
}
2 Answers

You can add JwtAuthorizationTokenFilter and call it before each request

In your security config use addFilterBefore()

// SecurityConfig changes

private final JwtAuthenticationEntryPoint unauthorizedHandler;

private final JwtAuthorizationTokenFilter authenticationTokenFilter;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            // we don't need CSRF because our token is invulnerable
            .csrf().disable()

            .exceptionHandling()
            .defaultAuthenticationEntryPointFor(
                    getRestAuthenticationEntryPoint(),
                    new AntPathRequestMatcher("/**")
            )
            .authenticationEntryPoint(unauthorizedHandler).and()

            // don't create session
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()

            .authorizeRequests()

            // system state endpoint
            .antMatchers("/ping").permitAll()

            // User authentication actions
            .antMatchers("/auth" + "/**").permitAll()
            .antMatchers("/**/*.css").permitAll()

            .anyRequest().authenticated()
    ;

    http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
    ;

    // disable page caching
    http
            .headers()
            .frameOptions().sameOrigin()
            .cacheControl();

}

JwtAuthorizationTokenFilter class

@Component
@AllArgsConstructor
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {

public static final String AUTHORIZATION_HEADER = HttpHeaders.AUTHORIZATION;
public static final String AUTHORIZATION_TOKEN_TYPE = "Bearer";

private final SSOManager ssoManager;
private final SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

    String requestURI = request.getRequestURI();
    boolean isRefreshRequest = ("/auth" + "/refresh").equals(requestURI);

    final String requestHeader = request.getHeader(AUTHORIZATION_HEADER);
    final String authenticationHeader = AUTHORIZATION_TOKEN_TYPE + " ";

    if (requestHeader != null && requestHeader.startsWith(authenticationHeader)) {
        try {
            String authToken = requestHeader.substring(authenticationHeader.length());

            AccessToken accessToken = isRefreshRequest ?
                    ssoManager.loadAccessTokenFromRefreshToken(authToken) :
                    ssoManager.loadAccessToken(authToken);

            if (accessToken != null) {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (authentication instanceof AnonymousAuthenticationToken || authentication == null) {
                    // security context was null, so authorizing user

                    JwtKeycloakAuthenticationToken jwtKeycloakAuthenticationToken = createJwtKeycloakAuthenticationToken(accessToken);
                    SecurityContextHolder.getContext().setAuthentication(jwtKeycloakAuthenticationToken);
                }
            }
        } catch (Exception e) {
            // ToDo: improve error handling
            // the token is expired and not valid anymore, TokenNotActiveException
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
            return;
        }
    }

    chain.doFilter(request, response);
}

public JwtKeycloakAuthenticationToken createJwtKeycloakAuthenticationToken(AccessToken accessToken) {
    String username = accessToken.getPreferredUsername();
    Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();

    UserPrincipal userPrincipal = new UserPrincipal(username);
    Set<GrantedAuthority> authorities = getAuthorities(resourceAccess);

    UserProfileDto userProfile = buildUserProfile(accessToken);
    JwtKeycloakAuthenticationToken jwtKeycloakAuthenticationToken = new JwtKeycloakAuthenticationToken(userPrincipal, authorities, accessToken, userProfile);

    return jwtKeycloakAuthenticationToken;
}

Set<GrantedAuthority> getAuthorities(Map<String, AccessToken.Access> resourceAccess) {
    Set<GrantedAuthority> roles = new HashSet<>();

    resourceAccess.forEach((key, value) -> {
        Set<SimpleGrantedAuthority> realmRoles = value.getRoles().stream()
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toSet());
        roles.addAll(realmRoles);
    });

    Set<GrantedAuthority> grantedAuthorities = simpleAuthorityMapper.mapAuthorities(roles);

    return grantedAuthorities;
}

public UserProfileDto buildUserProfile(AccessToken accessToken) {
    return UserProfileDto.builder()
        .userId(accessToken.getSubject())
        .username(accessToken.getPreferredUsername())
        .fullName(accessToken.getName())
        .firstName(accessToken.getGivenName())
        .lastName(accessToken.getFamilyName())
        .email(accessToken.getEmail())
        .roles(getResourceRoles(accessToken))
        .build();
}

private static Set<String> getResourceRoles(AccessToken accessToken) {
    final String realmClient = accessToken.getIssuedFor();

    final Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();
    if (resourceAccess.containsKey(realmClient)) {
        final AccessToken.Access access = resourceAccess.get(realmClient);
        return access.getRoles();
    }
    return Collections.emptySet();
}

}

Some custom SSOManager interface we used:

public interface SSOManager {

/**
 * This method will verify user access token and provide userId if token is valid. in case of
 * invalid access token it will throw ProjectCommon exception with 401.
 */
AccessToken loadAccessToken(String token) throws TokenNotActiveException, VerificationException, NoSuchFieldException;

AccessToken loadAccessTokenFromRefreshToken(String token) throws TokenNotActiveException, VerificationException, NoSuchFieldException;

/**
 * this method will do the user login with key cloak. after login it will provide access token object.
 */
AccessTokenResponse login(String userName, String password);

AccessTokenResponse refresh(String refreshToken);

void logout(String refreshToken);
}

And KeyCloakServiceImpl implementing SSOManager with all authenticating magic:

@Component
@Slf4j
public class KeyCloakServiceImpl implements SSOManager {

private RestTemplate restTemplate;

private final KeyCloakConnectionProvider keyCloakConnectionProvider;

@Autowired
public KeyCloakServiceImpl(KeyCloakConnectionProvider keyCloakConnectionProvider,
                           RestTemplateBuilder restTemplateBuilder) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {

    this.keyCloakConnectionProvider = keyCloakConnectionProvider;

    TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
    SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
            .loadTrustMaterial(null, acceptingTrustStrategy)
            .build();
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

    CloseableHttpClient httpClient = HttpClients.custom()
            .setSSLSocketFactory(csf)
            .build();

    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);

    this.restTemplate = restTemplateBuilder
            .requestFactory(requestFactory)
            .messageConverters(new MappingJackson2HttpMessageConverter(), new FormHttpMessageConverter())
            .build();
}

private AccessToken getAccessToken(String accessToken, boolean checkActive) throws VerificationException, NoSuchFieldException {
    try {
        PublicKey publicKey = getPublicKey();
        if (publicKey != null) {
            String realmUrl = keyCloakConnectionProvider.getRealmUrl();
            AccessToken token =
                    RSATokenVerifier.verifyToken(
                            accessToken,
                            publicKey,
                            realmUrl,
                            checkActive,
                            true);

            return token;
        } else {
            log.error("KeyCloakServiceImpl:verifyToken: SSO_PUBLIC_KEY is NULL.");
            throw new NoSuchFieldException("KeyCloakServiceImpl:verifyToken: SSO_PUBLIC_KEY is NULL.");
        }
    } catch (TokenNotActiveException e) {
        throw e;
    } catch (VerificationException e) {
        throw e;
    } catch (NoSuchFieldException e) {
        throw e;
    } catch (Exception e) {
        throw e;
    }
}

@Override
public AccessToken loadAccessToken(String accessToken) throws TokenNotActiveException, VerificationException, NoSuchFieldException {
    return getAccessToken(accessToken, true);
}

@Override
public AccessToken loadAccessTokenFromRefreshToken(String accessToken) throws TokenNotActiveException, VerificationException, NoSuchFieldException {
    return getAccessToken(accessToken, false);
}

/**
 * This method will call keycloak service to user login. after successful login it will provide
 * access token.
 */
@Override
public AccessTokenResponse login(String username, String password) {
    try {
        MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
        requestParams.add("client_id", keyCloakConnectionProvider.getResource());
        requestParams.add("username", username);
        requestParams.add("password", password);
        requestParams.add("grant_type", "password");
        requestParams.add("client_secret", keyCloakConnectionProvider.getClientSecret());
        requestParams.add("scope", "openid");


        AccessTokenResponse keycloakAccessToken = queryKeycloakByParams(requestParams);

        return keycloakAccessToken;
    } catch (Exception e) {
        log.info(e.getMessage(), e);
        throw e;
    }
}

@Override
public AccessTokenResponse refresh(String refreshToken) {
    try {
        MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
        requestParams.add("client_id", keyCloakConnectionProvider.getResource());
        requestParams.add("grant_type", "refresh_token");
        requestParams.add("client_secret", keyCloakConnectionProvider.getClientSecret());
        requestParams.add("refresh_token", refreshToken);

        AccessTokenResponse keycloakAccessToken = queryKeycloakByParams(requestParams);

        return keycloakAccessToken;
    } catch (Exception e) {
        log.info(e.getMessage(), e);
        throw e;
    }
}

private AccessTokenResponse queryKeycloakByParams(MultiValueMap<String, String> requestParams) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestParams, headers);

    String url = keyCloakConnectionProvider.getOpenIdConnectTokenUrl();

    AccessTokenResponse keycloakAccessToken = getAccessTokenResponse(request, url);

    return keycloakAccessToken;
}

private AccessTokenResponse getAccessTokenResponse(HttpEntity<MultiValueMap<String, String>> request, String url) {
    try {
        ResponseEntity<AccessTokenResponse> response = restTemplate.postForEntity(url, request, AccessTokenResponse.class);
        return response.getBody();
    } catch (ResourceAccessException e) {
        log.error("KeyCloak getAccessTokenResponse: " + e.getMessage());
        try {
            ResponseEntity<AccessTokenResponse> response = restTemplate.postForEntity(url, request, AccessTokenResponse.class);
            return response.getBody();
        } catch (Exception ex) {
            throw ex;
        }
    } catch (Exception e) {
        throw e;
    }
}

@Override
public void logout(String refreshToken) {
    try {
        MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
        requestParams.add("client_id", keyCloakConnectionProvider.getResource());
        requestParams.add("client_secret", keyCloakConnectionProvider.getClientSecret());
        requestParams.add("refresh_token", refreshToken);

        logoutUserSession(requestParams);

    } catch (Exception e) {
        log.info(e.getMessage(), e);
        throw e;
    }
}

private void logoutUserSession(MultiValueMap<String, String> requestParams) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestParams, headers);

    String url = keyCloakConnectionProvider.getOpenIdConnectLogoutUrl();

    restTemplate.postForEntity(url, request, Object.class);
}

private PublicKey getPublicKey() {
    PublicKey publicKey = keyCloakConnectionProvider.getPublicKey();
    if (publicKey == null) {
        LinkedHashMap publicKeyMap = requestKeyFromKeycloak(keyCloakConnectionProvider.getOpenIdConnectCertsUrl());
        publicKey = KeyCloakRsaKeyLoader.getPublicKeyFromKeyCloak(publicKeyMap);
        keyCloakConnectionProvider.setPublicKey(publicKey);
    }
    return publicKey;
}

/**
 * This method will connect to keycloak server using API call for getting public key.
 *
 * @param url A string value having keycloak base URL
 * @return Public key JSON response string
 */
private LinkedHashMap requestKeyFromKeycloak(String url) {
    try {
        ResponseEntity<LinkedHashMap> response = restTemplate.getForEntity(url, LinkedHashMap.class);
        LinkedHashMap body = response.getBody();

        if (body != null) {
            return body;
        } else {
            log.error("KeyCloakRsaKeyLoader:requestKeyFromKeycloak: Not able to fetch SSO public key from keycloak server");
        }
    } catch (Exception e) {
        log.error("KeyCloakRsaKeyLoader:requestKeyFromKeycloak: Exception occurred with message = " + e.getMessage());
    }
    return null;
}

 }

And KeyCloakConnectionProvider to work with properties

@Component
@Slf4j
@AllArgsConstructor
public class KeyCloakConnectionProvider {

private static PropertiesCache cache = PropertiesCache.getInstance();

private KeycloakSpringBootProperties keycloakProperties;

public String getAuthServerUrl() {
    return keycloakProperties.getAuthServerUrl();
}

public String getRealmUrl() {
    return getAuthServerUrl()
            + "/realms/"
            + getRealm();
}

public String getOpenIdConnectUrl() {
    return getRealmUrl() + "/protocol/openid-connect";
}

public String getOpenIdConnectTokenUrl() {
    return getOpenIdConnectUrl() + "/token";
}

public String getOpenIdConnectLogoutUrl() {
    return getOpenIdConnectUrl() + "/logout";
}

public String getOpenIdConnectCertsUrl() {
    return getOpenIdConnectUrl() + "/certs";
}

public String getRealm() {
    return keycloakProperties.getRealm();
}

public String getResource() {
    return keycloakProperties.getResource();
}

public String getClientId() {
    return getResource();
}

public String getClientSecret() {
    return String.valueOf(keycloakProperties.getCredentials().get("secret"));
}

public int getConnectionPoolSize() {
    return keycloakProperties.getConnectionPoolSize();
}

public PublicKey getPublicKey() {
    return cache.getPublicKey();
}

public PublicKey setPublicKey(PublicKey publicKey) {
    if (publicKey != null) {
        cache.savePublicKey(publicKey);
    }
    return cache.getPublicKey();
}

}

Try to get what you was looking from this files :)

I was able to solve this problem INDIRECTLY. I was looking for a solution to customizing KeyCloak authentication exceptions.

Look at my answer here: Keycloak get 401 error, but spring security does not handle this error

I also noticed that while troubleshooting, an invalid expired token validation was done TWICE.

After I added my CustomKeycloakAuthenticationFailureHandler I realized that invalid tokens were only validated ONCE.

The default KeycloakAuthenticationFailureHandler has a line: response.sendError(401, "Unable to authenticate using the Authorization header");

If this line is executed for some reason you will experience the DOUBLE validation and response header. I removed this line for my solution, and added my own output response.

Related