mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-02 10:35:22 +00:00
Fixed blank login page when v2 is disabled
This commit is contained in:
parent
13fa7475aa
commit
622a0f91aa
@ -50,4 +50,4 @@ spring.main.allow-bean-definition-overriding=true
|
||||
java.io.tmpdir=${stirling.tempfiles.directory:${java.io.tmpdir}/stirling-pdf}
|
||||
|
||||
# V2 features
|
||||
v2=true
|
||||
v2=false
|
||||
|
@ -78,6 +78,9 @@ public class CustomAuthenticationSuccessHandler
|
||||
request.getContextPath(), savedRequest.getRedirectUrl())) {
|
||||
// Redirect to the original destination
|
||||
super.onAuthenticationSuccess(request, response, authentication);
|
||||
} else {
|
||||
// No saved request or it's a static resource, redirect to home page
|
||||
getRedirectStrategy().sendRedirect(request, response, "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ public class SecurityConfiguration {
|
||||
boolean v2Enabled = appConfig.v2Enabled();
|
||||
|
||||
if (v2Enabled) {
|
||||
http.addFilterBefore(
|
||||
http.addFilterAt(
|
||||
jwtAuthenticationFilter(),
|
||||
UsernamePasswordAuthenticationFilter.class)
|
||||
.exceptionHandling(
|
||||
@ -143,7 +143,8 @@ public class SecurityConfiguration {
|
||||
exceptionHandling.authenticationEntryPoint(
|
||||
jwtAuthenticationEntryPoint));
|
||||
}
|
||||
http.addFilterAt(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
http.addFilterBefore(
|
||||
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.addFilterAfter(rateLimitingFilter(), userAuthenticationFilter.getClass())
|
||||
.addFilterAfter(firstLoginFilter, rateLimitingFilter().getClass());
|
||||
|
||||
@ -209,8 +210,7 @@ public class SecurityConfiguration {
|
||||
securityProperties, appConfig, jwtService))
|
||||
.clearAuthentication(true)
|
||||
.invalidateHttpSession(true)
|
||||
.deleteCookies(
|
||||
"JSESSIONID", "remember-me", "STIRLING_JWT_TOKEN"));
|
||||
.deleteCookies("JSESSIONID", "remember-me", "stirling_jwt"));
|
||||
http.rememberMe(
|
||||
rememberMeConfigurer -> // Use the configurator directly
|
||||
rememberMeConfigurer
|
||||
|
@ -38,6 +38,11 @@ public class JwtSaml2AuthenticationRequestRepository
|
||||
Saml2PostAuthenticationRequest authRequest,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
if (!jwtService.isJwtEnabled()) {
|
||||
log.warn("SAML2 v2 is not enabled, skipping saveAuthenticationRequest");
|
||||
return;
|
||||
}
|
||||
|
||||
if (authRequest == null) {
|
||||
removeAuthenticationRequest(request, response);
|
||||
return;
|
||||
|
@ -0,0 +1,307 @@
|
||||
package stirling.software.proprietary.security.filter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.proprietary.security.model.exception.AuthenticationFailureException;
|
||||
import stirling.software.proprietary.security.service.CustomUserDetailsService;
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class JwtAuthenticationFilterTest {
|
||||
|
||||
@Mock
|
||||
private JwtServiceInterface jwtService;
|
||||
|
||||
@Mock
|
||||
private CustomUserDetailsService userDetailsService;
|
||||
|
||||
@Mock
|
||||
private UserService userService;
|
||||
|
||||
@Mock
|
||||
private ApplicationProperties.Security securityProperties;
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Mock
|
||||
private HttpServletResponse response;
|
||||
|
||||
@Mock
|
||||
private FilterChain filterChain;
|
||||
|
||||
@Mock
|
||||
private UserDetails userDetails;
|
||||
|
||||
@Mock
|
||||
private SecurityContext securityContext;
|
||||
|
||||
@Mock
|
||||
private AuthenticationEntryPoint authenticationEntryPoint;
|
||||
|
||||
@InjectMocks
|
||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Test
|
||||
void shouldNotAuthenticateWhenJwtDisabled() throws ServletException, IOException {
|
||||
when(jwtService.isJwtEnabled()).thenReturn(false);
|
||||
|
||||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verify(jwtService, never()).extractTokenFromRequest(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterWhenPageIsLogin() throws ServletException, IOException {
|
||||
when(jwtService.isJwtEnabled()).thenReturn(true);
|
||||
when(request.getRequestURI()).thenReturn("/login");
|
||||
when(request.getMethod()).thenReturn("POST");
|
||||
|
||||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(request, response);
|
||||
verify(jwtService, never()).extractTokenFromRequest(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoFilterInternal() throws ServletException, IOException {
|
||||
String token = "valid-jwt-token";
|
||||
String newToken = "new-jwt-token";
|
||||
String username = "testuser";
|
||||
Map<String, Object> claims = Map.of("sub", username, "authType", "WEB");
|
||||
|
||||
when(jwtService.isJwtEnabled()).thenReturn(true);
|
||||
when(request.getRequestURI()).thenReturn("/protected");
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
when(jwtService.extractTokenFromRequest(request)).thenReturn(token);
|
||||
doNothing().when(jwtService).validateToken(token);
|
||||
when(jwtService.extractAllClaims(token)).thenReturn(claims);
|
||||
when(userDetails.getAuthorities()).thenReturn(Collections.emptyList());
|
||||
when(userDetailsService.loadUserByUsername(username)).thenReturn(userDetails);
|
||||
|
||||
try (MockedStatic<SecurityContextHolder> mockedSecurityContextHolder = mockStatic(SecurityContextHolder.class)) {
|
||||
UsernamePasswordAuthenticationToken authToken =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
|
||||
when(securityContext.getAuthentication()).thenReturn(null).thenReturn(authToken);
|
||||
mockedSecurityContextHolder.when(SecurityContextHolder::getContext).thenReturn(securityContext);
|
||||
when(jwtService.generateToken(any(UsernamePasswordAuthenticationToken.class), eq(claims))).thenReturn(newToken);
|
||||
|
||||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
|
||||
|
||||
verify(jwtService).validateToken(token);
|
||||
verify(jwtService).extractAllClaims(token);
|
||||
verify(userDetailsService).loadUserByUsername(username);
|
||||
verify(securityContext).setAuthentication(any(UsernamePasswordAuthenticationToken.class));
|
||||
verify(jwtService).generateToken(any(UsernamePasswordAuthenticationToken.class), eq(claims));
|
||||
verify(jwtService).addTokenToResponse(response, newToken);
|
||||
verify(filterChain).doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoFilterInternalWithMissingTokenForRootPath() throws ServletException, IOException {
|
||||
when(jwtService.isJwtEnabled()).thenReturn(true);
|
||||
when(request.getRequestURI()).thenReturn("/");
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
when(jwtService.extractTokenFromRequest(request)).thenReturn(null);
|
||||
|
||||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
|
||||
|
||||
verify(response).sendRedirect("/login");
|
||||
verify(filterChain, never()).doFilter(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationFailsWithInvalidToken() throws ServletException, IOException {
|
||||
String token = "invalid-jwt-token";
|
||||
|
||||
when(jwtService.isJwtEnabled()).thenReturn(true);
|
||||
when(request.getRequestURI()).thenReturn("/protected");
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
when(jwtService.extractTokenFromRequest(request)).thenReturn(token);
|
||||
doThrow(new AuthenticationFailureException("Invalid token")).when(jwtService).validateToken(token);
|
||||
|
||||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
|
||||
|
||||
verify(jwtService).validateToken(token);
|
||||
verify(authenticationEntryPoint).commence(eq(request), eq(response), any(AuthenticationFailureException.class));
|
||||
verify(filterChain, never()).doFilter(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validationFailsWithExpiredToken() throws ServletException, IOException {
|
||||
String token = "expired-jwt-token";
|
||||
|
||||
when(jwtService.isJwtEnabled()).thenReturn(true);
|
||||
when(request.getRequestURI()).thenReturn("/protected");
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
when(jwtService.extractTokenFromRequest(request)).thenReturn(token);
|
||||
doThrow(new AuthenticationFailureException("The token has expired")).when(jwtService).validateToken(token);
|
||||
|
||||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
|
||||
|
||||
verify(jwtService).validateToken(token);
|
||||
verify(authenticationEntryPoint).commence(eq(request), eq(response), any());
|
||||
verify(filterChain, never()).doFilter(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptinonThrown_WhenUserNotFound() throws ServletException, IOException {
|
||||
String token = "valid-jwt-token";
|
||||
String username = "nonexistentuser";
|
||||
Map<String, Object> claims = Map.of("sub", username, "authType", "WEB");
|
||||
|
||||
when(jwtService.isJwtEnabled()).thenReturn(true);
|
||||
when(request.getRequestURI()).thenReturn("/protected");
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
when(jwtService.extractTokenFromRequest(request)).thenReturn(token);
|
||||
doNothing().when(jwtService).validateToken(token);
|
||||
when(jwtService.extractAllClaims(token)).thenReturn(claims);
|
||||
when(userDetailsService.loadUserByUsername(username)).thenReturn(null);
|
||||
|
||||
try (MockedStatic<SecurityContextHolder> mockedSecurityContextHolder = mockStatic(SecurityContextHolder.class)) {
|
||||
when(securityContext.getAuthentication()).thenReturn(null);
|
||||
mockedSecurityContextHolder.when(SecurityContextHolder::getContext).thenReturn(securityContext);
|
||||
|
||||
UsernameNotFoundException result = assertThrows(UsernameNotFoundException.class, () -> jwtAuthenticationFilter.doFilterInternal(request, response, filterChain));
|
||||
|
||||
assertEquals("User not found: " + username, result.getMessage());
|
||||
verify(userDetailsService).loadUserByUsername(username);
|
||||
verify(filterChain, never()).doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterLoginPost() {
|
||||
when(request.getRequestURI()).thenReturn("/login");
|
||||
when(request.getMethod()).thenReturn("POST");
|
||||
|
||||
assertTrue(jwtAuthenticationFilter.shouldNotFilter(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterLoginGet() {
|
||||
when(request.getRequestURI()).thenReturn("/login");
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
|
||||
assertTrue(jwtAuthenticationFilter.shouldNotFilter(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterPublicPaths() {
|
||||
String[] publicPaths = {
|
||||
"/register",
|
||||
"/error",
|
||||
"/images/logo.png",
|
||||
"/public/file.txt",
|
||||
"/css/style.css",
|
||||
"/fonts/font.ttf",
|
||||
"/js/script.js",
|
||||
"/pdfjs/viewer.js",
|
||||
"/pdfjs-legacy/viewer.js",
|
||||
"/api/v1/info/status",
|
||||
"/site.webmanifest",
|
||||
"/favicon.ico"
|
||||
};
|
||||
|
||||
for (String path : publicPaths) {
|
||||
when(request.getRequestURI()).thenReturn(path);
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
|
||||
assertTrue(jwtAuthenticationFilter.shouldNotFilter(request),
|
||||
"Should not filter path: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterStaticFiles() {
|
||||
String[] staticFiles = {
|
||||
"/some/path/file.svg",
|
||||
"/another/path/image.png",
|
||||
"/path/to/icon.ico"
|
||||
};
|
||||
|
||||
for (String file : staticFiles) {
|
||||
when(request.getRequestURI()).thenReturn(file);
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
|
||||
assertTrue(jwtAuthenticationFilter.shouldNotFilter(request),
|
||||
"Should not filter file: " + file);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFilterProtectedPaths() {
|
||||
String[] protectedPaths = {
|
||||
"/protected",
|
||||
"/api/v1/user/profile",
|
||||
"/admin",
|
||||
"/dashboard"
|
||||
};
|
||||
|
||||
for (String path : protectedPaths) {
|
||||
when(request.getRequestURI()).thenReturn(path);
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
|
||||
assertFalse(jwtAuthenticationFilter.shouldNotFilter(request),
|
||||
"Should filter path: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFilterRootPath() {
|
||||
when(request.getRequestURI()).thenReturn("/");
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
|
||||
assertFalse(jwtAuthenticationFilter.shouldNotFilter(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAuthenticationEntryPointCalledWithCorrectException() throws ServletException, IOException {
|
||||
when(jwtService.isJwtEnabled()).thenReturn(true);
|
||||
when(request.getRequestURI()).thenReturn("/protected");
|
||||
when(request.getMethod()).thenReturn("GET");
|
||||
when(jwtService.extractTokenFromRequest(request)).thenReturn(null);
|
||||
|
||||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
|
||||
|
||||
verify(authenticationEntryPoint).commence(eq(request), eq(response), argThat(exception ->
|
||||
exception.getMessage().equals("JWT is missing from the request")
|
||||
));
|
||||
verify(filterChain, never()).doFilter(request, response);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user