Skip to content
This repository was archived by the owner on Apr 4, 2025. It is now read-only.

Error creating bean 'scopedTarget.accessTokenRequest' when using Spring Security OAuth and JacksonMongoSessionConverter #1

Open
gonzalad opened this issue Jun 1, 2017 · 12 comments

Comments

@gonzalad
Copy link

gonzalad commented Jun 1, 2017

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["scopedTargetoauth2ClientContext"]->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
@gregturn
Copy link
Contributor

gregturn commented Jun 1, 2017

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

@farrault
Copy link

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

@gregturn
Copy link
Contributor

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.

@farrault
Copy link

@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 ... )

@gregturn
Copy link
Contributor

Then you should properly close this issue and open one against against https://github.com/spring-projects/spring-sesssion.

@farrault
Copy link

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 ?

@gregturn
Copy link
Contributor

gregturn commented Aug 2, 2018

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.

@AlexeyPotopakhin
Copy link

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

@gregturn
Copy link
Contributor

gregturn commented Aug 6, 2018

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.

@gregturn
Copy link
Contributor

gregturn commented Aug 6, 2018

Duplicates spring-projects/spring-security#4886

@farrault
Copy link

farrault commented Aug 7, 2018

@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

@gregturn
Copy link
Contributor

gregturn commented Aug 7, 2018

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.

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

No branches or pull requests

4 participants