Thanks a lot in advance for reading this question.
Setup
I am using:
spring-security-oauth2:2.0.7.RELEASE
spring-cloud-security:1.0.1.RELEASE
spring-session:1.0.1.RELEASE
and would have a question regarding the persistence of spring-security-oauth2
OAuth2ClientContext
in a Redis datastore when using spring-session
(via @EnableRedisHttpSession
) in a Single-Sign-On (@EnableOAuth2Sso
), reverse proxy (@EnableZuulProxy
) gateway.
Problem
It seems to me that the SessionScoped
JdkDynamicAopProxied DefaultOAuth2ClientContext
created in org.springframework.cloud.security.oauth2.client.OAuth2ClientAutoConfiguration
is not correctly persisted in the Redis datastore.
@Configuration
@ConditionalOnBean(OAuth2SsoConfiguration.class)
@ConditionalOnWebApplication
protected abstract static class SessionScopedConfiguration extends BaseConfiguration {
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(accessTokenRequest);
}
}
Debugging the creation of the oauth2ClientContext
without @EnableRedisHttpSession
shows that (as expected) the bean will be instantiated once per client session and stored in the HttpSession
. This instance will then be reused to store the fetched OAuth2 bearerToken
details in addition to storing the OAuth2 accessToken
in Spring SecurityContext
's org.springframework.security.core.Authentication
.
However, once using @EnableRedisHttpSession
, the oauth2ClientContext
bean will be first created on the session creation but also later on (while still using the same client session). Debugging the Redis client session content confirms that oauth2ClientContext
is not correctly being persisted by session creation:
Before we retrieve the OAuth2 bearerToken
(NO SpringContext, NO scopedTarget.oauth2ClientContext
):
~$ redis-cli hkeys "spring:session:sessions:17c5e80b-390c-4fd6-b5f9-a6f225dbe8ea"
1) "maxInactiveInterval"
2) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
3) "lastAccessedTime"
4) "creationTime"
5) "sessionAttr:SPRING_SECURITY_SAVED_REQUEST"
After we retrieved the OAuth2 bearerToken
(SpringContext persisted, but NO scopedTarget.oauth2ClientContext
):
~$ redis-cli hkeys "spring:session:sessions:844ca2c4-ef2f-43eb-b867-ca6b88025c8b"
1) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
2) "lastAccessedTime"
3) "creationTime"
4) "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION"
5) "sessionAttr:SPRING_SECURITY_CONTEXT"
6) "maxInactiveInterval"
If we now try to access one of the configurer Zuul
's routes (therefore requiring to call org.springframework.security.oauth2.client.DefaultOAuth2ClientContext#getAccessToken
), another instance of oauth2ClientContext
will be created (since not persisted in Redis, with a null
AccessToken
.
Funnily enough, this instance will later be persisted in Redis (but a null
instance is persisted since the AccessToken
is not re-asked for):
~$ redis-cli hkeys "spring:session:sessions:c7120835-6709-4c03-8d2c-98f830ed6104"
1) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
2) "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION"
3) "sessionAttr:scopedTarget.oauth2ClientContext"
4) "sessionAttr:SPRING_SECURITY_CONTEXT"
5) "maxInactiveInterval"
6) "creationTime"
7) "lastAccessedTime"
8) "sessionAttr:org.springframework.web.context.request.ServletRequestAttributes.DESTRUCTION_CALLBACK.scopedTarget.oauth2ClientContext"
Creating a Simple ScopedProxyMode.TARGET_CLASS
Injected bean worked as expected however with the bean being persisted correctly in Redis.
public class HelloWorldService implements Serializable {
public HelloWorldService(){
System.out.println("HelloWorldService created");
}
private String name = "World";
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public String getHelloMessage() {
return "Hello " + this.name;
}
}
@Configuration
public class AppConfig {
private SecureRandom random = new SecureRandom();
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloWorldService myHelloService(){
HelloWorldService s = new HelloWorldService();
String name = new BigInteger(130, random).toString(32);
System.out.println("name = " + name);
s.setName(name);
System.out.println("Resource HelloWorldService created = " + s);
return s;
}
}
Example
The described problem can be reproduced in @dave-syer example for an OAuth2 reverse proxy gateway by adding the following dependencies:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
as well as the @EnableRedisHttpSession
annotation in UiApplication.
Question
Should we ignore org.springframework.cloud.security.oauth2.client.OAuth2ClientAutoConfiguration
from AutoConfiguration and manually create a oauth2ClientContext
with a different setup to enable spring-session
persistence in Redis? If so, can you please provide an example?
Otherwise: how to persist oauth2ClientContext
in Redis?
Many in advance to anyone reading this question an trying to help.
There's a known issue there (https://github.com/spring-projects/spring-session/issues/129 and https://github.com/spring-projects/spring-boot/issues/2637). You can work around it by adding a
RequestContextFilter
.