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?
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.