extends the functionality of oauth in Stirling PDF

This commit is contained in:
Ludy87
2024-05-12 19:58:34 +02:00
parent f2b7aeeb1c
commit 811c19e00d
49 changed files with 724 additions and 210 deletions

View File

@@ -0,0 +1,40 @@
package stirling.software.SPDF.config.security.oauth2;
import java.io.IOException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class CustomOAuth2AuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
if (exception instanceof OAuth2AuthenticationException) {
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
getRedirectStrategy()
.sendRedirect(request, response, "/login?error=oAuth::" + error.getErrorCode());
} else if (exception instanceof LockedException) {
getRedirectStrategy().sendRedirect(request, response, "/login?error=locked");
} else if (exception instanceof UsernameNotFoundException) {
getRedirectStrategy()
.sendRedirect(request, response, "/login?error=oauth2AuthenticationError");
} else {
super.onAuthenticationFailure(request, response, exception);
}
}
}

View File

@@ -0,0 +1,74 @@
package stirling.software.SPDF.config.security.oauth2;
import java.io.IOException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.utils.RequestUriUtils;
@Component
public class CustomOAuth2AuthenticationSuccessHandler
extends SavedRequestAwareAuthenticationSuccessHandler {
ApplicationProperties applicationProperties;
UserService userService;
public CustomOAuth2AuthenticationSuccessHandler(
ApplicationProperties applicationProperties, UserService userService) {
this.applicationProperties = applicationProperties;
this.userService = userService;
}
@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
OAuth2User oauthUser = (OAuth2User) authentication.getPrincipal();
// Get the saved request
HttpSession session = request.getSession(false);
SavedRequest savedRequest =
session != null
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
: null;
if (savedRequest != null
&& !RequestUriUtils.isStaticResource(savedRequest.getRedirectUrl())) {
// Redirect to the original destination
super.onAuthenticationSuccess(request, response, authentication);
} else {
OAUTH2 oAuth = applicationProperties.getSecurity().getOAUTH2();
String username = oauthUser.getAttribute(oAuth.getUseAsUsername());
if (userService.usernameExistsIgnoreCase(username)
&& userService.hasPassword(username)
&& !userService.isAuthenticationTypeByUsername(
username, AuthenticationType.OAUTH2)
&& oAuth.getAutoCreateUser()) {
response.sendRedirect(
request.getContextPath() + "/logout?oauth2AuthenticationError=true");
return;
} else {
try {
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
response.sendRedirect("/");
return;
} catch (IllegalArgumentException e) {
response.sendRedirect("/logout?invalidUsername=true");
return;
}
}
}
}
}

View File

@@ -0,0 +1,87 @@
package stirling.software.SPDF.config.security.oauth2;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
@Component
public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
@Autowired SessionRegistry sessionRegistry;
private ApplicationProperties applicationProperties;
public CustomOAuth2LogoutSuccessHandler(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@Override
public void onLogoutSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
boolean isOAuthUser = true;
String param = "logout=true";
if (authentication == null) {
response.sendRedirect("/");
return;
}
Object pri = authentication.getPrincipal();
if (pri instanceof UserDetails) {
UserDetails userDetails = (UserDetails) pri;
isOAuthUser = userDetails.getPassword() == null;
} else if (pri instanceof OAuth2User) {
isOAuthUser = true;
}
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
String provider = oauth.getProvider() != null && isOAuthUser ? oauth.getProvider() : "";
if (request.getParameter("oauth2AuthenticationError") != null) {
param = "error=oauth2AuthenticationError";
} else if (request.getParameter("invalidUsername") != null) {
param = "error=invalidUsername";
}
HttpSession session = request.getSession(false);
if (session != null) {
String sessionId = session.getId();
sessionRegistry.removeSessionInformation(sessionId);
}
switch (provider) {
case "keycloak":
String logoutUrl =
oauth.getIssuer()
+ "/protocol/openid-connect/logout"
+ "?client_id="
+ oauth.getClientId()
+ "&post_logout_redirect_uri="
+ response.encodeRedirectURL(
"http://" + request.getHeader("host") + "/login?" + param);
response.sendRedirect(logoutUrl);
break;
case "google":
default:
if (request.getParameter("oauth2AutoCreateDisabled") != null) {
response.sendRedirect(
request.getContextPath() + "/login?error=oauth2AutoCreateDisabled");
} else {
response.sendRedirect(request.getContextPath() + "/login?logout=true");
}
break;
}
}
}

View File

@@ -0,0 +1,52 @@
package stirling.software.SPDF.config.security.oauth2;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import stirling.software.SPDF.model.ApplicationProperties;
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
private final OidcUserService delegate = new OidcUserService();
private ApplicationProperties applicationProperties;
public CustomOAuth2UserService(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
String usernameAttribute =
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername();
try {
OidcUser user = delegate.loadUser(userRequest);
Map<String, Object> attributes = new HashMap<>(user.getAttributes());
// Ensure the preferred username attribute is present
if (!attributes.containsKey(usernameAttribute)) {
attributes.put(usernameAttribute, attributes.getOrDefault("email", ""));
usernameAttribute = "email";
}
// Return a new OidcUser with adjusted attributes
return new DefaultOidcUser(
user.getAuthorities(),
userRequest.getIdToken(),
user.getUserInfo(),
usernameAttribute);
} catch (java.lang.IllegalArgumentException e) {
throw new OAuth2AuthenticationException(
new OAuth2Error(e.getMessage()), e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,57 @@
package stirling.software.SPDF.config.security.oauth2;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import stirling.software.SPDF.model.ApplicationProperties;
public class CustomOAuthUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
private static final Logger logger = LoggerFactory.getLogger(CustomOAuthUserService.class);
private final OidcUserService delegate = new OidcUserService();
private ApplicationProperties applicationProperties;
public CustomOAuthUserService(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
String usernameAttribute =
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername();
try {
OidcUser user = delegate.loadUser(userRequest);
Map<String, Object> attributes = new HashMap<>(user.getAttributes());
// Ensure the preferred username attribute is present
if (!attributes.containsKey(usernameAttribute)) {
attributes.put(usernameAttribute, attributes.getOrDefault("email", ""));
usernameAttribute = "email";
logger.info("Adjusted username attribute to use email");
}
// Return a new OidcUser with adjusted attributes
return new DefaultOidcUser(
user.getAuthorities(),
userRequest.getIdToken(),
user.getUserInfo(),
usernameAttribute);
} catch (java.lang.IllegalArgumentException e) {
throw new OAuth2AuthenticationException(
new OAuth2Error(e.getMessage()), e.getMessage(), e);
}
}
}