119 lines
4.9 KiB
Java
119 lines
4.9 KiB
Java
package stirling.software.SPDF.config.security;
|
|
|
|
import java.io.IOException;
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Qualifier;
|
|
import org.springframework.context.annotation.Lazy;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.security.core.Authentication;
|
|
import org.springframework.security.core.AuthenticationException;
|
|
import org.springframework.security.core.context.SecurityContextHolder;
|
|
import org.springframework.security.core.userdetails.UserDetails;
|
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.filter.OncePerRequestFilter;
|
|
|
|
import jakarta.servlet.FilterChain;
|
|
import jakarta.servlet.ServletException;
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
import jakarta.servlet.http.HttpServletResponse;
|
|
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
|
|
|
@Component
|
|
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|
|
|
@Autowired private UserDetailsService userDetailsService;
|
|
|
|
@Autowired @Lazy private UserService userService;
|
|
|
|
@Autowired
|
|
@Qualifier("loginEnabled")
|
|
public boolean loginEnabledValue;
|
|
|
|
@Override
|
|
protected void doFilterInternal(
|
|
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
|
throws ServletException, IOException {
|
|
|
|
if (!loginEnabledValue) {
|
|
// If login is not enabled, just pass all requests without authentication
|
|
filterChain.doFilter(request, response);
|
|
return;
|
|
}
|
|
String requestURI = request.getRequestURI();
|
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
|
|
// Check for API key in the request headers if no authentication exists
|
|
if (authentication == null || !authentication.isAuthenticated()) {
|
|
String apiKey = request.getHeader("X-API-Key");
|
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
|
try {
|
|
// Use API key to authenticate. This requires you to have an authentication
|
|
// provider for API keys.
|
|
UserDetails userDetails = userService.loadUserByApiKey(apiKey);
|
|
if (userDetails == null) {
|
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
|
response.getWriter().write("Invalid API Key.");
|
|
return;
|
|
}
|
|
authentication =
|
|
new ApiKeyAuthenticationToken(
|
|
userDetails, apiKey, userDetails.getAuthorities());
|
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
} catch (AuthenticationException e) {
|
|
// If API key authentication fails, deny the request
|
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
|
response.getWriter().write("Invalid API Key.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we still don't have any authentication, deny the request
|
|
if (authentication == null || !authentication.isAuthenticated()) {
|
|
String method = request.getMethod();
|
|
String contextPath = request.getContextPath();
|
|
|
|
if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) {
|
|
response.sendRedirect(contextPath + "/login"); // redirect to the login page
|
|
return;
|
|
} else {
|
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
|
response.getWriter()
|
|
.write(
|
|
"Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternatively you can disable authentication if this is unexpected");
|
|
return;
|
|
}
|
|
}
|
|
|
|
filterChain.doFilter(request, response);
|
|
}
|
|
|
|
@Override
|
|
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
|
String uri = request.getRequestURI();
|
|
String contextPath = request.getContextPath();
|
|
String[] permitAllPatterns = {
|
|
contextPath + "/login",
|
|
contextPath + "/register",
|
|
contextPath + "/error",
|
|
contextPath + "/images/",
|
|
contextPath + "/public/",
|
|
contextPath + "/css/",
|
|
contextPath + "/js/",
|
|
contextPath + "/pdfjs/",
|
|
contextPath + "/api/v1/info/status",
|
|
contextPath + "/site.webmanifest"
|
|
};
|
|
|
|
for (String pattern : permitAllPatterns) {
|
|
if (uri.startsWith(pattern) || uri.endsWith(".svg")) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|