contextPath fixes

This commit is contained in:
Anthony Stirling 2023-12-28 13:50:31 +00:00
parent 3911be0177
commit 8acab77ae3
9 changed files with 88 additions and 28 deletions

View file

@ -5,11 +5,13 @@ import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@Component @Component
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@ -19,8 +21,32 @@ public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthent
@Override @Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
String username = request.getParameter("username"); String username = request.getParameter("username");
loginAttemptService.loginSucceeded(username); loginAttemptService.loginSucceeded(username);
super.onAuthenticationSuccess(request, response, authentication);
// Get the saved request
HttpSession session = request.getSession(false);
SavedRequest savedRequest = session != null ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") : null;
if (savedRequest != null && !isStaticResource(savedRequest)) {
// Redirect to the original destination
super.onAuthenticationSuccess(request, response, authentication);
} else {
// Redirect to the root URL (considering context path)
getRedirectStrategy().sendRedirect(request, response, "/");
}
//super.onAuthenticationSuccess(request, response, authentication);
} }
private boolean isStaticResource(SavedRequest savedRequest) {
String requestURI = savedRequest.getRedirectUrl();
return requestURI.startsWith("/css/")
|| requestURI.startsWith("/js/")
|| requestURI.startsWith("/images/")
|| requestURI.startsWith("/public/")
|| requestURI.startsWith("/pdfjs/")
|| requestURI.endsWith(".svg");
}
} }

View file

@ -7,6 +7,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
@ -15,12 +16,13 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import stirling.software.SPDF.repository.JPATokenRepositoryImpl; import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
@Configuration @Configuration
@EnableWebSecurity() @EnableWebSecurity()
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableMethodSecurity
public class SecurityConfiguration { public class SecurityConfiguration {
@Autowired @Autowired
@ -41,9 +43,7 @@ public class SecurityConfiguration {
@Autowired @Autowired
private UserAuthenticationFilter userAuthenticationFilter; private UserAuthenticationFilter userAuthenticationFilter;
@Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
@Autowired @Autowired
private LoginAttemptService loginAttemptService; private LoginAttemptService loginAttemptService;
@ -63,11 +63,13 @@ public class SecurityConfiguration {
http http
.formLogin(formLogin -> formLogin .formLogin(formLogin -> formLogin
.loginPage("/login") .loginPage("/login")
.successHandler(customAuthenticationSuccessHandler) .successHandler(new CustomAuthenticationSuccessHandler())
// .defaultSuccessUrl("/") .defaultSuccessUrl("/")
.failureHandler(new CustomAuthenticationFailureHandler(loginAttemptService)) .failureHandler(new CustomAuthenticationFailureHandler(loginAttemptService))
.permitAll() .permitAll()
) ).requestCache(requestCache -> requestCache
.requestCache(new NullRequestCache())
)
.logout(logout -> logout .logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout=true") .logoutSuccessUrl("/login?logout=true")
@ -79,8 +81,19 @@ public class SecurityConfiguration {
.tokenValiditySeconds(1209600) // 2 weeks .tokenValiditySeconds(1209600) // 2 weeks
) )
.authorizeHttpRequests(authz -> authz .authorizeHttpRequests(authz -> authz
.requestMatchers(req -> req.getRequestURI().startsWith("/login") || req.getRequestURI().endsWith(".svg") || req.getRequestURI().startsWith("/register") || req.getRequestURI().startsWith("/error") || req.getRequestURI().startsWith("/images/") || req.getRequestURI().startsWith("/public/") || req.getRequestURI().startsWith("/css/") || req.getRequestURI().startsWith("/js/")) .requestMatchers(req -> {
.permitAll() String uri = req.getRequestURI();
String contextPath = req.getContextPath();
// Remove the context path from the URI
String trimmedUri = uri.startsWith(contextPath) ? uri.substring(contextPath.length()) : uri;
return trimmedUri.startsWith("/login") || trimmedUri.endsWith(".svg") ||
trimmedUri.startsWith("/register") || trimmedUri.startsWith("/error") ||
trimmedUri.startsWith("/images/") || trimmedUri.startsWith("/public/") ||
trimmedUri.startsWith("/css/") || trimmedUri.startsWith("/js/");
}
).permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.userDetailsService(userDetailsService) .userDetailsService(userDetailsService)

View file

@ -74,8 +74,10 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
// If we still don't have any authentication, deny the request // If we still don't have any authentication, deny the request
if (authentication == null || !authentication.isAuthenticated()) { if (authentication == null || !authentication.isAuthenticated()) {
String method = request.getMethod(); String method = request.getMethod();
if ("GET".equalsIgnoreCase(method) && !"/login".equals(requestURI)) { String contextPath = request.getContextPath();
response.sendRedirect("/login"); // redirect to the login page
if ("GET".equalsIgnoreCase(method) && ! (contextPath + "/login").equals(requestURI)) {
response.sendRedirect(contextPath + "/login"); // redirect to the login page
return; return;
} else { } else {
response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setStatus(HttpStatus.UNAUTHORIZED.value());
@ -90,15 +92,17 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
@Override @Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String uri = request.getRequestURI(); String uri = request.getRequestURI();
String contextPath = request.getContextPath();
String[] permitAllPatterns = { String[] permitAllPatterns = {
"/login", contextPath + "/login",
"/register", contextPath + "/register",
"/error", contextPath + "/error",
"/images/", contextPath + "/images/",
"/public/", contextPath + "/public/",
"/css/", contextPath + "/css/",
"/js/" contextPath + "/js/",
contextPath + "/pdfjs/",
contextPath + "/site.webmanifest"
}; };
for (String pattern : permitAllPatterns) { for (String pattern : permitAllPatterns) {

View file

@ -15,14 +15,22 @@ import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletContext;
import stirling.software.SPDF.model.ApiEndpoint; import stirling.software.SPDF.model.ApiEndpoint;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
@Service @Service
public class ApiDocService { public class ApiDocService {
private final Map<String, ApiEndpoint> apiDocumentation = new HashMap<>(); private final Map<String, ApiEndpoint> apiDocumentation = new HashMap<>();
private final String apiDocsUrl = "http://localhost:8080/v1/api-docs"; // URL to your API documentation
@Autowired
private ServletContext servletContext;
private String getApiDocsUrl() {
String contextPath = servletContext.getContextPath();
return "http://localhost:8080" + contextPath + "/v1/api-docs";
}
@Autowired(required=false) @Autowired(required=false)
private UserServiceInterface userService; private UserServiceInterface userService;
@ -44,7 +52,7 @@ public class ApiDocService {
HttpEntity<String> entity = new HttpEntity<>(headers); HttpEntity<String> entity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(apiDocsUrl, HttpMethod.GET, entity, String.class); ResponseEntity<String> response = restTemplate.exchange(getApiDocsUrl(), HttpMethod.GET, entity, String.class);
String apiDocsJson = response.getBody(); String apiDocsJson = response.getBody();
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();

View file

@ -50,6 +50,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.ServletContext;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation; import stirling.software.SPDF.model.PipelineOperation;
@ -117,6 +118,14 @@ public class PipelineController {
return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId()); return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId());
} }
@Autowired
private ServletContext servletContext;
private String getBaseUrl() {
String contextPath = servletContext.getContextPath();
return "http://localhost:8080" + contextPath + "/";
}
private void handleDirectory(Path dir) throws Exception { private void handleDirectory(Path dir) throws Exception {
logger.info("Handling directory: {}", dir); logger.info("Handling directory: {}", dir);
@ -337,7 +346,7 @@ public class PipelineController {
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers); HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/" + operation; String url = getBaseUrl() + operation;
ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class); ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);

View file

@ -1,5 +1,5 @@
#searchBar { #searchBar {
background-image: url('/images/search.svg'); background-image: url('../images/search.svg');
background-position: 16px 16px; background-position: 16px 16px;
background-repeat: no-repeat; background-repeat: no-repeat;
width: 100%; width: 100%;

View file

@ -130,7 +130,7 @@ document.getElementById('submitConfigBtn').addEventListener('click', function()
formData.append('json', pipelineConfigJson); formData.append('json', pipelineConfigJson);
console.log("formData", formData); console.log("formData", formData);
fetch('/api/v1/pipeline/handleData', { fetch('api/v1/pipeline/handleData', {
method: 'POST', method: 'POST',
body: formData body: formData
}) })

View file

@ -306,7 +306,7 @@
<div class="mb-3 mt-4"> <div class="mb-3 mt-4">
<a href="/logout"> <a href="logout">
<button type="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</button> <button type="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</button>
</a> </a>
<a th:if="${role == 'ROLE_ADMIN'}" href="addUsers" target="_blank"> <a th:if="${role == 'ROLE_ADMIN'}" href="addUsers" target="_blank">

View file

@ -293,7 +293,7 @@
</a> </a>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a th:if="${@loginEnabled}" href="/logout"> <a th:if="${@loginEnabled}" href="logout">
<button type="button" class="btn btn-danger" th:text="#{settings.signOut}">Sign Out</button> <button type="button" class="btn btn-danger" th:text="#{settings.signOut}">Sign Out</button>
</a> </a>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>