Error creating bean 'scopedTarget.accessTokenRequest' when using Spring Security OAuth and JacksonMongoSessionConverter
I'm using
- Spring Session Mongo 1.3.1.RELEASE
- Spring Security OAuth 2.0.13.RELEASE
I have the issue only with JacksonMongoSessionConverter, not with JdkMongoSessionConverter.
I use the following Spring Session config :
@EnableMongoHttpSession
public class HttpSessionConfig {
@Bean
public AbstractMongoSessionConverter mongoSessionConverter() {
return new JacksonMongoSessionConverter();
}
}
For OAuth setup it's a Spring Boot OAuth2 Client setup similar to https://spring.io/guides/tutorials/spring-boot-oauth2/#_social_login_simple (adapted for my own OAuth Authorization Server)
When accessing the main page of my app I get a :
java.lang.IllegalStateException: Cannot convert MongoExpiringSession
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Error creating bean with name 'scopedTarget.accessTokenRequest': Scope 'request' is not active for the current thread...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.accessTokenRequest': Scope 'request' is not active for the current thread
Caused by: java.lang.IllegalStateException: No thread-bound request found
I thinks this issue is due because Spring Security OAuth relies on requestScoped or sessionScoped bean, and SessionRepositoryFilter is executed outside of RequestContextFilter, and JacksonMongoSessionConverter tries to access a session scoped bean but RequestContextHolder..currentRequestAttributes isn't anymore available.
Complete stack:
2017-06-01 15:14:56.513 WARN [iam-swagger-gateway,,,] 26065 --- [tp2027963364-91] org.eclipse.jetty.server.HttpChannel : /swagger/login
java.lang.IllegalStateException: Cannot convert MongoExpiringSession
at org.springframework.session.data.mongo.JacksonMongoSessionConverter.convert(JacksonMongoSessionConverter.java:92) ~[spring-session-1.3.1.RELEASE.jar:na]
at org.springframework.session.data.mongo.AbstractMongoSessionConverter.convert(AbstractMongoSessionConverter.java:110) ~[spring-session-1.3.1.RELEASE.jar:na]
at org.springframework.session.data.mongo.MongoOperationsSessionRepository.convertToDBObject(MongoOperationsSessionRepository.java:141) ~[spring-session-1.3.1.RELEASE.jar:na]
at org.springframework.session.data.mongo.MongoOperationsSessionRepository.save(MongoOperationsSessionRepository.java:77) ~[spring-session-1.3.1.RELEASE.jar:na]
at org.springframework.session.data.mongo.MongoOperationsSessionRepository.save(MongoOperationsSessionRepository.java:44) ~[spring-session-1.3.1.RELEASE.jar:na]
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:245) ~[spring-session-1.3.1.RELEASE.jar:na]
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:217) ~[spring-session-1.3.1.RELEASE.jar:na]
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:170) ~[spring-session-1.3.1.RELEASE.jar:na]
at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80) ~[spring-session-1.3.1.RELEASE.jar:na]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) ~[jetty-servlet-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.springframework.cloud.sleuth.instrument.web.TraceFilter.doFilter(TraceFilter.java:145) ~[spring-cloud-sleuth-core-1.1.3.RELEASE.jar:1.1.3.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) ~[jetty-servlet-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) ~[jetty-servlet-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) ~[spring-boot-actuator-1.5.2.RELEASE.jar:1.5.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) ~[jetty-servlet-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:541) ~[jetty-servlet-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548) ~[jetty-security-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:190) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1592) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1239) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:481) ~[jetty-servlet-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1561) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1141) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.Server.handle(Server.java:564) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:320) ~[jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251) [jetty-server-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279) [jetty-io-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:110) [jetty-io-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124) [jetty-io-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:672) [jetty-util-9.4.2.v20170220.jar:9.4.2.v20170220]
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:590) [jetty-util-9.4.2.v20170220.jar:9.4.2.v20170220]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_101]
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Error creating bean with name 'scopedTarget.accessTokenRequest': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. (through reference chain: org.springframework.session.data.mongo.MongoExpiringSession["attrs"]->java.util.LinkedHashMap["scopedTargetoauth2ClientContext"]->org.springframework.security.oauth2.client.DefaultOAuth2ClientContext["accessTokenRequest"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:388) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:348) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:343) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:698) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:633) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:536) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:30) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3681) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3057) ~[jackson-databind-2.8.7.jar:2.8.7]
at org.springframework.session.data.mongo.JacksonMongoSessionConverter.convert(JacksonMongoSessionConverter.java:87) ~[spring-session-1.3.1.RELEASE.jar:na]
... 41 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.accessTokenRequest': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:355) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:192) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at com.sun.proxy.$Proxy116.isEmpty(Unknown Source) ~[na:na]
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:516) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:30) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704) ~[jackson-databind-2.8.7.jar:2.8.7]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690) ~[jackson-databind-2.8.7.jar:2.8.7]
... 52 common frames omitted
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41) ~[spring-web-4.3.7.RELEASE.jar:4.3.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
... 60 common frames omitted
@rwinch @dsyer Is this a MongoDB-specific issue, a Spring Session Commons issue, a Spring Security OAuth issue, or a Spring issue? I want to properly understand the cause before engineering some solution.
Hello, I hit the same issue. While I'm also trying to use MongoDb, I don't think this is a MongoDB-specific issue.
Spring Session : 1.3.2.RELEASE Spring Data Mongo : 1.10.1.RELEASE Spring Boot : 1.5.2.RELEASE Spring Security OAuth 2.0.13.RELEASE
Here is my understanding of the problem :
- this is a collaboration issue between :
- org.springframework.session.web.http.SessionRepositoryFilter that manage Spring Session
- org.springframework.web.filter.RequestContextFilter that manage the request and session scope of Spring Framework
- the SessionRepositoryFilter is configured with a high order, so it is executed first when a request arrives and wraps the request and the response
- the RequestContextFilter is configured with a lower order (as it should to correctly use the wrapped request)
- in my case, I'm using Spring Boot that explicitly place this filter after the Spring Session Filter via org.springframework.boot.web.filter.OrderedRequestContextFilter
- the problem is that when the request returns :
- RequestContextFilter is the first to execute and clears the RequestContextHolder which "closes" the spring "request" scope
- when SessionRepositoryFilter try to persist the last bit of the session data, the request scope is closed but the OAuth2ClientContext stored in session as a field referencing the bean accessTokenRequest which is request scope => the request scope being closed, it fails ...
Per http://projects.spring.io/spring-session-data-mongodb/, Spring Session MongoDB currently supported versions only includes 2.0, which requires Spring Boot 2.0 as well. As for OAuth support, you'll have to check with the Spring Security team on covering that piece.
@gregturn Indeed. That why I used Spring Session 1.3.2.RELEASE (which include Mongo supports) ( And it seems that @gonzalad was also using a 1.x version of Spring Session )
Please note, that the problem we hit, is not specific to OAuth support. Any standard Spring "request" scoped bean referenced by a session attribute, will show the same behavior. ( That being said, maybe that only Spring Security OAuth2 does this ... )
Then you should properly close this issue and open one against against https://github.com/spring-projects/spring-sesssion.
I'm very sorry @gonzalad, I didn't read carefully your first comment : indeed declaring a JdkMongoSessionConverter does allow the persistence of the session in mongo and you already explained the RequestContextFilter versus SessionRepositoryFilter potential conflict
After some thinking, it appears clear to me now, that the fact that the RequestContextFilter closes the scope before the SessionRepositoryFilter store the session is not the real problem (and is not a problem at all). Because in fact we don't want to store the value behind OAuth2ClientContext.accessTokenRequest, we just want to store the fact that OAuth2ClientContext.accessTokenRequest is a proxy to a request scoped bean (and this target request scoped bean only need to be determined when the field is accessed) (and this is possible even after the scope is closed)
JdkMongoSessionConverter using java serialization AND the spring framework allowing the serialisation of BeanFactory based proxies (because DefaultListableBeanFactory is serialized only by id) => it does exactly that and it works
JacksonMongoSessionConverter is just unable to correctly identified the special case of a proxy as it only serialize object's fields
@gregturn : so it appears that this limitation is indeed linked to the Spring Session Mongo specific implementation : JacksonMongoSessionConverter Fixing this is would probably required a custom Jackson serialisation/deserialisation of OAuth2ClientContext and I don't know if it is necessary/needed. (And agreed, that in this case, it's probably a Spring Security OAuth2 issue)
Moreover, the documentation does state that JacksonMongoSessionConverter doesn't store correctly some Spring Security objects "We explicitly configure JdkMongoSessionConverter since Spring Security’s objects cannot be automatically persisted using Jackson (the default if Jackson is on the classpath)" But it doesn't described which.
This affects Spring Session 1.x at least. The code of Spring Session Mongo 2.x seems to add extra support for some Spring Security beans in org.springframework.session.data.mongo.JacksonMongoSessionConverter.buildObjectMapper() (but it seems not documented ) (Side note : so JacksonMongoSessionConverter does need Spring Security now in the classpath to work : is that normal ?)
I think that for this issue :
- we only need some additional documentation on the differents limitations or constraints of JacksonMongoSessionConverter (referenced scoped beans is one such limitation but I suspect there is more as classic spring security beans need mixin) and maybe the necessity to create custom jackson Mixins if particular beans are put in the session.
- And maybe an enhanced exception, which propose to switch to JdkMongoSessionConverter if jackson serialization fails ?
Spring Security has instituted "white listing" to insulate from Jackson serializing/deserializing unacceptable types and hacking the security paradigm.
Spring Session MongoDB leverages this when using it's Jackson-based solution.
If you are able to update your project to Boot 2.0, then we can continue working on where the root of this issue is. Otherwise, you may have to settle for MongoDB not being a session option with Boot 1.x.
@gregturn for example OAuth2Authentication class has no default constructor. So Jackson can't serialize/deserialize that instance from the MongoSession in JacksonMongoSessionConverter. And yes, we have to add additional mixin for the OAuth2Authentication to the ObjectMapper as it defined for MongoSession and HashMap.
Issue sounds twofold:
- need to make OAuth2Authentication supported by Jackson
- need to have OAuth2Authentication whitelisted as well
Both of these require coordination with the Spring Security team.
Duplicates https://github.com/spring-projects/spring-security/issues/4886
@gregturn, just to be more precise about your previous comment for others users :
Otherwise, you may have to settle for MongoDB not being a session option with Boot 1.x.
MongoDB is sill an option in Spring Security OAuth2 / Boot 1.x context, but users do need to configure a JdkMongoSessionConverter
Spring Session MongoDB 2.0 only supports Spring Boot 2.0+.
Version 1.3 was discontinued, the version that works with Boot 1.5.
Any other configuration is not supported.