It would be nice to provide custom serialization mechanism that uses Jackson for objects Spring Security places in session. This would make serializing much faster and ensure consistency when using things like Spring Session. Some of the interfaces (and classes) are: - SecurityContext (SecurityContextImpl) - Authentication (various implementations of it) - UserDetails (User) - CsrfToken (DefaultCsrfToken) - SavedRequest (DefaultSavedRequest)
We might also try to provide compression on the serialization. For example when UserDetails is the principal of an Authentication, we can derive a UsernamePasswordAuthenticationToken from the UserDetails and assume SecurityContextImpl is used.
UPDATE https://github.com/spring-projects/spring-session/pull/434 has must of the support we would need for this feature. We may want to merge those changes into Spring Security
Comment From: jeetmp3
@rwinch as per my understanding, you want put serialized SecurityContext into session. i. e. Instead of storing whole SecurityContext object, a serialized json string will be stored in session. Please correct me if my understanding is wrong.
Comment From: rwinch
@jeetmp3 Thanks for reaching out! For now I would focus on ensuring we can serialize and deserialize any Spring Security objects that go into session with Jackson. This will help enable users to write hooks to serialize Spring Security with JSON. After that we can add hooks in Spring Security that uses the the serialization mechanisms.
Comment From: jeetmp3
@rwinch For now only mix-in classes for Spring Security objects will be added right ?
Comment From: rwinch
@jeetmp3 Correct. Pretty much the work you have already done in the Spring Session Sample you added.
Comment From: jeetmp3
@rwinch let me know when you verified those sample mixins, so that i can start adding those mixins classes into security. PS Is there any specific package/module to add mix-ins?
Comment From: rwinch
Once again thank you for your contribution. I was able to verify the mixins and they look pretty good from my perspective.
- Rather than having DummyServletRequest we should probably modify DefaultSavedRequest to support being built (perhaps using a builder) without the HttpServletRequest object. This should also allow removal of DummyHttpSession.
- Some updates for the tests
* I'd change the tests to be a little more exact. Perhaps you can use something like JSONassert.
* We should have tests that verify the exact output of serializing each of the types. This will ensure that we do not change the way the objects are written
* We should have tests that verify the result of deserializing each objects
* You will need to use assertj to get the checkstyle to passs
Comment From: jeetmp3
@rwinch thanks for the feedback! I'll start working on tests. Other then DefaultSavedRequest there is another class WebAuthenticationDetails which also use HttpServletRequest. I think we should also consider to modify this class too.
Comment From: jeetmp3
@rwinch I've updated all the tests. Should we start merging these mix-ins into spring-security?
Comment From: rwinch
@jeetmp3 Thanks for the response! Yes I think we should try and get a separate Pull Request open for merging the Mixins. Thanks again for all your efforts with this :)
Comment From: jeetmp3
@rwinch thanks for the quick response! Just wants to confirm, should i add these mix-ins in one specific module or in target class specific module ?
Comment From: rwinch
Here are my thoughts, but feedback welcome.
We should add them to the module that the class they serialize belongs to. For example DefaultCsrfTokenMixin would go into spring-security-web because that is where DefaultCsrfToken is.
I would place them within a package named base-package.jackson. For example, DefaultCsrfTokenMixin would be placed in org.springframework.security.web.jackson2 because the base package for spring-security-web is org.springframework.security.web.
Comment From: rwinch
Closed via https://github.com/spring-projects/spring-security/pull/3812#issuecomment-244429416
Comment From: kazuki43zoo
Hi @rwinch ,
I tried this feature on my application.
However, the PreAuthenticatedAuthenticationToken does not support. Can you support classes of the pre authentication feature ?
A following error is occurred when load a security context from a Redis server.
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat Nov 05 23:29:30 JST 2016
There was an unexpected error (type=Internal Server Error, status=500).
Could not read JSON: Can not construct instance of org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?) at [Source: [B@66ab0465; line: 1, column: 196] (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?) at [Source: [B@66ab0465; line: 1, column: 196] (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"])
Log is:
2016-11-05 23:29:30.613 ERROR 17739 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Can not construct instance of org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: [B@66ab0465; line: 1, column: 196] (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: [B@66ab0465; line: 1, column: 196] (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"])
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:119) ~[spring-data-redis-1.7.3.RELEASE.jar:na]
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:98) ~[spring-data-redis-1.7.3.RELEASE.jar:na]
at org.springframework.data.redis.core.AbstractOperations.deserializeHashValue(AbstractOperations.java:335) ~[spring-data-redis-1.7.3.RELEASE.jar:na]
at org.springframework.data.redis.core.AbstractOperations.deserializeHashMap(AbstractOperations.java:279) ~[spring-data-redis-1.7.3.RELEASE.jar:na]
at org.springframework.data.redis.core.DefaultHashOperations.entries(DefaultHashOperations.java:227) ~[spring-data-redis-1.7.3.RELEASE.jar:na]
at org.springframework.data.redis.core.DefaultBoundHashOperations.entries(DefaultBoundHashOperations.java:102) ~[spring-data-redis-1.7.3.RELEASE.jar:na]
at org.springframework.session.data.redis.RedisOperationsSessionRepository.getSession(RedisOperationsSessionRepository.java:432) ~[spring-session-1.2.2.RELEASE.jar:na]
at org.springframework.session.data.redis.RedisOperationsSessionRepository.getSession(RedisOperationsSessionRepository.java:402) ~[spring-session-1.2.2.RELEASE.jar:na]
at org.springframework.session.data.redis.RedisOperationsSessionRepository.getSession(RedisOperationsSessionRepository.java:245) ~[spring-session-1.2.2.RELEASE.jar:na]
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:326) ~[spring-session-1.2.2.RELEASE.jar:na]
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:343) ~[spring-session-1.2.2.RELEASE.jar:na]
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:214) ~[spring-session-1.2.2.RELEASE.jar:na]
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:231) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.springframework.security.web.context.HttpSessionSecurityContextRepository.loadContext(HttpSessionSecurityContextRepository.java:110) ~[spring-security-web-4.2.0.RC1.jar:4.2.0.RC1]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:100) ~[spring-security-web-4.2.0.RC1.jar:4.2.0.RC1]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.0.RC1.jar:4.2.0.RC1]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-4.2.0.RC1.jar:4.2.0.RC1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.0.RC1.jar:4.2.0.RC1]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214) ~[spring-security-web-4.2.0.RC1.jar:4.2.0.RC1]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177) ~[spring-security-web-4.2.0.RC1.jar:4.2.0.RC1]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:164) ~[spring-session-1.2.2.RELEASE.jar:na]
at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80) ~[spring-session-1.2.2.RELEASE.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at com.example.config.SecurityConfig$SsoUsernameStoringFilter.doFilterInternal(SecurityConfig.java:72) ~[classes/:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:108) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:784) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:802) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1410) [tomcat-embed-core-8.5.5.jar:8.5.5]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.5.jar:8.5.5]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_102]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_102]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.5.jar:8.5.5]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_102]
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: [B@66ab0465; line: 1, column: 196] (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:261) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1203) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:314) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:183) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:150) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:129) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:142) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:497) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:101) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:178) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:150) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:129) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromAny(AsPropertyTypeDeserializer.java:174) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:554) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:63) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789) ~[jackson-databind-2.8.3.jar:2.8.3]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2920) ~[jackson-databind-2.8.3.jar:2.8.3]
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:117) ~[spring-data-redis-1.7.3.RELEASE.jar:na]
... 64 common frames omitted
Comment From: rwinch
@kazuki43zoo Thanks for the report! I fixed this in #4120
Comment From: conghairui
Can I modify SpringSecurityCoreVersion.SERIAL_VERSION_UID to make new and old versions compatible