Неправильный доступ к URL-адресам, когда пользователь вошел в систему. Ошибка: нет статического ресурса. Spring работает неправильно

Вошедший в систему пользователь может получить доступ к любому несуществующему маршруту, получив Error: There is no static resource

У меня есть две проблемы с конфигурацией безопасности Springboot, когда я вхожу в систему.

1 — URL-доступ:

1.1 - Если я не авторизован

- Если я введу какую-либо страницу, которая не существует, безопасность Springboot правильно перенаправит вас на страницу 404.html. "response.sendRedirect("/404.html");"

1.2 - Если я вошел в систему

- Если я зайду на какую-либо страницу, я смогу получить к ней доступ без загрузки страницы 404.html со следующим сообщением.

{"data":null,"status":"404 NOT_FOUND","timestamp":"2024-07-18T08:24:38.2990731","server":"MyServer","error":"Error: No static resource insssssdex.html."}

Такое поведение неверно, оно должно перенаправляться на страницу 404.html.

1.3 - Если я вошел в систему, на любой некорректной странице будет та же ошибка, что и в 1.2. Так быть не должно, должно работать как в 1.1.

2 - LogOut, поскольку предыдущий не выполняется, он просто переходит по маршруту /logout и ничего не происходит, так как Spring ничего не определяет. Поэтому как будто при обращении к нему пружина перестает управлять страницами. работает как в 1.2

@Configuration
@EnableWebSecurity
public class SecurityConfig {
...
 @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((requests) -> requests
                        .requestMatchers(
                                "/register", "/login",
                                "/register.html", "/login.html",
                                "/*.js", "/*.json",
                                "/css/**", "/js/**", "/images/**",
                                "/scss/**", "/vendor/**",
                                "/api/**", "/api/userControllerRest/**",
                                "/*.ico",
                                "/404.html",
                                "/logout")
                        .permitAll()
                        .anyRequest().authenticated())
                .formLogin((form) -> form
                        .loginPage("/login.html")
                        .defaultSuccessUrl("/index.html", true)
                        .permitAll())
                .logout((logout) -> logout
                        .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                        .logoutUrl("/logout")
                        .logoutSuccessUrl("/login.html")
                        .invalidateHttpSession(true)
                        .deleteCookies("JSESSIONID")
                        .permitAll())
                .csrf(csrf -> csrf
                        .ignoringRequestMatchers("/api/game/**")
                        .csrfTokenRepository(new HttpSessionCsrfTokenRepository()))
                .exceptionHandling(handling -> handling
                        // .defaultAuthenticationEntryPointFor(
                        //         (request, response, authException) -> {
                        //             response.sendRedirect("/404.html");
                        //         },
                        //         new AntPathRequestMatcher("/**"))
                        .authenticationEntryPoint((request, response, authException) -> {
                            response.sendRedirect("/404.html");
                        })
                        .accessDeniedHandler((request, response, accessDeniedException) -> {
                            response.sendRedirect("/404.html");
                        }))
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
        // .requiresChannel(channel -> channel
        // .anyRequest().requiresSecure()); // Redirige HTTP a HTTPS

        ;
        return http.build();
    }

Обработчик глобального исключения

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<CustomResponse> handleNotFound(NoHandlerFoundException ex) {
        CustomResponse response = new CustomResponse(
                null,
                "Error: " + ex.getMessage(),
                "404 NOT_FOUND",
                LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME),
                "MyServer");

        return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<CustomResponse> handleAllExceptions(Exception ex, WebRequest request) {
        CustomResponse errorResponse = new CustomResponse(
                null,
                "Error: " + ex.getMessage(),

                getHttpStatusFromException(ex).toString(),
                LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME),
                "MyServer");

        return new ResponseEntity<>(errorResponse, getHttpStatusFromException(ex));
    }

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<CustomResponse> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
        CustomResponse errorResponse = new CustomResponse(
                null,
                "Error: " + ex.getMessage(),
                HttpStatus.NOT_FOUND.toString(),
                LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME),
                "MyServer");
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }

    private HttpStatus getHttpStatusFromException(Exception ex) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        if (ex instanceof NoHandlerFoundException) {
            status = HttpStatus.NOT_FOUND;
        } else if (ex instanceof UserNotFoundException) {
            status = HttpStatus.NOT_FOUND;

        } else if (ex instanceof NoResourceFoundException) { 
            status = HttpStatus.NOT_FOUND;
        }
        return status;
    }
}

Моя кнопка: TemplateButtonLogOut.html.

<a class = "dropdown-item" href = "/logout">
    <i class = "fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
    Logout
</a>

logOutButton.js

document.addEventListener("DOMContentLoaded", function () {
  fetch("/template/TemplateButtonLogOut.html")
    .then((response) => response.text())
    .then((data) => {
      document
        .querySelectorAll(".logout-button-container")
        .forEach((container) => {
          container.innerHTML = data;
        });
    });
});

внедрить HTML

<div class = "logout-button-container"></div>

По какой-то причине, когда я нажимаю кнопку выхода из системы, загружается точка 1.2, что неверно, и кажется, что когда точка 1.2 может быть загружена, она не выходит из системы правильно с сообщением:

{"data":null,"status":"404 NOT_FOUND","timestamp":"2024-07-18T08:32:03.8629218","server":"MyServer","error":"Error: No static resource logout."}

Как будто прерывистый маршрут его не обнаружил. Я не знаю, выполняю ли я слишком много проверок или оставил что-то в «ExceptionHandling» о том, когда он вошел в систему, я не знаю.

Случай 1. Перенаправление безопасности Spring Boot успешно выполнено.

Случай 2. Когда кнопка нажата, выйдите из системы без необходимости настраивать ее вручную, поскольку об этом позаботится безопасность Spring. Если случай 1 будет решен, то и случай 2, я думаю, тоже будет решен. Поэтому все ложится на случай 1

Хотя я понимаю, что в вашем приложении может возникнуть ряд проблем, пожалуйста, задавайте по одной проблеме на каждый вопрос, иначе это может сбить с толку. Пожалуйста, отредактируйте свой вопрос, включив в него один ясный вопрос.

J Asgarov 18.07.2024 08:40

Как показано здесь, вам нужно обработать ResourceNotFoundException исключение для 404, но я не уверен, что это сработает, если вы намеренно возвращаете несуществующий html из метода контроллера.

J Asgarov 18.07.2024 08:43

Либо используйте logoutRequestMatcher, либо logoutUrl, но не оба. А еще /logout это корень, вы уверены, что это http://localhost:8080/logout? Если по какой-то причине вы используете подпуть, вам необходимо его включить.

M. Deinum 18.07.2024 08:44

Спасибо за ответ. Проблема 1 приводит к проблеме 2, и вы можете видеть, что, если неправильно «контролировать» перенаправление на 404.html, функция выхода из системы отключается, поскольку она не может «контролировать» маршруты. Случай 2 показывает, как из-за проблемы с неправильно управляемыми маршрутами он перестает работать.

Drakgoku 18.07.2024 08:46

Добавьте журналы Spring Security TRACE к своему вопросу.

dur 18.07.2024 21:38
0
5
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Благодаря комментарию и точности Дж. Аскарова: «Я не уверен, что это сработает, если вы намеренно возвращаете несуществующий HTML-код из метода контроллера». Если вы вернете ResponseEntity, он всегда вернет страницу JSON. Я думал, что, передав код HttpStatus, Spring позаботится о его перенаправлении как таковом, если обнаружит «resource.html», но на самом деле он обрабатывает запрос как полный ResponseEntity.

Я понимаю, что когда вы перенаправляетесь на представление, Spring интерпретирует его как json вместо автоматического перенаправления, и Spring не будет знать, что именно делать, и вернет то, что ему передано, следовательно, ResponseEntity независимо от кода. Результатом будет страница с ошибкой в ​​формате json вместо перенаправления на страницу 404.html.

В этом случае я просто помещаю такое перенаправление в «void», так как хочу, чтобы это обрабатывалось Spring:

    @ExceptionHandler(Exception.class)
    public void handleAllExceptions(Exception ex, HttpServletResponse request) throws IOException {
        if (getHttpStatusFromException(ex) == HttpStatus.NOT_FOUND) {
            request.sendRedirect("/404.html");
            // DEBUGG / email
            throw new RuntimeException(ex);
        } else {
            request.sendRedirect("/500.html");
            throw new RuntimeException(ex);
        }
    }

A — Неавторизованные пользователи Настройки безопасности: доступ к определенным URL-адресам разрешен без аутентификации (/register, /login и т. д.). Обработка ошибок: если неаутентифицированный пользователь пытается получить доступ к несуществующему URL-адресу, он перенаправляется на /errorPage. Обработчик ошибок: обработчик handleError перенаправляет на разные страницы ошибок (/404.html, /500.html, /genericError.html) на основе кода состояния HTTP.

  
                // LOGOUT
                .logout((logout) -> logout
                        // .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                        // .logoutUrl("/logout")
                        .logoutUrl("/logout")
                        // .logoutSuccessUrl("/login.html")
                        .logoutSuccessUrl("/login.html")
                        .invalidateHttpSession(true)
                        .deleteCookies("JSESSIONID")
                        .permitAll())
                // El CSRF (del inglés Cross-site request forgery o falsificación de petición en
                // sitios cruzados)
                .csrf(csrf -> csrf
                        // AQUÍ PONER LOS ENDPOINTS DEL JUEGO - Deshabilitar CSRF para rutas del
                        .ignoringRequestMatchers("/api/game/**")
                        // SEGURIDAD WEB
                        .csrfTokenRepository(new HttpSessionCsrfTokenRepository()))
                // HANDLING DE USUARIOS NO AUTENTICADOS
                .exceptionHandling(handling -> handling
                        .authenticationEntryPoint((request, response, authException) -> {
                            // response.sendRedirect("/404.html");
                            response.sendRedirect("/errorPage");
                        })
                        .accessDeniedHandler((request, response, accessDeniedException) -> {
                            // response.sendRedirect("/404.html");
                            response.sendRedirect("/errorPage");
                        }))

    }
@Controller
public class CustomErrorController implements ErrorController {
    @RequestMapping("/errorPage")
    public String handleError(HttpServletRequest request) {
        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);

        if (status != null) {
            Integer statusCode = Integer.valueOf(status.toString());

            if (statusCode == HttpStatus.NOT_FOUND.value()) {
                return "forward:/404.html";
            } else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
                return "forward:/500.html";
            }
        }
        return "forward:/genericError.html";
    }

}
  • Помните, что если вы используете шаблон или другую форму перенаправления на фронтенд, вам придется адаптировать его под свои нужды.
  • Помните, что сейчас я использую чистый JavaScript. Поэтому, если вы используете другой способ передачи данных из серверной части во внешний интерфейс, например шаблон (freemaker (лучший, наименее навязчивый), Thymeleaf...), вам придется изменить представления «login.html». -> «вход» или если вы используете Rest для конечной точки «/login»…

B – Вошедшие пользователи Обработка исключений. Если прошедший проверку подлинности пользователь пытается получить доступ к несуществующему URL-адресу, метод handleAllExceptions перенаправляет на /404.html или /500.html в зависимости от типа исключения.


@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public void handleAllExceptions(Exception ex, HttpServletResponse request) throws IOException {
        if (getHttpStatusFromException(ex) == HttpStatus.NOT_FOUND) {
            request.sendRedirect("/404.html"); // or /errorPage
            // DEBUGG / email
            throw new RuntimeException(ex);
        } else {
            request.sendRedirect("/500.html");
            throw new RuntimeException(ex);
        }
    }
    

    // USER
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<CustomResponse> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
      ...
    }
     private HttpStatus getHttpStatusFromException(Exception ex) {
      ...
    }
}

Важность обеих настроек:

Без A: неаутентифицированные пользователи не будут корректно перенаправляться на страницы ошибок.

Без B: пользователи, прошедшие проверку подлинности, не будут корректно перенаправляться на страницы ошибок.

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

Способ закрытия с логином:

Таким образом вы можете переопределить onlick, если не используете шаблоны. Если вы используете реакцию, вам придется адаптировать ее к компоненту, но она очень похожа.

            const token = await getCsrfToken();

            // Realizar la solicitud de logout
            fetch("/logout", {
              method: "POST",
              headers: {
                "X-CSRF-TOKEN": token,
              },
            }).then((response) => {
              if (response.ok) {
                window.location.href = "/login.html";
              } else {
                console.error("Logout failed");
              }
            });
          });
        });

Примечание. Безопасность кодировщика обеспечивается с помощью Argon2.

Я перенял лучшие практики 2024 года по сравнению с предыдущими годами.

Другие вопросы по теме