Вошедший в систему пользователь может получить доступ к любому несуществующему маршруту, получив 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
Как показано здесь, вам нужно обработать ResourceNotFoundException
исключение для 404, но я не уверен, что это сработает, если вы намеренно возвращаете несуществующий html из метода контроллера.
Либо используйте logoutRequestMatcher
, либо logoutUrl
, но не оба. А еще /logout
это корень, вы уверены, что это http://localhost:8080/logout
? Если по какой-то причине вы используете подпуть, вам необходимо его включить.
Спасибо за ответ. Проблема 1 приводит к проблеме 2, и вы можете видеть, что, если неправильно «контролировать» перенаправление на 404.html, функция выхода из системы отключается, поскольку она не может «контролировать» маршруты. Случай 2 показывает, как из-за проблемы с неправильно управляемыми маршрутами он перестает работать.
Добавьте журналы Spring Security TRACE
к своему вопросу.
Благодаря комментарию и точности Дж. Аскарова: «Я не уверен, что это сработает, если вы намеренно возвращаете несуществующий 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";
}
}
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 года по сравнению с предыдущими годами.
Хотя я понимаю, что в вашем приложении может возникнуть ряд проблем, пожалуйста, задавайте по одной проблеме на каждый вопрос, иначе это может сбить с толку. Пожалуйста, отредактируйте свой вопрос, включив в него один ясный вопрос.