Hello, I have already gained experience with the Spring Security Chain for my VNC proxy.
Furthermore I need 2 routes in my project, which should be accessible once with and once without authentication.
GET requests are always possible when you are authentificated, POST requests are rejected (403) although I send the CSRF token.
I have searched the forum and unfortunately cannot find a solution, even various articles on the Internet do not lead to a solution.
I wanted to push the topic again, maybe there are alternatives.
package de.bytestore.hostinger.api;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import de.bytestore.hostinger.entity.cms.Page;
import de.bytestore.hostinger.gson.templates.JSONStatus;
import de.bytestore.hostinger.gson.templates.JSONStatusMessage;
import de.bytestore.hostinger.handler.WebHandler;
import io.jmix.core.DataManager;
import io.jmix.core.JmixSecurityFilterChainOrder;
import io.jmix.core.querycondition.PropertyCondition;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.util.UriTemplate;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
/**
* BuilderAPIRoute is a HttpServlet that handles API routes related to a Builder functionality.
*
* This class provides methods for configuring security filters, registering servlets, and handling HTTP requests.
* It also contains utility methods for extracting routes from URIs and generating JSON status messages.
*/
@Configuration
@WebServlet(value = "/internal/builder/{route}", loadOnStartup = 1)
public class BuilderAPIRoute extends HttpServlet {
protected static final Logger log = LoggerFactory.getLogger(BuilderAPIRoute.class);
protected final DataManager dataManager;
public BuilderAPIRoute(DataManager dataManager) {
this.dataManager = dataManager;
}
/**
* Returns a ServletRegistrationBean for the example servlet.
*
* @return The ServletRegistrationBean for the example servlet.
*/
@Bean
public ServletRegistrationBean builderServletBean() {
ServletRegistrationBean bean = new ServletRegistrationBean(
this, "/internal/builder/*");
bean.setLoadOnStartup(1);
return bean;
}
@Bean(name = "builderSecurityFilterChain")
@Order(JmixSecurityFilterChainOrder.FLOWUI - 10)
// https://forum.jmix.io/t/static-resources-problem/2351/10
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(httpSecurityCsrfConfigurer -> {
httpSecurityCsrfConfigurer.ignoringRequestMatchers(new AntPathRequestMatcher("/internal/builder/**"));
});
http.securityMatcher("/internal/builder/**")
.authorizeHttpRequests((authorize) -> authorize.requestMatchers("/internal/builder/**").permitAll());
return http.build();
}
/**
* Handles a POST request and saves the page content to the database.
*
* @param requestIO The HttpServletRequest object.
* @param responseIO The HttpServletResponse object.
* @throws ServletException If an error occurs during the request processing.
* @throws IOException If an I/O error occurs while handling the response.
*/
@Override
protected void doPost(HttpServletRequest requestIO, HttpServletResponse responseIO) throws ServletException, IOException {
// Parse Route from Request.
String routeIO = extractRouteFromUri(requestIO.getRequestURI());
this.handleCRSF(requestIO, responseIO);
responseIO.getWriter().write("OK");
responseIO.setStatus(HttpServletResponse.SC_OK);
// try {
// if (routeIO != null) {
// // Get the Page Object of given Route.
// de.bytestore.hostinger.entity.cms.Page pageIO = dataManager.load(de.bytestore.hostinger.entity.cms.Page.class).condition(PropertyCondition.equal("route", routeIO)).one();
//
// if (pageIO != null) {
// // @todo: Add Permission Check.
//
// // Read Body of Request and set as Page Payload.
// pageIO.setContent(IOUtils.toString(requestIO.getReader()));
//
// // Save Page to Database.
// dataManager.save(pageIO);
//
// responseIO.setStatus(HttpServletResponse.SC_OK);
// responseIO.setContentType("application/json");
// responseIO.getWriter().write(new Gson().toJson(new JSONStatusMessage(JSONStatus.SUCCESS, "Page saved.")));
// } else {
// notFound(responseIO);
// }
//
// } else {
// notFound(responseIO);
// }
//
// } catch(Exception exceptionIO){
// log.error("Unable to save Builder Template with Route '" + routeIO + "'.", exceptionIO);
//
// notFound(responseIO);
// }
}
/**
* Sets the HTTP response status code to {@link HttpServletResponse#SC_INTERNAL_SERVER_ERROR} and writes a JSON status message to the response body indicating that the route was
* not found.
* The JSON status message is generated using {@link Gson} library.
*
* @param responseIO the HTTP servlet response object
* @throws IOException if an I/O error occurs while writing the response
*/
private void notFound(HttpServletResponse responseIO) throws IOException {
responseIO.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
responseIO.setContentType("application/json");
responseIO.getWriter().write(new Gson().toJson(new JSONStatusMessage(JSONStatus.ERROR, "Unable to find route.")));
}
/**
* Handles a GET request and retrieves the page content based on the requested route.
*
* @param requestIO The HttpServletRequest object.
* @param responseIO The HttpServletResponse object.
* @throws ServletException If an error occurs during the request processing.
* @throws IOException If an I/O error occurs while handling the response.
*/
@Override
protected void doGet(HttpServletRequest requestIO, HttpServletResponse responseIO) throws ServletException, IOException {
// Parse Route from Request.
String routeIO = extractRouteFromUri(requestIO.getRequestURI());
this.handleCRSF(requestIO, responseIO);
if (routeIO != null && routeIO.equalsIgnoreCase("listPages")) {
// Get all Pages Objects.
List<Page> pagesIO = dataManager.load(Page.class).all().list();
// Create new JSON Response object.
JSONStatusMessage statusIO = new JSONStatusMessage(JSONStatus.SUCCESS, new JsonObject());
// Create new Array Buffer.
JsonArray arrayIO = new JsonArray();
// Loop through JPA Entries.
pagesIO.forEach(page -> {
JsonObject objectIO = new JsonObject();
objectIO.addProperty("route", page.getId());
objectIO.addProperty("name", page.getName());
arrayIO.add(objectIO);
});
// Add Array to Response.
statusIO.getValue().add("pages", arrayIO);
responseIO.setContentType("application/json");
responseIO.getWriter().write(new Gson().toJson(statusIO));
} else {
try {
// Get the Page Object of given Route.
Page pageIO = dataManager.load(Page.class).condition(PropertyCondition.equal("id", routeIO)).one();
if (pageIO != null) {
// @todo: Add Permission Check.
// Return Body of Page.
responseIO.setContentType("text/html");
PrintWriter writerIO = responseIO.getWriter();
writerIO.print(pageIO.getContent());
writerIO.flush();
writerIO.close();
} else {
this.notFound(responseIO);
}
} catch (Exception exceptionIO) {
log.error("Unable to load Builder Template with Route '" + routeIO + "'.", exceptionIO);
this.notFound(responseIO);
}
// responseIO.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
// responseIO.getWriter().write(new Gson().toJson(new JSONStatusMessage(JSONStatus.ERROR, "Please use POST Method.")));
}
}
private void handleCRSF(HttpServletRequest requestIO, HttpServletResponse responseIO) {
CsrfToken tokenIO = WebHandler.getCSRFToken(requestIO);
if(tokenIO != null) {
// Create new CRSF Cookie.
Cookie cookieIO = new Cookie("XSRF-TOKEN", tokenIO.getToken());
// Add Wilcard.
cookieIO.setPath("/");
// Add Cookie to Response.
responseIO.addCookie(cookieIO);
} else {
// Print Warn / Debug Message.
log.warn("Can't find CSRF token for request.");
}
}
/**
* Extracts the route from the given URI using the provided UriTemplate.
*
* @param uri The URI from which to extract the route.
* @return The extracted route.
*/
private String extractRouteFromUri(String uri) {
UriTemplate template = new UriTemplate("/internal/builder/{route}");
return template.match(uri).get("route");
}
}
package de.bytestore.hostinger.handler;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.web.csrf.CsrfToken;
public class WebHandler {
/**
* Retrieves the CSRF token from the provided HttpServletRequest object.
*
* @param requestIO The HttpServletRequest object from which to retrieve the CSRF token.
* @return The CSRF token.
*/
public static CsrfToken getCSRFToken(HttpServletRequest requestIO) {
return (CsrfToken) requestIO.getAttribute(CsrfToken.class.getName());
}
}
Error:
I have also tried to switch off CRSF, but apparently this is not recognized / overwritten.