Spring/Thymeleaf throws "Cannot create a session after the response has been committed" when processing @PostMapping
Asked Answered
D

5

6

I build a Spring MVC application with thymeleaf views and ran into the following problem. I have a page which should process a form and create a new Entity to persist in the database. In my controller class I have two methods for this. First, the @GetMapping to render the page:

@GetMapping("/dispo/orderCreate")
private String showCreateOrder(Model model) {
    List<MdUser> userList = service.getUsers();
    model.addAttribute("userList", userList);

    return "/dispo/orderCreate";
}

As far as I just wanted to show the page without adding some action to the form, everything works fine. The model attribute "userList" is correctly populated with users from the database.

Now I changed the view to add an action and an object to the form. The code of the view now looks like this:

<form action="#" class="form" id="newOrderForm" th:action="@{/dispo/addOrder}" th:object="${loadOrder}" method="post">
<table class="cont-table" cellpadding="2" cellspacing="2" width="100%">
    <tbody>
        <tr align="left">
            <th align="left" valign="top" width="110">Protokollführer:</th>
                <td>
                    <table border="0" cellpadding="0" cellspacing="1" width="100%">
                        <tbody>
                            <tr>
                                <td height="30">
                                    <select class="selectOneMenue" id="newOrderPersoDropDown" th:field="*{supervisor}">
                                        <option>Bitte auswählen</option>
                                        <option th:each="user : ${userList}"
                                                th:value="user.userId"
                                                th:text="${user.firstName}+' '+${user.lastName}"></option>
                                    </select>
                                </td>
                                    . . .
            </tr>
        </tbody>
    </table>
    <br />
    <input style="width:200px" type="submit" value="Speichern" class="commandExButton" id="newOrderSubmit" />
    <input style="width:120px" type="reset" value="Zurücksetzen" class="commandExButton" id="newOrderReset" />
</form>

The corresponding @PostMapping looks like this:

@PostMapping("/dispo/addOrder")
public String submit(@ModelAttribute("loadOrder") LoadOrderModel loadOrder, BindingResult result, Model model) {
    if (result.hasErrors()) {
        return "error";
    }

    service.createAndSaveLoadOrder(loadOrder);
    return "/dispo/success";
}

Now the rendering of the view crashes when the form is reached with the following stacktrace:

Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring5.processor.SpringActionTagProcessor' (template: "/dispo/orderCreate" - line 41, col 58)
at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:117) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1314) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.engine.TemplateHandlerAdapterMarkupHandler.handleOpenElementEnd(TemplateHandlerAdapterMarkupHandler.java:304) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler$InlineMarkupAdapterPreProcessorHandler.handleOpenElementEnd(InlinedOutputExpressionMarkupHandler.java:278) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.standard.inline.OutputExpressionInlinePreProcessorHandler.handleOpenElementEnd(OutputExpressionInlinePreProcessorHandler.java:186) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler.handleOpenElementEnd(InlinedOutputExpressionMarkupHandler.java:124) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.attoparser.HtmlElement.handleOpenElementEnd(HtmlElement.java:109) ~[attoparser-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.attoparser.HtmlMarkupHandler.handleOpenElementEnd(HtmlMarkupHandler.java:297) ~[attoparser-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.attoparser.MarkupEventProcessorHandler.handleOpenElementEnd(MarkupEventProcessorHandler.java:402) ~[attoparser-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.attoparser.ParsingElementMarkupUtil.parseOpenElement(ParsingElementMarkupUtil.java:159) ~[attoparser-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.attoparser.MarkupParser.parseBuffer(MarkupParser.java:710) ~[attoparser-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:301) ~[attoparser-2.0.4.RELEASE.jar:2.0.4.RELEASE]
... 87 common frames omitted
Caused by: java.lang.IllegalStateException: Cannot create a session after the response has been committed
at org.apache.catalina.connector.Request.doGetSession(Request.java:3030) ~[tomcat-embed-core-8.5.34.jar:8.5.34]
at org.apache.catalina.connector.Request.getSession(Request.java:2468) ~[tomcat-embed-core-8.5.34.jar:8.5.34]
at org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:896) ~[tomcat-embed-core-8.5.34.jar:8.5.34]
at org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:908) ~[tomcat-embed-core-8.5.34.jar:8.5.34]
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240) ~[tomcat-embed-core-8.5.34.jar:8.5.34]
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240) ~[tomcat-embed-core-8.5.34.jar:8.5.34]
at org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.saveToken(HttpSessionCsrfTokenRepository.java:63) ~[spring-security-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.saveTokenIfNecessary(LazyCsrfTokenRepository.java:175) ~[spring-security-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken.getToken(LazyCsrfTokenRepository.java:127) ~[spring-security-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor.getExtraHiddenFields(CsrfRequestDataValueProcessor.java:71) ~[spring-security-web-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.thymeleaf.spring5.context.webmvc.SpringWebMvcThymeleafRequestDataValueProcessor.getExtraHiddenFields(SpringWebMvcThymeleafRequestDataValueProcessor.java:80) ~[thymeleaf-spring5-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.spring5.requestdata.RequestDataValueProcessorUtils.getExtraHiddenFields(RequestDataValueProcessorUtils.java:79) ~[thymeleaf-spring5-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.spring5.processor.SpringActionTagProcessor.doProcess(SpringActionTagProcessor.java:118) ~[thymeleaf-spring5-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor.doProcess(AbstractStandardExpressionAttributeTagProcessor.java:142) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
... 100 common frames omitted

Line 41 as indicated by the TemplateProcessingException is the line with the form tag. I have nearly no experience with frontend development so please have patience with me. I think I have to do some http session managing here but don't know what to do and how to do it. Can anyone help me?

Domett answered 25/10, 2018 at 5:57 Comment(2)
What is at the line 41 in your Thymeleaf template?Southerly
Line 41 says: <form action="#" class="form" id="newOrderForm" th:action="@{/dispo/addOrder}" th:object="${loadOrder}" method="post">Domett
D
21

I finally made it. The problem indeed lied in the http session, or to be more precise, in the HttpSecurity. So I added the following to my configure method in my SecurityConfig class:

http.sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)

I still have to find out what SessionCreationPolicy.ALWAYS means for the rest of my application but for now it works :D .

Domett answered 26/10, 2018 at 8:48 Comment(4)
Are you running as Tomcat container or on another server?Nesbitt
I run the application on Tomcat container.Domett
@Domett Can you explain it briefly?Loveinidleness
I'm using Spring Boot 3.1.5 and Keycloak 22.0.5 as IDP with JWT. Using http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS) allows me to use th:action="@{...}" in forms because it adds CSRF token.Nocturnal
B
3

The main reason for this error is that Thymeleaf will output content immediately when produced. When processor reaches form suddenly Spring Security kicks in and wants to generate new session (to inject CSRF token into the form).

Solution is to buffer thymeleaf output - discussion available here https://github.com/thymeleaf/thymeleaf-spring/issues/113

In application properties

spring.thymeleaf.servlet.produce-partial-output-while-processing=false

Then you can still use session ifRequired.

Botanomancy answered 10/4, 2023 at 21:45 Comment(1)
I'm using Spring Boot 3.1.5 and Keycloak 22.0.5 as IDP with JWT. Using spring.thymeleaf.servlet.produce-partial-output-while-processing=false allows me to use th:action="@{...}" in forms because it adds CSRF token. THX!Nocturnal
T
2

I was also having the same issue; applied the suggestion by @Raistlin and its working fine now!!

I had a modal dialog with "form-post" in the lading page. It will fail if I try the modal first. And works fine if I try a GET page first, and come back to this page; definitely something to do with session.

  • always – a session will always be created if one doesn't already exist
  • ifRequired – a session will be created only if required (default)
  • never – the framework will never create a session itself but it will use one if it already exists
  • stateless – no session will be created or used by Spring Security

https://www.baeldung.com/spring-security-session

Thomasinathomasine answered 16/2, 2020 at 14:5 Comment(0)
V
0

In order to use th:object in a form, we must be able to map a new entity to that form. You can send it though an attribute or you can set a method like the one below in your controller and it will do it automatically for you.

@ModelAttribute(value = "loadOrder")
public LoadOrderModel newLoadOrder() {return new LoadOrderModel();}
Vino answered 26/10, 2018 at 0:40 Comment(1)
Thank you too. I also tried your suggestion. But with the same effect as with Hithams tip.Domett
E
0

I ran into the same Problem while upgrading from Spring Boot 1.x to 2.x (which also includes a newer Thymeleaf version). My problem was that Thymeleaf tried to automatically create a hidden csrf input for each html form. The generated csrf tokens needed to be persisted in the http session.

Enabling sessions (like shown by @Raistlin) or disabling csrf protection (http.csrf().disable()) solved the issue for me.

You can also limit csrf token to certain pages using http.csrf().requireCsrfProtectionMatcher(..) or http.csrf().ignoringRequestMatchers(..)

Evasion answered 23/11, 2019 at 21:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.