Skip to content
This repository has been archived by the owner on Apr 5, 2022. It is now read-only.

AccessTokenContextRelay does not renew expired access tokens #223

Open
HJK181 opened this issue Oct 25, 2019 · 11 comments
Open

AccessTokenContextRelay does not renew expired access tokens #223

HJK181 opened this issue Oct 25, 2019 · 11 comments

Comments

@HJK181
Copy link

HJK181 commented Oct 25, 2019

Spring cloud version: Greenwich.SR3

My UAA service is also an oauth2 client, which needs to relay JWT tokens coming in from Zuul. When configuring the oauth2 client the following way

@Configuration
@EnableOAuth2Client
@RibbonClient(name = "downstream")
public class OAuthClientConfiguration {

	@Bean
	public OAuth2RestTemplate restTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
		return new OAuth2RestTemplate(resource, context);
	}
}

I do get a 401 response from the downstream service as my access token has a very short validity and the AccessTokenContextRelay#copyToken() which get's executed by the autowired ResourceServerTokenRelayRegistrationAutoConfiguration drops the validity and refresh token information.

This leads to the following behavior:

  • Zuul does renew expired access tokens by calling OAuth2RestTemplate#getAccessToken
  • The UAA service also call OAuth2RestTemplate#getAccessToken but, as the access token populated by AccessTokenContextRelay in the OAuth2ClientContext does always return false for accessToken.isExpired(), the token is neither renewed nor does AccessTokenContextRelay refresh updated incoming access tokens. Instead the expired token is used again and again.
@ryanjbaxter
Copy link
Contributor

Can you provide a complete, minimal, verifiable sample that reproduces the problem? It should be available as a GitHub (or similar) project or attached to this issue as a zip file.

@HJK181
Copy link
Author

HJK181 commented Oct 29, 2019

Hi @ryanjbaxter

I'm trying to prepare the sample application but I'm struggling with the token relay configuration of the uaa service. According to the documentation https://cloud.spring.io/spring-cloud-security/reference/html/#_resource_server_token_relay all I need to do for token relay to work without UserInfoTokenServices is to add @EnableOAuth2Client . However, my application does not start in this case because of

Parameter 0 of method restTemplate in com.auth.config.OAuthClientConfiguration required a bean of type 'org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails' that could not be found.

The following candidates were found but could not be injected:
	- Bean method 'oauth2RemoteResource' in 'OAuth2RestOperationsConfiguration.SingletonScopedConfiguration' not loaded because AnyNestedCondition 0 matched 2 did not; NestedCondition on OAuth2RestOperationsConfiguration.ClientCredentialsCondition.NoWebApplication @ConditionalOnWebApplication found 'session' scope and did not find reactive web application classes; NestedCondition on OAuth2RestOperationsConfiguration.ClientCredentialsCondition.ClientCredentialsConfigured @ConditionalOnProperty (security.oauth2.client.grant-type=client_credentials) did not find property 'grant-type'


Action:

Consider revisiting the entries above or defining a bean of type 'org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails' in your configuration.

My client configuration looks as follows:

@Service
public class Resource2Client {

	private final OAuth2RestOperations	restTemplate;

	@Autowired
	public Resource2Client(final OAuth2RestOperations restTemplate,
			final DiscoveryClient discoveryClient) {
		this.restTemplate = restTemplate;
	}

	public String callDownstream() {
		String getResp = restTemplate.getForObject("http://localhost:8086/reza", String.class);
		return getResp;
	}
}

My OAuth2RestTemplate creation:

@Configuration
@EnableOAuth2Client
public class OAuthClientConfiguration {

	@Bean
	public OAuth2RestTemplate restTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
		return new OAuth2RestTemplate(resource, context);
	}
}

In order to fix this, I need to add the following to my uaa service:

security:
  oauth2:
    client:
      client-id: uaa-service
      client-secret: secret
      accessTokenUri: http://localhost:9191/uaa/oauth/token
      userAuthorizationUri: http://localhost:9191/uaa/oauth/authorize
      grant-type: client_credentials

Any idea what I'm doing wrong? Or am I just misunderstanding the whole idea and my uaa-service must be an client in order to be able to refresh an expired token? But than how can I refresh the token for a user which logged in via authorization_code and user name and password on the API gateway in with an client_credentials on the token relay?

Here you can find the current sample state: https://github.com/HJK181/spring-oauth2
User credentials:
name: user
password: password

The downstream call I'm trying to test is: http://localhost:8080/uaa/downstream where I expect the token to be relayed from Zuul trough uaa down to resource2.

@HJK181
Copy link
Author

HJK181 commented Oct 30, 2019

Updated the sample. Found that I need to add /downstream to the antMatchers of the uaa-service's ResourceServerConfig in order to activate OAuth2AuthenticationProcessingFilter for that request, which pulls the Authentication object from the token. With this in place I can remove accessTokenUri, client-secret and userAuthorizationUri from the applicatiuon.yml.

The behavior is now as follows:
Requests to http://localhost:8080/uaa/downstream do work until the access token expires. If the token is expired I get an Whitelabel Error Page because of

2019-10-30 10:06:45.474 ERROR 36580 --- [nio-9191-exec-4] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/uaa] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause

java.lang.NullPointerException: null
	at java.lang.StringBuilder.<init>(StringBuilder.java:112) ~[na:1.8.0_151]
	at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.getAccessTokenUri(OAuth2AccessTokenSupport.java:162) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:137) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider.obtainAccessToken(ClientCredentialsAccessTokenProvider.java:44) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:148) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:121) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:731) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.security.oauth2.client.OAuth2RestTemplate.doExecute(OAuth2RestTemplate.java:143) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:311) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at com.auth.client.Resource2Client.callDownstream(Resource2Client.java:20) ~[classes/:na]
	at com.auth.controller.PrincipleRestController.downstream(PrincipleRestController.java:28) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_151]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_151]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_151]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_151]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:88) ~[spring-boot-actuator-2.1.8.RELEASE.jar:2.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176) ~[spring-security-oauth2-2.3.6.RELEASE.jar:na]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:77) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:114) ~[spring-boot-actuator-2.1.8.RELEASE.jar:2.1.8.RELEASE]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:104) ~[spring-boot-actuator-2.1.8.RELEASE.jar:2.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_151]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_151]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.24.jar:9.0.24]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_151]

The subsequent request than returns an valid response until the token expires again, which will throw the NPE and so on ...

The NPE is caused by the fact that I need to remove accessTokenUri so that the refresh token of the existing access token is used and the oauth2 client of the uaa service does not authenticate itself via it's client credentials, as this will result in missing authorities in the fetched access token!

@HJK181
Copy link
Author

HJK181 commented Oct 30, 2019

Nailed my initial problem down to @HystrixCommand which was used for my method where I use the OAuth2RestTemplate to call the downstream service. With Hystrix in place a call to the downstream service with an expired token results in a 401 which than results in a HttpRetryException: cannot retry due to server authentication, in streaming mode which can't be handled by OAuth2ErrorHandler#handleError. Thus no OAuth2Exception is thrown by the error handler and the OAuth2RestTemplate#doExecute method never clears the access token to obtain a new on in this block

if (accessToken != null && retryBadAccessTokens) {
			context.setAccessToken(null);
			try {
				return super.doExecute(url, method, requestCallback, responseExtractor);
			}
			catch (InvalidTokenException e) {
				// Don't reveal the token value in case it is logged
				rethrow = new OAuth2AccessDeniedException("Invalid token for client=" + getClientId());
			}
		}

To fix this, I need to declare another http client, e.g. from Apache. Than token renewal with Hystrix does work. However I'M still facing the NPE from my last comment!

@ryanjbaxter
Copy link
Contributor

The NPE looks like a problem in Spring Security, it does not look a problem with Spring Cloud Security (from what I can see in the stack trace). I suggest you open an issue there.

@HJK181
Copy link
Author

HJK181 commented Nov 4, 2019

That's a really disappointing answer/reaction after one week of waiting and a lot of time invested in providing you a minimal example and detail information whilst debugging the problem! I clearly pointed out several things which are not working here:

  1. You can't use Hystrix in conjunction with @EnableOAuth2Client as expired access tokens will cause an HttpRetryException which can't be handled by OAuth2ErrorHandler. Instead of adding some entries in the Spring documentation, I would have expected that a different http client get's auto configured in that scenario as this is a pretty common setup which will fail for everybody.
  2. The token relay functionality does not make any sense as you need to configure your Oauth2 client in order to make the relay auto configured! But, you do not even want a client to be configured, the relay should to what it's supposed to do, relay the available access token to the downstream service! The task of renewing the token is thing of the service passing the token downwards to the relay. The NPE is just the cause of the miss configuration needed in order to get the relay working!

In general I'm really disappointed about the community support. You always ask the for sample applications, which you get in most cases, whilst tickets get prematurely closed without any help. If I point you to a bug I would expect you to open a ticket in other modules of the Spring ecosystem as it should be your interest of providing qualitative software to the community.

@dsyer @spencergibb any constructive feedback on this?

@dsyer dsyer reopened this Nov 4, 2019
@dsyer
Copy link
Contributor

dsyer commented Nov 4, 2019

Looks like Ryan misread the NPE - there's a lot to grok, so don't blame him. If you have something working with apache http, then it's not really urgent, right (finding it hard myself to take in all the details)?

@HJK181
Copy link
Author

HJK181 commented Nov 4, 2019

Thanks for reopening @dsyer.

Yes, appache http is somehow working. And I can totally live with the fact that I need to configure a different http client as I'm now aware of that HttpRetryException. But I guess other will fall into the same trap, so there might be room for improvement.

However, even with the different http client, there's still the problem that as soon as the token relay get's passed an expired access token, it tries to use the refresh token to renew it, which will cause the NPE, thus leading to every X request to fail. I don't know how this should be solved as for the API-Gateway the access token might be valid at the time the request arrives but until it reaches the the relay that token might expire. Thus the token relay needs to use the refresh token to obtain a new access token but without authenticating himself via it's grant-type configuration.

@dsyer
Copy link
Contributor

dsyer commented Nov 4, 2019

IMO the relaying party should pass the 401 back to upstream (i.e. never try to attempt to renew the token). I'm not sure that can be automated, but if you have any suggestions, please go ahead.

@HJK181
Copy link
Author

HJK181 commented Nov 4, 2019

This sounds reasonable for me. IF the upstream service is Zuul or Spring-Cloud-Gateway would that 401 be handled by renewing the access token and retrying that failed request? With Ribbon and Hystrix in place, this should be true, right? https://cloud.spring.io/spring-cloud-netflix/multi/multi_retrying-failed-requests.html#retrying-failed-requests

@dsyer
Copy link
Contributor

dsyer commented Nov 4, 2019

It depends on the HTTP client (not on whether it is using Zuul or SCG). But I think a well-behaved OAuth2 client should renew a refresh token if it knows about it. The Spring Security ones do try to IIRC.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

No branches or pull requests

4 participants