A logged in user can access any non-existent route obtaining the Error: There is no static resource
I have two problems with the springboot security configuration when I am logged in.
1 – URL access:
1.1 – If I am not logged in
– If I enter any page that does not exist, springboot security correctly redirects you to the 404.html page. “response.sendRedirect(“/404.html”);”
1.2 – If I am logged in
– If I enter any page, I can access it without loading the 404.html page with the following message
{"data":null,"status":"404 NOT_FOUND","timestamp":"2024-07-18T08:24:38.2990731","server":"MyServer","error":"Error: No static resource insssssdex.html."}
This behavior is not correct, it should redirect to the 404.html page.
1.3 – If I am logged in, any incorrect page will be the same error as in 1.2. This should not be like this, it would have to work as in 1.1.
2 – LogOut, since the previous one is not fulfilled, it simply goes to a /logout route and nothing happens since spring does not determine anything. Therefore, it is as if when it is accessed, spring stops controlling the pages. It works like in 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();
}
GlobalExceptionHandler
@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;
}
}
My button: 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;
});
});
});
impl html
<div class="logout-button-container"></div>
For some reason, when I call the logout button, point 1.2 is loaded which is not correct and it seems that when point 1.2 can be loaded it does not logout correctly with the message:
{"data":null,"status":"404 NOT_FOUND","timestamp":"2024-07-18T08:32:03.8629218","server":"MyServer","error":"Error: No static resource logout."}
It is as if the intermittent route did not detect it. I don’t know if I’m doing too many checks or if I’ve left something in the “exceptionHandling” about when it’s logged in, I don’t know.
Case 1 – Spring boot security redirection successfully
Case 2 – When the button is executed, logout without having to configure it manually since spring security takes care of it. If case 1 is solved, then case 2 I think will also be solved. Therefore, everything falls on case 1
5
Thanks to the comment and precision of J Asgarov “I am not sure that would work if you are deliberately returning a non existing html from your controller method”, If you return a ResponseEntity, it will always return a JSON page. I thought that by passing the HttpStatus code, Spring would take care of redirecting it as such if it found the “resource.html”, but in reality it processes the request as a complete ResponseEntity.
I understand that when you redirect to the view, Spring will interpret it as a json instead of automatically redirecting it and Spring won’t know what exactly to do and will return what is passed to it, hence a ResponseEntity regardless of the code. The result will be a page with the error in json format instead of redirecting to the 404.html page.
In this case, I simply put redirect like this in “void” since I want this to be handled by 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 – Unlogged users
Security settings: Access to certain URLs is allowed without authentication (/register, /login, etc.).
Error Handling: If an unauthenticated user tries to access a non-existent URL, they are redirected to /errorPage.
Error Handler: The handleError handler redirects to different error pages (/404.html, /500.html, /genericError.html) based on the HTTP status code.
// 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";
}
}
- Remember that if you use a template or another form of redirection to the frontend you will have to adapt it to your needs.
- Remember that I am using pure JavaScript at the moment. Therefore, if you are using another way to pass data from backend to frontend like a template (freemaker (the best, least intrusive), Thymeleaf…) you will have to modify the “login.html” views. -> “login” or if you use Rest to an endpoint”/login”…
B – Logged in users
Exception Handling: If an authenticated user attempts to access a non-existent URL, the handleAllExceptions method redirects to /404.html or /500.html depending on the type of exception.
@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) {
...
}
}
Importance of both settings:
Without A: Unauthenticated users would not be correctly redirected to error pages.
Without B: Authenticated users would not be correctly redirected to error pages.
Both settings are necessary to ensure that all users, authenticated or not, are properly redirected in case of errors or non-existent URLs.
The way to close with the login:
You can override the onlick this way if you don’t use templates.
If you use react you have to adapt it to the component but it is very similar.
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");
}
});
});
});
Note: Encoder’s security is made with Argon2
I have taken best practices in 2024 compared to previous years.