Describe the bug When serializing and deserializing pages in the cache store in a upgraded wicket application (upgraded from spring boot 2.7.18 and Wicket 9.15, java 11) using Wicket 10.0.0-M2 with Spring Boot 3.2 (using Spring-security web 6.2.1) and Java 17, Wicket pages with a CSRF token cannot be (de)serialized and give the following exception:

2024-02-06 14:59:32,472 [https-jsse-nio-51363-exec-4] ERROR o.a.w.serialize.java.JavaSerializer:114 - Error serializing object class nl.lostlemon.lll.wicket.art.page.home.Home [object=[Page class = XXXXXXXXX.wicket.art.page.home.Home, id = 0, render count = 1]] org.apache.wicket.core.util.objects.checker.CheckingObjectOutputStream$ObjectCheckException: The object type is not Serializable! A problem occurred while checking object with type: org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler$CachedCsrfTokenSupplier .....

final org.springframework.security.web.csrf.CsrfToken XXXXXXX.wicket.component.form.field.CsRfField$1.val$token [class=org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler$SupplierCsrfToken] private final java.util.function.Supplier org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler$SupplierCsrfToken.csrfTokenSupplier [class=org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler$CachedCsrfTokenSupplier] <----- field that is causing the problem

Is this something that should be fixed on the Wicket side of things or does this class in Spring Security Web need to implement Serializable?

hope this is the correct way to report this problem.

thanks in advance for any advice, if you require additional information please let me know

Tom Benjamins

Comment From: jzheaux

Hi, @TomBenjamins, thanks for the report. I'm not sure that CachedCsrfTokenSupplier should be serializable as I'm imagining that the token would be extracted from the supplier before any session gets serialized.

  1. Can you confirm that the token is getting written as part of the HTTP response?
  2. If so, can you please create and share a minimal sample that reproduces the issue?

Comment From: TomBenjamins

Hello Josh,

i'll look into your question next week. Thanks for your response.

Tom

Comment From: TomBenjamins

Hello Josh,

sorry for the delay, we have made a minimal Wicket/Spring boot Application that reproduces the issue. testproject.zip this zip contains a Wicket 10.0-M2/Spring Boot 3.2/JDK17 Maven project.

The only requirement is that you add --add-opens=java.base/java.io=ALL-UNNAMED to Java VM arguments (this is a bug in wicket 10.0-M2 I guess, but do not know exactly where and how to report that.)

Then run the TestSpringBootApplication main method to boot up the container on port 8080. After that you can run http://localhost:8080 in your browser and the error appears in the console of your IDE. In the browser you can see that the csrf token is written to the html if you view the page source. (requirement 1)

If you have additional questions, please let me know.

Tom

Comment From: jzheaux

@TomBenjamins, thank you for the sample, it was very helpful in getting to the issue quickly.

Because the creation of the token is now deferred, it needs to be read in order to materialize. For example, in CsRfField in the sample, you do:

final ServletRequest request = (ServletRequest) getRequest().getContainerRequest();
final CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());

if (token != null) {
    final IModel<String> tokenModel = new IModel<String>() {
        @Override
        public String getObject() {
            return token.getValue();
        }
    };
    add(AttributeModifier.replace("name", token.getParameterName()));
    add(AttributeModifier.replace("value", tokenModel));
}

While it's not clear from the code, token doesn't yet have a token value and placing it inside of an IModel means that it won't be materialized until after the session it written.

I'm not as familiar with Wicket and whether something should be done to ensure the IModel is resolved before the session is serialized; however, you can address this issue by reading the token outside of the model like so:

final ServletRequest request = (ServletRequest) getRequest().getContainerRequest();
final CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());

if (token != null) {
    String value = token.getValue();
    final IModel<String> tokenModel = new IModel<String>() {
        @Override
        public String getObject() {
            return value;
        }
    };
    add(AttributeModifier.replace("name", token.getParameterName()));
    add(AttributeModifier.replace("value", tokenModel));
}

When I made this change, the program proceeded without error.

At this point, I don't think this should be made serializable. However, if you feel I'm mistaken, or there is more to discuss, please feel free to follow up.