Search code examples
spring-bootspring-cloud-gatewayspring-oauth2spring-resource-server

Spring Boot Resource Server: "tokenValue cannot be empty" during Error Handling after Successful Authentication


I am building a Spring Boot application using the Backend for Frontend (BFF) pattern. I'm using React for the frontend, Spring Cloud Gateway as the OAuth client, Keycloak as the authorization server, and a Spring Boot project as the OAuth resource server. I am using token relay to send the token to the resource server.

First, is this a good and secure pattern?

Second I am getting this error in resource server

2025-02-13T16:43:00.494+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@20d99e58, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@21011db6, org.springframework.security.web.context.SecurityContextHolderFilter@2e86807a, org.springframework.security.web.header.HeaderWriterFilter@43b5021c, org.springframework.security.web.csrf.CsrfFilter@3451f01d, org.springframework.security.web.authentication.logout.LogoutFilter@49a6f486, org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter@705a8dbc, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@590f0c50, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@3a4ab7f7, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@728d949d, org.springframework.security.web.access.ExceptionTranslationFilter@6bee793f, org.springframework.security.web.access.intercept.AuthorizationFilter@4ecd00b5]] (1/1)
2025-02-13T16:43:00.494+05:30 DEBUG 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Securing GET /
2025-02-13T16:43:00.494+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/12)
...................other filters
2025-02-13T16:43:00.495+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.csrf.CsrfFilter         : Did not protect against CSRF since request did not match And [CsrfNotRequired [TRACE, HEAD, GET, OPTIONS], Not [Or [org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer$BearerTokenRequestMatcher@7cfb4736]]]
2025-02-13T16:43:00.495+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/12)
2025-02-13T16:43:00.495+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2025-02-13T16:43:00.495+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking BearerTokenAuthenticationFilter (7/12)
2025-02-13T16:43:00.510+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.s.authentication.ProviderManager     : Authenticating request with JwtAuthenticationProvider (1/2)
2025-02-13T16:43:00.543+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] s.o.s.r.a.JwtGrantedAuthoritiesConverter : Looking for scopes in claim resource_access.spring-client.roles
2025-02-13T16:43:00.544+05:30 DEBUG 20028 --- [resource-server] [nio-9092-exec-8] o.s.s.o.s.r.a.JwtAuthenticationProvider  : Authenticated token
2025-02-13T16:43:00.544+05:30 DEBUG 20028 --- [resource-server] [nio-9092-exec-8] .s.r.w.a.BearerTokenAuthenticationFilter : Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@e76a89ac, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[]]
2025-02-13T16:43:00.544+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (8/12)
2025-02-13T16:43:00.544+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.s.w.s.HttpSessionRequestCache        : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2025-02-13T16:43:00.544+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (9/12)
2025-02-13T16:43:00.544+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (10/12)
2025-02-13T16:43:00.544+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking ExceptionTranslationFilter (11/12)
2025-02-13T16:43:00.544+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking AuthorizationFilter (12/12)
2025-02-13T16:43:00.544+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] estMatcherDelegatingAuthorizationManager : Authorizing GET /
2025-02-13T16:43:00.544+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] estMatcherDelegatingAuthorizationManager : Checking authorization on GET / using org.springframework.security.authorization.AuthenticatedAuthorizationManager@357287af
2025-02-13T16:43:00.544+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.s.w.a.AnonymousAuthenticationFilter  : Did not set SecurityContextHolder since already authenticated JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@e76a89ac, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[]]
2025-02-13T16:43:00.544+05:30 DEBUG 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Secured GET /
2025-02-13T16:43:00.549+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]
2025-02-13T16:43:00.549+05:30 ERROR 20028 --- [resource-server] [nio-9092-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.jwt.Jwt]: Constructor threw exception] with root cause

java.lang.IllegalArgumentException: tokenValue cannot be empty
    at org.springframework.util.Assert.hasText(Assert.java:240) ~[spring-core-6.1.12.jar:6.1.12]
    at org.springframework.security.oauth2.core.AbstractOAuth2Token.<init>(AbstractOAuth2Token.java:61) ~[spring-security-oauth2-core-6.3.3.jar:6.3.3]
    at org.springframework.security.oauth2.jwt.Jwt.<init>(Jwt.java:67) ~[spring-security-oauth2-jose-6.3.3.jar:6.3.3]
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[na:na]
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) ~[na:na]
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:208) ~[spring-beans-6.1.12.jar:6.1.12]
    at org.springframework.validation.DataBinder.createObject(DataBinder.java:994) ~[spring-context-6.1.12.jar:6.1.12]
    at org.springframework.validation.DataBinder.construct(DataBinder.java:903) ~[spring-context-6.1.12.jar:6.1.12]
    at org.springframework.web.bind.ServletRequestDataBinder.construct(ServletRequestDataBinder.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.constructAttribute(ServletModelAttributeMethodProcessor.java:157) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:148) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:224) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:178) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.28.jar:6.0]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.28.jar:6.0]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter.doFilterInternal(BearerTokenAuthenticationFilter.java:145) ~[spring-security-oauth2-resource-server-6.3.3.jar:6.3.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) ~[spring-security-web-6.3.3.jar:6.3.3]
    at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195) ~[spring-webmvc-6.1.12.jar:6.1.12]
    at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:230) ~[spring-security-config-6.3.3.jar:6.3.3]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268) ~[spring-web-6.1.12.jar:6.1.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.12.jar:6.1.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.12.jar:6.1.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:1
--- other errros

2025-02-13T16:43:00.555+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@20d99e58, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@21011db6, org.springframework.security.web.context.SecurityContextHolderFilter@2e86807a, org.springframework.security.web.header.HeaderWriterFilter@43b5021c, org.springframework.security.web.csrf.CsrfFilter@3451f01d, org.springframework.security.web.authentication.logout.LogoutFilter@49a6f486, org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter@705a8dbc, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@590f0c50, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@3a4ab7f7, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@728d949d, org.springframework.security.web.access.ExceptionTranslationFilter@6bee793f, org.springframework.security.web.access.intercept.AuthorizationFilter@4ecd00b5]] (1/1)
2025-02-13T16:43:00.557+05:30 DEBUG 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Securing GET /error
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (5/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Ant [pattern='/logout', POST]
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking BearerTokenAuthenticationFilter (7/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (8/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.s.w.s.HttpSessionRequestCache        : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (9/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (10/12)
2025-02-13T16:43:00.557+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking ExceptionTranslationFilter (11/12)
2025-02-13T16:43:00.558+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Invoking AuthorizationFilter (12/12)
2025-02-13T16:43:00.558+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] estMatcherDelegatingAuthorizationManager : Authorizing GET /error
2025-02-13T16:43:00.558+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] estMatcherDelegatingAuthorizationManager : Checking authorization on GET /error using org.springframework.security.authorization.AuthenticatedAuthorizationManager@357287af
2025-02-13T16:43:00.558+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2025-02-13T16:43:00.558+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2025-02-13T16:43:00.558+05:30 TRACE 20028 --- [resource-server] [nio-9092-exec-8] o.s.s.w.a.AnonymousAuthenticationFilter  : Did not set SecurityContextHolder since already authenticated JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@e76a89ac, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[]]
2025-02-13T16:43:00.558+05:30 DEBUG 20028 --- [resource-server] [nio-9092-exec-8] o.s.security.web.FilterChainProxy        : Secured GET /error

Here is my OAuth client (9091 port) application.yml:

spring:
  application:
    name: oauth2-client
  security:
    oauth2:
      client:
        registration:
          spring-client:
            client-id: "spring-client"
            client-secret: "dumDmmCxJm0oTH4aUnGpYbocDxLceSOq"
            client-name: "Spring Boot Client 1"
            client-authentication-method: "client_secret_basic"
            authorization-grant-type: "authorization_code"
            provider: "spring-client"
            scope:
              - openid
              - read
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
        provider:
          spring-client:
            issuer-uri: "http://localhost:8080/realms/test"
  cloud:
    gateway:
      routes:
        - id: resource-server
          uri: http://localhost:9092
          predicates:
            - Path=/**
          filters:
            - TokenRelay=
server:
  port: 9091

logging:
  level:
    org.springframework.security: trace

# 8080 - auth server, keycloak
# 9091 - client, gateway
# 9092 - resource server

Here is my resource server (9092 port) code (please forget about code style; this is just a quick POC):

@SpringBootApplication
public class ResourceServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResourceServerApplication.class, args);
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .authorizeHttpRequests(req -> req.anyRequest().authenticated())
                .oauth2ResourceServer(rs -> rs.jwt(
                        jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())
                ))
                .build();
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        grantedAuthoritiesConverter.setAuthoritiesClaimName("resource_access.spring-client.roles");

        JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
        authenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return authenticationConverter;
    }

}

@RestController
class DemoController {

    @GetMapping("/")
    public String demo(Authentication authentication, Jwt jwt) {
        System.out.println("Principal: " + authentication);
        System.out.println("JWT Token: " + jwt.getTokenValue());
        System.out.println("JWT Claims: " + jwt.getClaims());
        return "Hello " + jwt.getSubject();
    }
}

Here is my resource server application.properties:

spring.application.name=resource-server

server.port=9092

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/realms/test

logging.level.org.springframework.security=TRACE

Here is the decoded payload of the JWT token that I obtained from debugging the filter the first time it was called:

{
  "exp": 1739443481,
  "iat": 1739443181,
  "auth_time": 1739442183,
  "jti": "9359c44e-d352-4bf9-93a5-7b80954f418e",
  "iss": "http://localhost:8080/realms/test",
  "aud": "account",
  "sub": "593ca545-528f-4fe9-9fcd-067f5b6e7a51",
  "typ": "Bearer",
  "azp": "spring-client",
  "sid": "13830f59-6cd5-4e7c-b4e7-c5ac35d37cca",
  "acr": "0",
  "allowed-origins": [
    "*"
  ],
  "realm_access": {
    "roles": [
      "default-roles-test",
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "spring-client": {
      "roles": [
        "read",
        "openid"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid profile email",
  "email_verified": false,
  "name": "kush p",
  "preferred_username": "kush",
  "given_name": "kush",
  "family_name": "p",
  "email": "kush@gmail.com"
}

I am hitting port 9091, then being redirected to Keycloak. After login, I am redirected back to 9091 and get a 500 error.

Why is each filter being called twice? I have tried putting a debug pointer at the BearerTokenAuthenticationFilter. The first time, there is a token, and the second time, there is a null token. I think the security context is being lost between calls. Why is this happening?


Solution

  • The error you report is due to Spring ignoring how to inject the Jwt jwt parameter in the handler for @GetMapping("/"). The following implementation will remove this error:

    @RestController
    class DemoController {
    
      @GetMapping("/")
      public String demo(Authentication authentication) {
        if (authentication instanceof final JwtAuthenticationToken jwtAuth) {
          System.out.println("JWT Token: " + jwtAuth.getToken().getTokenValue());
        }
    
        return "Hello " + authentication.getName() + " you are granted with "
            + authentication.getAuthorities();
      }
    }
    

    By the way, this implementation shows that your AuthenticationConverter does not work as expected: JwtAuthenticationConverter does not use JSON-path to interpret the authorities claim name, so your client roles are not mapped.

    The code you provide would work with such a token payload:

    {
      "resource_access.spring-client.roles": ["read", "openid"]
    }
    

    Not with a token issued by Keycloak with client roles mapper as you included in your question:

    {
      "resource_access": {
        "spring-client": {
          "roles": ["read", "openid"]
        }
      }
    }
    

    (Yes, JSON property names can include dots, and the 1st is not equivalent to the 2nd)

    is this (the OAuth2 BFF pattern) a good and secure pattern?

    Yes. As far as I know, this is the safest pattern, and what Google, Facebook, Amazon, Microsoft, LinkedIn, etc. use. Why it is better than configuring Single-Page Applications as public OAuth2 clients is explained in this post from the Spring Security team (4 years old, but still accurate).

    what i expect is my frontend app will store token in http only cookie and my gateway will forward token from this cookie

    Read the documentation for the TokenRelay= filter again, this is not how it works. Tokens are stored in the gateway session and the filter replaces the session cookie with a Bearer token (in session, not in the cookie which contains no more than a session ID, or a new one if the token in session has expired) while routing a request.

    I recommend reading this Baedung article I wrote. You are far from mastering your subject and will have a lot of bad surprises without strong guidance.