Cannot render error page for request

I want to get data form repository to controller and send them into view so, I want to send data from controller to view using Hashmap(K,V). But value of Hashmap(V) is List, I got error lik...

I want to get data form repository to controller and send them into view so, I want to send data from controller to view using Hashmap(K,V). But value of Hashmap(V) is List<>, I got error like that…

Stack trace

Cannot render error page for request [/meetzen/] and exception [An error happened during template parsing (template: «class path resource [templates/post.jsp]»)] as the response has already been committed. As a result, the response may have the wrong status code.

Query

@Query(nativeQuery = true, value="SELECT  * FROM request_master WHERE sender_id = ? AND status = ?")
List<Request> getAcceptRequestFrnd(Integer Sender_id, String Status);
    
@Query(nativeQuery = true, value="SELECT  rsm.receiver_id,pm.profile, rgm.username, upm.post_id, upm.post, upm.date FROM registration_master AS rgm INNER JOIN profile_master AS pm ON rgm.u_id = pm.user_id INNER JOIN uploadpost_master AS upm ON rgm.u_id = upm.user_id INNER JOIN request_master AS rsm ON rgm.u_id = rsm.receiver_id WHERE rsm.receiver_id = ? ORDER BY upm.date DESC LIMIT 1")
List<ProfileJoin> getPostWithAccount(Integer Receiver_id);

Controller

@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Model mdl, HttpSession session, Request request) {
    Integer sessionId = Integer.parseInt(session.getAttribute("uid").toString());

    Map<Integer, List<ProfileJoin>> userUploadPost = new HashMap<>();

    List<Request> getUser = 
    this.service.getAcceptRequestFrnd(sessionId, "Accept");
    for(Request getUserForPost : getUser)
    {
        List<ProfileJoin> getUserWithPost = this.service.getPostWithAccount(getUserForPost.getReceiver_id()); 
        userUploadPost.put(getUserForPost.getReceiver_id(), getUserWithPost);
    }

    mdl.addAttribute("userWithPost", userUploadPost);
    mdl.addAttribute("getUser", getUser);

    return "index";
}

Thymeleaf:

<div th:each="getData: ${getUserWithPost.value}">
  <span th:text="${getData.username}"></span>
  <span th:text="${getData.post}"></span>
</div>

I want to read data from services to controler and display on the UI on Spring boot, but it gives the below error

ERROR 20984 — [nio-8080-exec-2] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/countries] as the response has already been committed. As a result, the response may have the wrong status code.

The database has also the below data

mysql> select * from country;

+----+-------------+------+---------------+---------------+-------------+
| id | capital     | code | continent     | description   | nationality |
+----+-------------+------+---------------+---------------+-------------+
|  1 | Washington  | 01   | North America | United States | American    |
|  2 | Addis Ababa | 01   | Africa        | Ethiopia      | Ethiopian   |
+----+-------------+------+---------------+---------------+-------------+ 

Model

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Country {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String code;
    private String capital;
    private String description;
    private String nationality;
    private String continent;
    
    @OneToMany(mappedBy="country")
    private List<State> states;
}

Repository

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.kindsonthegenius.FleetApp.models.Country;

@Repository
public interface CountryRepository extends JpaRepository<Country, Integer> {

}

Service

  @Service
public class CountryService {

    @Autowired
    private CountryRepository countryRepository;
    
    public List<Country> getCountries(){
        return countryRepository.findAll();
    }
    
}

Controller

@Controller
public class CountryController {
@Autowired
private CountryService countryService;

@GetMapping("/countries")
public String goCountry(Model model) {
    List<Country> countryList = countryService.getCountries(); 
    model.addAttribute("countries", countryList);
    return "country";
}
}

UI

<table class="table">
            <thead>
              <tr>
                <th scope="col">id</th>
                <th scope="col">Description</th>
                <th scope="col">Capital</th> 
              </tr>
            </thead>
            <tbody> 
              <tr th:each="country : ${countries}">
                <td th:text="${country.id}"></td>
                <td th:text="${country.description}"></td>
                <td th:text="${country.capital}"></td> 
              </tr>
            </tbody>
       </table>

Please help me to fix this error?

Comments

@tankilo

@wilkinsona
wilkinsona

changed the title
ErrorPageFilter cause the request to non-existent resource returned 200 instead of 404

ErrorPageFilter causes a forwarded request that sends an error to actually send a 200 OK response instead

Jan 29, 2018

wilkinsona

added a commit
to wilkinsona/spring-boot
that referenced
this issue

Apr 9, 2018

@wilkinsona

Previously, the error page filter used sendError to set the response
status when handling an exception and before forwarding the request
to the error controller. Following the fix for spring-projectsgh-11814, this meant
that the error controller was unable to write its response and the
containers default error page was returned instead.

This commit updates the error page filter to use setStatus rather than
sendError. This ensures that the response has the correct status code
while allowing the error controller to write its body. Tests have
been added to the Tomcat deployment test suite to verify that the
error page filter behaves as intended when dealing with a sent error
and an exception for requests accepting HTML, JSON, or anything.

Closes spring-projectsgh-12787

в настоящее время используюSpring Boot Появится если есть ошибкаWhitelabel Error Page Пейдж, этоSpring Boot Страница, которая обрабатывает ошибки по умолчанию, создается в жестко запрограммированной форме. Мы можем заменить тональный сигнал, использовать нашу собственную страницу ошибки и украсить ее.

В Интернете есть много похожих статей, но многие из них являются неполными, а некоторые ошибочными (например, настройкиserver.error.whitelabel.enabled=falseдобавить вerror.html, Это предпосылка), сегодня мы анализируем это с точки зрения исходного кода и даем решение.

Первый выборSpring Boot Если возникает ошибка, например:500503404И т.п.org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController Мы также можем переписать его (если вы переписываете его, вы будете удивлены, обнаружив, что следующий контент является избыточным, ха-ха) для реализации вашей собственной логики;

Как вы можете видеть на картинкеSpring Boot Я думаю, очень хорошо, как синхронная, так и асинхронная обработка, здесь мы обсуждаем только проблему синхронизации, потому что будет происходить только синхронизацияWhitelabel Error Page

Мы находим код, который анализирует представление

ModelAndView modelAndView = resolveErrorView(request, response, status, model);

resolveErrorView Родительский классAbstractErrorControllersдостигать

ErrorViewResolver Реализация по умолчаниюDefaultErrorViewResolver

ИзresolveВ методе мы можем разделить несколько шагов
1. Найдите доступного поставщика шаблонизатора

Если ошибка500 Тогда на этот разerrorViewName="error/500"

  • Найти логикуTemplateAvailabilityProvidersкатегорияgetProviderМетод, этот метод будет кэшировать существующий поставщик шаблонизатора, в частностиfindProviderЧтобы найти, еслиfindProviderЯ не нашел его и зарегистрировал бы по умолчаниюNoTemplateAvailabilityProvider

  • Вниманиеthis.providers это Spring Boot Он будет добавлен при запуске контейнера. Будет ли добавлен поставщик механизма шаблонов, определяется наличием имени класса механизма шаблонов.Spring Boot Зарегистрируем несколько поставщиков шаблонных движков;

  • Загрузить провайдер шаблонного движка

loadFactories Загрузка естьspring-boot-autoconfigure-2.xxxx.jarНижеMETA-INF/spring.factoriesЭтот метод может получить все классы реализации родительского класса.

  • ВотThymeleafTemplateAvailabilityProviderНапример

    Из рисунка видно, что вы ищетеclasspath:/templates/error/500.html
    Так что если оно появится500Мы можем ошибатьсяsrc/main/resources/ Новый каталогtemplates/errorСоздать новыйthymeleafиз500.htmlШаблон достаточно;404сопереживание

  • Отсюда нетрудно понять, что если вы хотите успешно найти неправильный шаблон, вы должны соответствовать следующим условиям

  1. Путь к классу существует в шаблонизатореjar
  2. classpathСуществует текущий шаблон ошибки под(Пример: 500.html)

2. Если на шаге 1providerЕсли вы найдете его, вам не нужно переходить к следующему шагу, вы можете напрямую вернуться к просмотру

3. Если на шаге 1 не выполнены условияproviderТогда сдайresolveResourceРучка, давайте посмотримresolveResourceлогика

Этот шаг в основном предназначен для непосредственного получения статикиhtml;Общее500с участием404Вы можете напрямую использовать статическийHTMLРесурс может отображать дружественную подсказку, поэтому здесь вы можете получить статический файл ресурса напрямую
Местоположение поиска по умолчанию следующее

Может быть изменено по конфигурации

Если ошибка500, Результат поиска показан на рисунке, нам нужно только создать файл ресурса для доступа<staticLocations>/error/5xx.html

4. Что если вышеуказанные три шага не выполнены? ИзBasicErrorController Вы можете увидеть, что будетerrorВид по умолчанию, вы найдетеclasspath:/templates/error.html, Логика обработки такая же, как указано выше, если она все еще не может быть найденаSpring Boot По умолчанию будетErrorMvcAutoConfigurationсреднийStaticView Оказывать

Предпосылкаserver.error.whitelabel.enabled=true

private static class StaticView implements View {

    private static final Log logger = LogFactory.getLog(StaticView.class);

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        if (response.isCommitted()) {
            String message = getMessage(model);
            logger.error(message);
            return;
        }
        StringBuilder builder = new StringBuilder();
        Date timestamp = (Date) model.get("timestamp");
        Object message = model.get("message");
        Object trace = model.get("trace");
        if (response.getContentType() == null) {
            response.setContentType(getContentType());
        }
        builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
                "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
                .append("<div id='created'>").append(timestamp).append("</div>")
                .append("<div>There was an unexpected error (type=")
                .append(htmlEscape(model.get("error"))).append(", status=")
                .append(htmlEscape(model.get("status"))).append(").</div>");
        if (message != null) {
            builder.append("<div>").append(htmlEscape(message)).append("</div>");
        }
        if (trace != null) {
            builder.append("<div style='white-space:pre-wrap;'>")
                    .append(htmlEscape(trace)).append("</div>");
        }
        builder.append("</body></html>");
        response.getWriter().append(builder.toString());
    }

    private String htmlEscape(Object input) {
        return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
    }

    private String getMessage(Map<String, ?> model) {
        Object path = model.get("path");
        String message = "Cannot render error page for request [" + path + "]";
        if (model.get("message") != null) {
            message += " and exception [" + model.get("message") + "]";
        }
        message += " as the response has already been committed.";
        message += " As a result, the response may have the wrong status code.";
        return message;
    }

    @Override
    public String getContentType() {
        return "text/html";
    }

}

Это Whitelabel Error Page источник

В итоге, есть следующие решения

Если в проекте используется шаблонизатор, напримерthymeleaf freemarker
в это времяserver.error.whitelabel.enabledВыключить и включить

  1. вclasspathНовый шаблон/templates/error/5xx.html /templates/error/4xx.html
  2. вclasspathСоздать новый статический ресурс/<staticlocation>/error/5xx.html /<staticlocation>/error/4xx.html
  3. вclasspathНапрямую создайте новый шаблон/templates/error.html

Если в проекте нет шаблонного движка

  1. Настроитьserver.error.whitelabel.enabled=trueИспользуйте встроенныйviewRender, появитьсяWhitelabel Error Page
  2. Еслиserver.error.whitelabel.enabled=falseТам не будет никакой информации

Если у вас есть какие-либо вопросы или ошибки, вы можете оставить сообщение
Код не прост, пожалуйста, укажите источник

Обратите внимание на публичную учетную запись WX, вы не можете пропустить это

common.ftl

<#macro page>


    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Twit</title>
        <link rel="stylesheet" href="/static/style.css"/>

        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

        <!-- Bootstrap CSS -->
        <#-- если не подключен интернет-->
        <link rel="stylesheet" href="static/bootstrap/css/bootstrap.min.css"/>

        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
              integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
              crossorigin="anonymous">


    </head>
    <body>

    <#include "navbar.ftl">

    <#--Данная секция будет принимать некоторый вложенный код.-->
    <div class="container mt-5">
        <#nested>
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->

    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
            integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
            crossorigin="anonymous"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
            integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
            crossorigin="anonymous"></script>

    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
            integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
            crossorigin="anonymous"></script>

    </body>
    </html>

</#macro>

main.ftl

<#import "parts/common.ftl" as c>

<@c.page>
<#-- форма поиска-->
    <div class="form-row">
        <div class="form-group col-md-6">
            <form method="get" action="/main" class="form-inline">

                <input type="text" name="filter" class="form-control"
                       value="${filter?ifExists}"
                       placeholder="Search by tag">

                <#--ml-2 - оnступ слева на 2-->
                <button type="submit" class="btn btn-primary ml-2">Search</button>

            </form>
        </div>
    </div>

<#--Скрытие или показ формы-->
    <a class="btn btn-primary" data-toggle="collapse" href="#collapseform">
        Add new message
    </a>

<#--id="collapseExample" - по данному id будет доступна анимация (сркрытиепоказ)-->
<#--ввод сообщения-->
<#-- (textError??) - если в поле есть ошибка, тогда получим true и полученное значение
            нужно привести к строке
            ?string - приводим к строке значение, которое содержится в (textError??)
            'is-invalid' - если ошибка есть, тогда добавляем в данный атрибут class, еще один
            селектор, по которому bootstap будет знать, как оформить сообщение
            '' - селектор не добавляется, если ошибки нет
           #value = ...if message??-- Если пользователь ввел сообщение и у нас есть ошибка,
          чтобы сообщение не потерялось, мы его сохраняем в поле-->
    <div id="collapseform" class="collapse">
        <div class="form-group">
            <form method="post" enctype="multipart/form-data" >

                <div class="form-group col-3 mt-3">
                    <input type="text"
                           class="form-control ${(textError??)?string('is-invalid', '')}"
                           name="text"
                           value="<#if message??>
                                      ${message.text}
                                  </#if>
                                  "
                           placeholder="Введите сообщение"/>

                    <#-- Валидация поля - ввод сообщения-->
                    <#if textError??>
                        <div class="invalid-feedback">
                            ${textError}
                        </div>
                    </#if>

                </div>


                <div class="form-group col-3">
                    <input type="text" class="form-control" name="tag" placeholder="Tag">
                </div>
                <div class="form-group col-3">
                    <div class="custom-file">
                        <input type="file" name="file" class="custom-file-input" id="customFile">
                        <label class="custom-file-label" for="customFile">Choose file</label>
                    </div>
                </div>

                <input type="hidden" name="_csrf" value="${_csrf.token}"/>
                <button type="submit" class="btn btn-primary">Добавить</button>
            </form>
        </div>
    </div>


    <div>Список сообщений</div>
    <div class="card-columns">
        <#-- mu-3 - отступ слева и снизу-->
        <div class="card my-3">
            <#list messages as message>
            <!--?? - указывает, что нужно привести данное выражени к булевому типу
        - Те есть провекра указывает на то, если получим значение поля filename у объекта message
      занчит это true иначе false-->
            <#if message.filename??>

                <img style="width: 150px" src="/static/img/${message.filename}" class="card-img-top">

            </#if>

            <#--m-2 - оступ со всех 4-х сторон-->
            <div class="m-2">
                <span>${message.text}</span>
                <i>${message.tag}</i>
            </div>


            <div class="card-footer text-muted">
                ${message.authorName}
            </div>

        </div>
    <#else >
        No message
        </#list>
    </div>
    </div>

</@c.page>

вот в это месте появляется ошибка

<div>Список сообщений</div>
    <div class="card-columns">
        <#-- mu-3 - отступ слева и снизу-->
        <div class="card my-3">
            <#list messages as message>
            <!--?? - указывает, что нужно привести данное выражени к булевому типу
        - Те есть провекра указывает на то, если получим значение поля filename у объекта message
      занчит это true иначе false-->
            <#if message.filename??>

                <img style="width: 150px" src="/static/img/${message.filename}" class="card-img-top">

            </#if>

 <#list messages as message>

вот контроллер

@PostMapping("/main")
    public String add(
            @AuthenticationPrincipal User user,
            @Valid Message message,
            BindingResult bindingResult,
            Model model,
             @RequestParam("file") MultipartFile file
    ) {

         message.setAuthor(user);

        if (bindingResult.hasErrors()) {

            Map<String, String> errorsMap = getListValidationErr(bindingResult);

            model.mergeAttributes(errorsMap);
            model.addAttribute("message", message);

        } else {
            prepareFileForSave(file, message);
            saveImgFileAndMessageToBase(message, model);
        }

          return "main";
    }

При попытке обработки ошибки в контроллере, когда добваляю сообщение, появляется вот такая ошибка

2019-10-21 18:06:55.312 ERROR 14524 — [nio-8080-exec-5] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page
for request [/main] and exception [The following has evaluated to null
or missing:
==> messages [in template «main.ftl» at line 80, column 20]

—- Tip: If the failing expression is known to legally refer to something that’s sometimes null or missing, either specify a default
value like myOptionalVar!myDefault, or use <#if
myOptionalVar??>when-present<#else>when-missing. (These only
cover the last step of the expression; to cover the whole expression,

use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??

—- FTL stack trace («~» means nesting-related):
— Failed at: #list-#else-container [in template «main.ftl» at line 80, column 13] ~ Reached through: #nested [in template
«parts/common.ftl» in macro «page» at line 27, column 9] ~ Reached
through: @c.page [in template «main.ftl» at line 3, column 1]
—-] as the response has already been committed. As a result, the response may have the wrong status code.

Если кто знает, подскажите в чем дело.

Решение

<#if messages??> <#--Проверка - есть ли список сообщений или нет, если список пустой, тогда ничего не рисуется-->
            <#list messages as message>
            <#if message.filename??>
                <img style="width: 150px" src="/static/img/${message.filename}" class="card-img-top">
            </#if>

            <#--m-2 - оступ со всех 4-х сторон-->
            <div class="m-2">
                <span>${message.text}</span>
                <i>${message.tag}</i>
            </div>
            <div class="card-footer text-muted">
                ${message.authorName}
            </div>
        </div>
    <#else >
        No message
        </#list>
            </#if>

нужно было добавить проверку, приходит ли на страницу переменная, которая содержить спискок элементов, если переменная null, тогда таблица для списка не рисуется.

Problem:

Product.java

package com.example.springdemo.model.entity;

import lombok.Data;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table
@Data
public class Product {

@Id
@GeneratedValue
private Long id;
private String name;

@ManyToOne(fetch = FetchType.LAZY)
private Product parent;

@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Product> children = new ArrayList<>();

}

Table of Product:

product_cycle

Error Logs:

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
...
java.lang.StackOverflowError: null
...
ERROR 9816 --- [nio-8080-exec-5] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/rest/product/list] and exception 
[Could not write JSON: Infinite recursion (StackOverflowError); 
nested exception is com.fasterxml.jackson.databind.JsonMappingException: 
Infinite recursion (StackOverflowError) 
...

Solution 1 – @JsonIdentityInfo – Example Project:

Project Structure

structure0044

ProductController.java

package com.example.springdemo.controller;

import com.example.springdemo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(path = "/rest/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @RequestMapping(path = "/list", method = RequestMethod.GET)
    public ResponseEntity getProducts() {
        return ResponseEntity.ok(productService.getProducts());
    }

}

ProductRepository.java

package com.example.springdemo.dao;

import com.example.springdemo.model.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {

    @Query("select distinct p from Product p " +
            "left join fetch p.parent pp " +
            "left join fetch p.children ch " +
            "where pp is null")
    List<Product> findAll();
}

ProductService.java

package com.example.springdemo.service;

import com.example.springdemo.dao.ProductRepository;
import com.example.springdemo.model.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    public List<Product> getProducts() {
        return productRepository.findAll();
    }
}

Product.java

package com.example.springdemo.model.entity;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.Data;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table
@Data
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Product {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Product parent;

    @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
    private List<Product> children = new ArrayList<>();

}

SpringdemoApplication.java

package com.example.springdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringdemoApplication.class, args);
    }

}

application.properties

spring.datasource.url=jdbc:postgresql://localhost:5432/demo
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.hibernate.ddl-auto=update

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

spring.jackson.serialization.indent-output=true
spring.jackson.default-property-inclusion=non_empty

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springdemo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <vaadin.version>14.0.10</vaadin.version>
        <spring.version>5.2.0</spring.version>
    </properties>

    <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Solution 2 – @JsonIgnore

public class Product {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @JsonIgnore
    // or: @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @ManyToOne(fetch = FetchType.LAZY)
    private Product parent;

    @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
    private List<Product> children = new ArrayList<>();

}

Output:

json_output1


У меня есть панель навигации с LOGIN , PROFILE и LOGOUT . Моя цель — удалить ЛОГИН и показать только ПРОФИЛЬ и ВЫХОД. после того, как пользователь вошел в систему. У меня есть контроллер, проверяющий аутентифицированных пользователей:

@Controller
public class LoginController {

    @GetMapping("/login")
    private String loginRender(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || authentication instanceof AnonymousAuthenticationToken){
            return "login";
        }else {
            return "redirect:/";
        }
    }
}

Шаблон Thymeleaf выглядит так:

<ul class="logout-ul top-links-container">
    <li class="top-links-item  text-center" sec:authorize="!isAuthenticated()"><a href="/login"> <i class="icon-user4" style="color: #e35f5f"></i>Login</a></li>
    <li class="top-links-item text-center" sec:authorize="isAuthenticated()"><a href="/user/profile"><i class="icon-user4" style="color: #e35f5f"></i>Profile</a></li>
    <li class="text-center logout" sec:authorize="isAuthenticated()"><form th:action="@{/logout}" th:method="post"><button title="LOGOUT" class="btn" type="submit"><i class="icon-off"></i></button></form></li>
</ul>

Конфигурация:

 http
            .authorizeRequests()
            .antMatchers("/login", "/", "register",
                    "/custom-transfers/**", "/about-us", "/information/**", "/support/**").permitAll()
            .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
            .antMatchers("/admin").hasRole(UserRoleEnum.ADMIN.name())
            .antMatchers("/**").authenticated()
          .and()
                .formLogin()
                .loginPage("/login")
                .usernameParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY)
                .passwordParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY)
                .defaultSuccessUrl("/user/profile")
                //TODO validation page
                .failureForwardUrl("/login")
          .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID");

ПРОБЛЕМА Все это работает, когда пользователь впервые входит в систему. При выходе из системы и повторном входе в систему Thymleaf показывает исключение TemplateInputException.

Исключение:

ERROR 26304 --- [nio-8080-exec-9] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/login] and exception [An error happened during template parsing (template: "class path resource [templates/login.html]")] as the response has already been committed. As a result, the response may have the wrong status code.

org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/login.html]")
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:241) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parseStandalone(AbstractMarkupTemplateParser.java:100) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
    at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:666) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
    at org.thymeleaf.spring5.view.ThymeleafView.renderFragment(ThymeleafView.java:366) ~[thymeleaf-spring5-3.0.12.RELEASE.jar:3.0.12.RELEASE]
    at org.thymeleaf.spring5.view.ThymeleafView.render(ThymeleafView.java:190) ~[thymeleaf-spring5-3.0.12.RELEASE.jar:3.0.12.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1400) ~[spring-webmvc-5.3.10.jar:5.3.10]
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1145) ~[spring-webmvc-5.3.10.jar:5.3.10]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1084) ~[spring-webmvc-5.3.10.jar:5.3.10]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.10.jar:5.3.10]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.10.jar:5.3.10]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.10.jar:5.3.10]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[tomcat-embed-core-9.0.53.jar:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.10.jar:5.3.10]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.53.jar:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:121) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115) ~[spring-security-web-5.5.2.jar:5.5.2]

Я попытался разместить sec: authorize = «! IsAuthenticated ()» снаружи в <div>, попытался передать boolean в шаблон с RedirectAttribute и Model, но я все еще не могу решить проблему. Это работает, когда я добавляю: <li class="top-links-item" sec:authorize="!isAuthenticated()">TEST</li> поэтому я предполагаю, что это потому, что я использую sec:authorize для страницы входа, которая управляется Spring Security. Может быть какая-то связь?

ОБНОВЛЕНИЕ . Я обнаружил, что после выхода из системы cookie уничтожается и не создает новый для второго входа в систему. Также проблема заключается только в форме выхода из системы в html, когда я добавляю sec:authorize=isAuthenticated(). Поэтому проблема связана с выходом из системы, а не со страницей входа. Почему это могло произойти?

Это происходит только тогда, когда я использую isAuthenticated() в Thyemleaf.

Что я делаю неправильно? Буду очень признателен за вашу помощь. Спасибо.

1 ответ

Лучший ответ

Я думаю, что здесь происходит следующее:

Ваша форма выхода управляется Thymeleaf, что означает, что под капотом Thymeleaf попытается создать скрытое поле с токеном CSRF внутри. Обычно это работает, и для этого требуется существующий или новый сеанс.

Проблема в том, что Thymeleaf разработан для минимизации задержек, создавая выходные данные шаблона по мере обработки вашего шаблона. Таким образом, когда страница становится длиннее по размеру (как в вашем случае) к тому времени, когда движок достигает вашего выхода из системы, <form> помечает, что часть вывода МОЖЕТ БЫТЬ передана клиенту и сервер начинает считать ваш ответ подтвержденным . Как только ответ будет помечен как совершенный, Thymeleaf не сможет получить доступ / создать сеанс. Я предполагаю, что это архитектурное ограничение.

Так что я не уверен, что именно вам здесь посоветовать. Возможно, попробуйте вывести кнопку выхода раньше в ваших шаблонах или, при желании, попытаться инициализировать токен CSRF. Этот параметр в HttpSecurity, возможно, сделает это:

.and()
   .csrf()
   .csrfTokenRepository(new HttpSessionCsrfTokenRepository())


1

Lachezar Balev
12 Ноя 2021 в 16:37

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Cannot recover after last error any further errors will be ignored перевод
  • Cannot receive hello packet miflash ошибка edl
  • Cannot receive hello packet miflash как исправить
  • Cannot read property url of undefined ошибка wink как исправить
  • Cannot read property match of undefined как исправить

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии