More clean up

This commit is contained in:
Dario Ghunney Ware 2025-07-10 13:16:29 +01:00
parent dfeb17c886
commit b66787b738
6 changed files with 68 additions and 139 deletions

View File

@ -17,7 +17,6 @@ public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
HttpServletResponse response, HttpServletResponse response,
AuthenticationException authException) AuthenticationException authException)
throws IOException { throws IOException {
response.sendError( response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: " + authException.getMessage());
} }
} }

View File

@ -73,7 +73,6 @@ public class SecurityConfiguration {
private final ApplicationProperties.Security securityProperties; private final ApplicationProperties.Security securityProperties;
private final AppConfig appConfig; private final AppConfig appConfig;
private final UserAuthenticationFilter userAuthenticationFilter; private final UserAuthenticationFilter userAuthenticationFilter;
private final JWTAuthenticationFilter jwtAuthenticationFilter;
private final JWTServiceInterface jwtService; private final JWTServiceInterface jwtService;
private final JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint; private final JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final LoginAttemptService loginAttemptService; private final LoginAttemptService loginAttemptService;
@ -93,7 +92,6 @@ public class SecurityConfiguration {
AppConfig appConfig, AppConfig appConfig,
ApplicationProperties.Security securityProperties, ApplicationProperties.Security securityProperties,
UserAuthenticationFilter userAuthenticationFilter, UserAuthenticationFilter userAuthenticationFilter,
JWTAuthenticationFilter jwtAuthenticationFilter,
JWTServiceInterface jwtService, JWTServiceInterface jwtService,
JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint, JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint,
LoginAttemptService loginAttemptService, LoginAttemptService loginAttemptService,
@ -111,7 +109,6 @@ public class SecurityConfiguration {
this.appConfig = appConfig; this.appConfig = appConfig;
this.securityProperties = securityProperties; this.securityProperties = securityProperties;
this.userAuthenticationFilter = userAuthenticationFilter; this.userAuthenticationFilter = userAuthenticationFilter;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.jwtService = jwtService; this.jwtService = jwtService;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint; this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.loginAttemptService = loginAttemptService; this.loginAttemptService = loginAttemptService;
@ -138,9 +135,10 @@ public class SecurityConfiguration {
} }
if (loginEnabledValue) { if (loginEnabledValue) {
if (jwtEnabled && jwtAuthenticationFilter != null) { if (jwtEnabled) {
http.addFilterBefore( http.addFilterBefore(
jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class)
.exceptionHandling( .exceptionHandling(
exceptionHandling -> exceptionHandling ->
exceptionHandling.authenticationEntryPoint( exceptionHandling.authenticationEntryPoint(
@ -370,4 +368,10 @@ public class SecurityConfiguration {
public PersistentTokenRepository persistentTokenRepository() { public PersistentTokenRepository persistentTokenRepository() {
return new JPATokenRepositoryImpl(persistentLoginRepository); return new JPATokenRepositoryImpl(persistentLoginRepository);
} }
@Bean
public JWTAuthenticationFilter jwtAuthenticationFilter() {
return new JWTAuthenticationFilter(
jwtService, userDetailsService, jwtAuthenticationEntryPoint);
}
} }

View File

@ -5,11 +5,12 @@ import java.io.IOException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
@ -24,17 +25,20 @@ import stirling.software.proprietary.security.service.CustomUserDetailsService;
import stirling.software.proprietary.security.service.JWTServiceInterface; import stirling.software.proprietary.security.service.JWTServiceInterface;
@Slf4j @Slf4j
@Component
@ConditionalOnBooleanProperty("security.jwt.enabled") @ConditionalOnBooleanProperty("security.jwt.enabled")
public class JWTAuthenticationFilter extends OncePerRequestFilter { public class JWTAuthenticationFilter extends OncePerRequestFilter {
private final JWTServiceInterface jwtService; private final JWTServiceInterface jwtService;
private final CustomUserDetailsService userDetailsService; private final CustomUserDetailsService userDetailsService;
private final AuthenticationEntryPoint authenticationEntryPoint;
public JWTAuthenticationFilter( public JWTAuthenticationFilter(
JWTServiceInterface jwtService, CustomUserDetailsService userDetailsService) { JWTServiceInterface jwtService,
CustomUserDetailsService userDetailsService,
AuthenticationEntryPoint authenticationEntryPoint) {
this.jwtService = jwtService; this.jwtService = jwtService;
this.userDetailsService = userDetailsService; this.userDetailsService = userDetailsService;
this.authenticationEntryPoint = authenticationEntryPoint;
} }
@Override @Override
@ -59,14 +63,17 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
response.sendRedirect("/login"); response.sendRedirect("/login");
return; return;
} }
sendUnauthorizedResponse(response, "JWT is missing from the request"); handleAuthenticationFailure(
request,
response,
new AuthenticationFailureException("JWT is missing from the request"));
return; return;
} }
try { try {
jwtService.validateToken(jwtToken); jwtService.validateToken(jwtToken);
} catch (AuthenticationFailureException e) { } catch (AuthenticationFailureException e) {
sendUnauthorizedResponse(response, e.getMessage()); handleAuthenticationFailure(request, response, e);
return; return;
} }
@ -139,26 +146,11 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
return false; return false;
} }
private void sendUnauthorizedResponse(HttpServletResponse response, String message) private void handleAuthenticationFailure(
throws IOException { HttpServletRequest request,
int unauthorized = HttpServletResponse.SC_UNAUTHORIZED; HttpServletResponse response,
AuthenticationException authException)
response.setStatus(unauthorized); throws IOException, ServletException {
response.setContentType("application/json"); authenticationEntryPoint.commence(request, response, authException);
response.setCharacterEncoding("UTF-8");
String jsonResponse =
String.format(
"""
{
"error": "Unauthorized",
"message": "%s",
"status": %d
}
""",
message, unauthorized);
response.getWriter().write(jsonResponse);
response.getWriter().flush();
} }
} }

View File

@ -10,6 +10,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import java.io.IOException; import java.io.IOException;
import stirling.software.proprietary.security.model.exception.AuthenticationFailureException;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -23,7 +24,7 @@ class JWTAuthenticationEntryPointTest {
private HttpServletResponse response; private HttpServletResponse response;
@Mock @Mock
private AuthenticationException authException; private AuthenticationFailureException authException;
@InjectMocks @InjectMocks
private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint; private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@ -35,8 +36,6 @@ class JWTAuthenticationEntryPointTest {
jwtAuthenticationEntryPoint.commence(request, response, authException); jwtAuthenticationEntryPoint.commence(request, response, authException);
verify(response).sendError( verify(response).sendError(HttpServletResponse.SC_UNAUTHORIZED, errorMessage);
HttpServletResponse.SC_UNAUTHORIZED,
"Unauthorized: " + errorMessage);
} }
} }

View File

@ -1,5 +1,6 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import jakarta.inject.Inject;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@ -7,6 +8,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@ -18,6 +20,7 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import stirling.software.proprietary.security.model.exception.AuthenticationFailureException; import stirling.software.proprietary.security.model.exception.AuthenticationFailureException;
import stirling.software.proprietary.security.service.CustomUserDetailsService; import stirling.software.proprietary.security.service.CustomUserDetailsService;
import stirling.software.proprietary.security.service.JWTServiceInterface; import stirling.software.proprietary.security.service.JWTServiceInterface;
@ -32,6 +35,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@ -61,15 +65,14 @@ class JWTAuthenticationFilterTest {
@Mock @Mock
private PrintWriter printWriter; private PrintWriter printWriter;
@Mock
private AuthenticationEntryPoint authenticationEntryPoint;
@InjectMocks
private JWTAuthenticationFilter jwtAuthenticationFilter; private JWTAuthenticationFilter jwtAuthenticationFilter;
@BeforeEach
void setUp() {
jwtAuthenticationFilter = new JWTAuthenticationFilter(jwtService, userDetailsService);
}
@Test @Test
void testDoFilterInternalWhenJwtDisabled() throws ServletException, IOException { void shouldNotAuthenticateWhenJwtDisabled() throws ServletException, IOException {
when(jwtService.isJwtEnabled()).thenReturn(false); when(jwtService.isJwtEnabled()).thenReturn(false);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
@ -79,7 +82,7 @@ class JWTAuthenticationFilterTest {
} }
@Test @Test
void testDoFilterInternalWhenShouldNotFilter() throws ServletException, IOException { void shouldNotFilterWhenPageIsLogin() throws ServletException, IOException {
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
when(request.getRequestURI()).thenReturn("/login"); when(request.getRequestURI()).thenReturn("/login");
when(request.getMethod()).thenReturn("POST"); when(request.getMethod()).thenReturn("POST");
@ -91,7 +94,7 @@ class JWTAuthenticationFilterTest {
} }
@Test @Test
void testDoFilterInternalWithValidToken() throws ServletException, IOException { void testDoFilterInternal() throws ServletException, IOException {
String token = "valid-jwt-token"; String token = "valid-jwt-token";
String newToken = "new-jwt-token"; String newToken = "new-jwt-token";
String username = "testuser"; String username = "testuser";
@ -106,13 +109,9 @@ class JWTAuthenticationFilterTest {
when(userDetailsService.loadUserByUsername(username)).thenReturn(userDetails); when(userDetailsService.loadUserByUsername(username)).thenReturn(userDetails);
try (MockedStatic<SecurityContextHolder> mockedSecurityContextHolder = mockStatic(SecurityContextHolder.class)) { try (MockedStatic<SecurityContextHolder> mockedSecurityContextHolder = mockStatic(SecurityContextHolder.class)) {
// Create the authentication token that will be set and returned
UsernamePasswordAuthenticationToken authToken = UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// Mock the security context behavior:
// - First call (in createAuthToken): returns null
// - Second call (in createAuthToken after setting): returns the created token
when(securityContext.getAuthentication()).thenReturn(null).thenReturn(authToken); when(securityContext.getAuthentication()).thenReturn(null).thenReturn(authToken);
mockedSecurityContextHolder.when(SecurityContextHolder::getContext).thenReturn(securityContext); mockedSecurityContextHolder.when(SecurityContextHolder::getContext).thenReturn(securityContext);
when(jwtService.generateToken(authToken)).thenReturn(newToken); when(jwtService.generateToken(authToken)).thenReturn(newToken);
@ -143,24 +142,7 @@ class JWTAuthenticationFilterTest {
} }
@Test @Test
void testDoFilterInternalWithMissingTokenForNonRootPath() throws ServletException, IOException { void validationFailsWithInvalidToken() throws ServletException, IOException {
when(jwtService.isJwtEnabled()).thenReturn(true);
when(request.getRequestURI()).thenReturn("/protected");
when(request.getMethod()).thenReturn("GET");
when(jwtService.extractTokenFromRequest(request)).thenReturn(null);
when(response.getWriter()).thenReturn(printWriter);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
verify(response).setContentType("application/json");
verify(response).setCharacterEncoding("UTF-8");
verify(printWriter).write(contains("JWT is missing from the request"));
verify(filterChain, never()).doFilter(request, response);
}
@Test
void testDoFilterInternalWithInvalidToken() throws ServletException, IOException {
String token = "invalid-jwt-token"; String token = "invalid-jwt-token";
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
@ -168,20 +150,16 @@ class JWTAuthenticationFilterTest {
when(request.getMethod()).thenReturn("GET"); when(request.getMethod()).thenReturn("GET");
when(jwtService.extractTokenFromRequest(request)).thenReturn(token); when(jwtService.extractTokenFromRequest(request)).thenReturn(token);
doThrow(new AuthenticationFailureException("Invalid token")).when(jwtService).validateToken(token); doThrow(new AuthenticationFailureException("Invalid token")).when(jwtService).validateToken(token);
when(response.getWriter()).thenReturn(printWriter);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
verify(jwtService).validateToken(token); verify(jwtService).validateToken(token);
verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); verify(authenticationEntryPoint).commence(eq(request), eq(response), any(AuthenticationFailureException.class));
verify(response).setContentType("application/json");
verify(response).setCharacterEncoding("UTF-8");
verify(printWriter).write(contains("Invalid token"));
verify(filterChain, never()).doFilter(request, response); verify(filterChain, never()).doFilter(request, response);
} }
@Test @Test
void testDoFilterInternalWithExpiredToken() throws ServletException, IOException { void validationFailsWithExpiredToken() throws ServletException, IOException {
String token = "expired-jwt-token"; String token = "expired-jwt-token";
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
@ -189,20 +167,16 @@ class JWTAuthenticationFilterTest {
when(request.getMethod()).thenReturn("GET"); when(request.getMethod()).thenReturn("GET");
when(jwtService.extractTokenFromRequest(request)).thenReturn(token); when(jwtService.extractTokenFromRequest(request)).thenReturn(token);
doThrow(new AuthenticationFailureException("The token has expired")).when(jwtService).validateToken(token); doThrow(new AuthenticationFailureException("The token has expired")).when(jwtService).validateToken(token);
when(response.getWriter()).thenReturn(printWriter);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
verify(jwtService).validateToken(token); verify(jwtService).validateToken(token);
verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); verify(authenticationEntryPoint).commence(eq(request), eq(response), any());
verify(response).setContentType("application/json");
verify(response).setCharacterEncoding("UTF-8");
verify(printWriter).write(contains("The token has expired"));
verify(filterChain, never()).doFilter(request, response); verify(filterChain, never()).doFilter(request, response);
} }
@Test @Test
void testDoFilterInternalWithUserNotFound() throws ServletException, IOException { void exceptinonThrown_WhenUserNotFound() throws ServletException, IOException {
String token = "valid-jwt-token"; String token = "valid-jwt-token";
String username = "nonexistentuser"; String username = "nonexistentuser";
@ -218,45 +192,16 @@ class JWTAuthenticationFilterTest {
when(securityContext.getAuthentication()).thenReturn(null); when(securityContext.getAuthentication()).thenReturn(null);
mockedSecurityContextHolder.when(SecurityContextHolder::getContext).thenReturn(securityContext); mockedSecurityContextHolder.when(SecurityContextHolder::getContext).thenReturn(securityContext);
assertThrows(UsernameNotFoundException.class, () -> { UsernameNotFoundException result = assertThrows(UsernameNotFoundException.class, () -> jwtAuthenticationFilter.doFilterInternal(request, response, filterChain));
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
});
assertEquals("User not found: " + username, result.getMessage());
verify(userDetailsService).loadUserByUsername(username); verify(userDetailsService).loadUserByUsername(username);
verify(filterChain, never()).doFilter(request, response); verify(filterChain, never()).doFilter(request, response);
} }
} }
@Test @Test
void testDoFilterInternalWithExistingAuthentication() throws ServletException, IOException { void shouldNotFilterLoginPost() {
String token = "valid-jwt-token";
String newToken = "new-jwt-token";
String username = "testuser";
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.extractUsername(token)).thenReturn(username);
try (MockedStatic<SecurityContextHolder> mockedSecurityContextHolder = mockStatic(SecurityContextHolder.class)) {
Authentication existingAuth = mock(Authentication.class);
when(securityContext.getAuthentication()).thenReturn(existingAuth);
mockedSecurityContextHolder.when(SecurityContextHolder::getContext).thenReturn(securityContext);
when(jwtService.generateToken(existingAuth)).thenReturn(newToken);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
verify(userDetailsService, never()).loadUserByUsername(anyString());
verify(jwtService).generateToken(existingAuth);
verify(jwtService).addTokenToResponse(response, newToken);
verify(filterChain).doFilter(request, response);
}
}
@Test
void testShouldNotFilterLoginPost() {
when(request.getRequestURI()).thenReturn("/login"); when(request.getRequestURI()).thenReturn("/login");
when(request.getMethod()).thenReturn("POST"); when(request.getMethod()).thenReturn("POST");
@ -264,7 +209,7 @@ class JWTAuthenticationFilterTest {
} }
@Test @Test
void testShouldNotFilterLoginGet() { void shouldNotFilterLoginGet() {
when(request.getRequestURI()).thenReturn("/login"); when(request.getRequestURI()).thenReturn("/login");
when(request.getMethod()).thenReturn("GET"); when(request.getMethod()).thenReturn("GET");
@ -272,7 +217,7 @@ class JWTAuthenticationFilterTest {
} }
@Test @Test
void testShouldNotFilterPublicPaths() { void shouldNotFilterPublicPaths() {
String[] publicPaths = { String[] publicPaths = {
"/register", "/register",
"/error", "/error",
@ -298,7 +243,7 @@ class JWTAuthenticationFilterTest {
} }
@Test @Test
void testShouldNotFilterStaticFiles() { void shouldNotFilterStaticFiles() {
String[] staticFiles = { String[] staticFiles = {
"/some/path/file.svg", "/some/path/file.svg",
"/another/path/image.png", "/another/path/image.png",
@ -315,7 +260,7 @@ class JWTAuthenticationFilterTest {
} }
@Test @Test
void testShouldFilterProtectedPaths() { void shouldFilterProtectedPaths() {
String[] protectedPaths = { String[] protectedPaths = {
"/protected", "/protected",
"/api/v1/user/profile", "/api/v1/user/profile",
@ -333,7 +278,7 @@ class JWTAuthenticationFilterTest {
} }
@Test @Test
void testShouldFilterRootPath() { void shouldFilterRootPath() {
when(request.getRequestURI()).thenReturn("/"); when(request.getRequestURI()).thenReturn("/");
when(request.getMethod()).thenReturn("GET"); when(request.getMethod()).thenReturn("GET");
@ -341,23 +286,17 @@ class JWTAuthenticationFilterTest {
} }
@Test @Test
void testSendUnauthorizedResponseFormat() throws ServletException, IOException { void testAuthenticationEntryPointCalledWithCorrectException() throws ServletException, IOException {
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
when(request.getRequestURI()).thenReturn("/protected"); when(request.getRequestURI()).thenReturn("/protected");
when(request.getMethod()).thenReturn("GET"); when(request.getMethod()).thenReturn("GET");
when(jwtService.extractTokenFromRequest(request)).thenReturn(null); when(jwtService.extractTokenFromRequest(request)).thenReturn(null);
when(response.getWriter()).thenReturn(printWriter);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
verify(response).setStatus(401); verify(authenticationEntryPoint).commence(eq(request), eq(response), argThat(exception ->
verify(response).setContentType("application/json"); exception.getMessage().equals("JWT is missing from the request")
verify(response).setCharacterEncoding("UTF-8");
verify(printWriter).write(argThat((String json) ->
json.contains("\"error\": \"Unauthorized\"") &&
json.contains("\"message\": \"JWT is missing from the request\"") &&
json.contains("\"status\": 401")
)); ));
verify(printWriter).flush(); verify(filterChain, never()).doFilter(request, response);
} }
} }

View File

@ -204,20 +204,16 @@ class JWTServiceTest {
String token = jwtService.generateToken("testuser", new HashMap<>()); String token = jwtService.generateToken("testuser", new HashMap<>());
assertFalse(jwtService.isTokenExpired(token)); assertFalse(jwtService.isTokenExpired(token));
// Create a token that expires immediately
when(jwtProperties.getExpiration()).thenReturn(1L); when(jwtProperties.getExpiration()).thenReturn(1L);
JWTService shortLivedJwtService = new JWTService(securityProperties); JWTService shortLivedJwtService = new JWTService(securityProperties);
String expiredToken = shortLivedJwtService.generateToken("testuser", new HashMap<>()); String expiredToken = shortLivedJwtService.generateToken("testuser", new HashMap<>());
// Wait a bit to ensure expiration
try { try {
Thread.sleep(10); Thread.sleep(10);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
// Since expired tokens now throw exceptions in extractAllClaimsFromToken,
// isTokenExpired will also throw an exception
assertThrows(AuthenticationFailureException.class, () -> { assertThrows(AuthenticationFailureException.class, () -> {
shortLivedJwtService.isTokenExpired(expiredToken); shortLivedJwtService.isTokenExpired(expiredToken);
}); });