This commit is contained in:
DarioGii 2025-07-09 16:28:44 +01:00 committed by Dario Ghunney Ware
parent 13e985657f
commit 2fb60eee8d
5 changed files with 73 additions and 28 deletions

View File

@ -10,7 +10,6 @@ import java.util.Properties;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -151,9 +150,8 @@ public class AppConfig {
@Bean(name = "activeSecurity") @Bean(name = "activeSecurity")
public boolean missingActiveSecurity() { public boolean missingActiveSecurity() {
return ClassUtils.isPresent( return ClassUtils.isPresent(
"stirling.software.proprietary.security.configuration.SecurityConfiguration", "stirling.software.proprietary.security.configuration.SecurityConfiguration",
this.getClass().getClassLoader() this.getClass().getClassLoader());
);
} }
@Bean(name = "directoryFilter") @Bean(name = "directoryFilter")

View File

@ -53,17 +53,21 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
String jwtToken = jwtService.extractTokenFromRequest(request); String jwtToken = jwtService.extractTokenFromRequest(request);
if (jwtToken == null) { if (jwtToken == null) {
// Special handling for root path - redirect to login instead of 401 // Redirect to /login instead of 401
if ("/".equals(request.getRequestURI()) if ("/".equals(request.getRequestURI())
&& "GET".equalsIgnoreCase(request.getMethod())) { && "GET".equalsIgnoreCase(request.getMethod())) {
response.sendRedirect("/login"); response.sendRedirect("/login");
return; return;
} }
throw new AuthenticationFailureException("JWT is missing from request"); sendUnauthorizedResponse(response, "JWT is missing from the request");
return;
} }
if (!jwtService.validateToken(jwtToken)) { try {
throw new AuthenticationFailureException("JWT is invalid or expired"); jwtService.validateToken(jwtToken);
} catch (AuthenticationFailureException e) {
sendUnauthorizedResponse(response, e.getMessage());
return;
} }
String tokenUsername = jwtService.extractUsername(jwtToken); String tokenUsername = jwtService.extractUsername(jwtToken);
@ -134,4 +138,27 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
return false; return false;
} }
private void sendUnauthorizedResponse(HttpServletResponse response, String message)
throws IOException {
int unauthorized = HttpServletResponse.SC_UNAUTHORIZED;
response.setStatus(unauthorized);
response.setContentType("application/json");
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

@ -6,4 +6,8 @@ public class AuthenticationFailureException extends AuthenticationException {
public AuthenticationFailureException(String message) { public AuthenticationFailureException(String message) {
super(message); super(message);
} }
public AuthenticationFailureException(String message, Throwable cause) {
super(message, cause);
}
} }

View File

@ -26,6 +26,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties;
import stirling.software.proprietary.security.model.exception.AuthenticationFailureException;
@Slf4j @Slf4j
@Service @Service
@ -69,27 +70,29 @@ public class JWTService implements JWTServiceInterface {
} }
@Override @Override
public boolean validateToken(String token) { public void validateToken(String token) {
if (!isJwtEnabled()) { if (!isJwtEnabled()) {
return false; throw new IllegalStateException("JWT is not enabled");
} }
try { try {
Jwts.parser().verifyWith(keyPair.getPublic()).build().parseSignedClaims(token); extractAllClaimsFromToken(token);
return true;
} catch (SignatureException e) { } catch (SignatureException e) {
log.warn("Invalid JWT signature: {}", e.getMessage()); log.warn("Invalid signature: {}", e.getMessage());
throw new AuthenticationFailureException("Invalid signature", e);
} catch (MalformedJwtException e) { } catch (MalformedJwtException e) {
log.warn("Invalid JWT token: {}", e.getMessage()); log.warn("Invalid token: {}", e.getMessage());
throw new AuthenticationFailureException("Invalid token", e);
} catch (ExpiredJwtException e) { } catch (ExpiredJwtException e) {
log.warn("JWT token is expired: {}", e.getMessage()); log.warn("The token has expired: {}", e.getMessage());
throw new AuthenticationFailureException("The token has expired", e);
} catch (UnsupportedJwtException e) { } catch (UnsupportedJwtException e) {
log.warn("JWT token is unsupported: {}", e.getMessage()); log.warn("The token is unsupported: {}", e.getMessage());
throw new AuthenticationFailureException("The token is unsupported", e);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.warn("JWT claims string is empty: {}", e.getMessage()); log.warn("Claims are empty: {}", e.getMessage());
throw new AuthenticationFailureException("Claims are empty", e);
} }
return false;
} }
@Override @Override
@ -118,15 +121,28 @@ public class JWTService implements JWTServiceInterface {
} }
private Claims extractAllClaimsFromToken(String token) { private Claims extractAllClaimsFromToken(String token) {
if (!isJwtEnabled()) { try {
throw new IllegalStateException("JWT is not enabled"); return Jwts.parser()
.verifyWith(keyPair.getPublic())
.build()
.parseSignedClaims(token)
.getPayload();
} catch (SignatureException e) {
log.warn("Invalid JWT signature: {}", e.getMessage());
throw new AuthenticationFailureException("Invalid JWT signature", e);
} catch (MalformedJwtException e) {
log.warn("Invalid JWT token: {}", e.getMessage());
throw new AuthenticationFailureException("Invalid JWT token", e);
} catch (ExpiredJwtException e) {
log.warn("JWT token is expired: {}", e.getMessage());
throw new AuthenticationFailureException("JWT token is expired", e);
} catch (UnsupportedJwtException e) {
log.warn("JWT token is unsupported: {}", e.getMessage());
throw new AuthenticationFailureException("JWT token is unsupported", e);
} catch (IllegalArgumentException e) {
log.warn("JWT claims are empty: {}", e.getMessage());
throw new AuthenticationFailureException("JWT claims are empty", e);
} }
return Jwts.parser()
.verifyWith(keyPair.getPublic())
.build()
.parseSignedClaims(token)
.getPayload();
} }
@Override @Override

View File

@ -34,7 +34,7 @@ public interface JWTServiceInterface {
* @param token the JWT token to validate * @param token the JWT token to validate
* @return true if token is valid, false otherwise * @return true if token is valid, false otherwise
*/ */
boolean validateToken(String token); void validateToken(String token);
/** /**
* Extract username from JWT token * Extract username from JWT token