Added SSOAutoLogin functionality to SAML 2 logins

This commit is contained in:
Dario Ghunney Ware 2025-02-19 16:22:15 +00:00
parent 8a9886c821
commit 264de0bbd7
5 changed files with 33 additions and 44 deletions

View File

@ -19,6 +19,7 @@ import stirling.software.SPDF.utils.GeneralUtils;
@Service
@Slf4j
public class KeygenLicenseVerifier {
// todo: place in config files?
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
private static final ObjectMapper objectMapper = new ObjectMapper();
@ -67,7 +68,7 @@ public class KeygenLicenseVerifier {
return false;
} catch (Exception e) {
log.error("Error verifying license: " + e.getMessage());
log.error("Error verifying license: {}", e.getMessage());
return false;
}
}
@ -94,10 +95,9 @@ public class KeygenLicenseVerifier {
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
log.debug(" validateLicenseResponse body: " + response.body());
log.debug("ValidateLicenseResponse body: {}", response.body());
JsonNode jsonResponse = objectMapper.readTree(response.body());
if (response.statusCode() == 200) {
JsonNode metaNode = jsonResponse.path("meta");
boolean isValid = metaNode.path("valid").asBoolean();
@ -119,7 +119,7 @@ public class KeygenLicenseVerifier {
log.info(applicationProperties.toString());
} else {
log.error("Error validating license. Status code: " + response.statusCode());
log.error("Error validating license. Status code: {}", response.statusCode());
}
return jsonResponse;
}

View File

@ -1,7 +1,7 @@
package stirling.software.SPDF.config.security;
import java.util.*;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
@ -10,7 +10,6 @@ import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
@ -23,16 +22,10 @@ import org.springframework.security.saml2.provider.service.web.authentication.Op
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.context.DelegatingSecurityContextRepository;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
@ -47,7 +40,6 @@ import stirling.software.SPDF.repository.PersistentLoginRepository;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@Slf4j
@DependsOn("runningEE")
public class SecurityConfiguration {
@ -104,7 +96,7 @@ public class SecurityConfiguration {
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, SecurityContextRepository securityContextRepository) throws Exception {
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
http.csrf(csrf -> csrf.disable());
}
@ -264,13 +256,6 @@ public class SecurityConfiguration {
authenticationProvider.setResponseAuthenticationConverter(
new CustomSaml2ResponseAuthenticationConverter(userService));
http.authenticationProvider(authenticationProvider)
.securityContext(security ->
security.securityContextRepository(
new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository())
)
)
.saml2Login(
saml2 -> {
try {

View File

@ -27,6 +27,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.SPDFApplication;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApplicationProperties;
@ -121,7 +122,11 @@ public class AccountWebController {
String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId();
if (applicationProperties.getEnterpriseEdition().isSsoAutoLogin()) {
return "redirect:login" + saml2AuthenticationPath;
return "redirect:"
+ SPDFApplication.getStaticBaseUrl()
+ ":"
+ SPDFApplication.getStaticPort()
+ saml2AuthenticationPath;
} else {
providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)");
}

View File

@ -108,7 +108,7 @@ public class ApplicationProperties {
private int loginAttemptCount;
private long loginResetTimeMinutes;
private String loginMethod = "all";
private String customGlobalAPIKey; // todo: expose?
private String customGlobalAPIKey;
public Boolean isAltLogin() {
return saml2.getEnabled() || oauth2.getEnabled();

View File

@ -12,7 +12,7 @@
security:
enableLogin: true # set to 'true' to enable login
enableLogin: false # set to 'true' to enable login
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
@ -39,33 +39,32 @@ security:
clientSecret: '' # client secret for GitHub OAuth2
scopes: read:user # scope for GitHub OAuth2
useAsUsername: login # field to use as the username for GitHub OAuth2. Available options are: [email | login | name]
issuer: https://trial-6373896.okta.com/home/okta_flow_sso/0oaok4lk1nVvNBnqK697/alnbibn6b0OPFATt20g7 # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
clientId: 0oaok4lk4eNm6PtFD697 # client ID from your provider
clientSecret: lmwlmxFZSJ0miOoRpUAKf2jg8tVPPXhUxgL2VB-b4uJfhnk4sI02YodKWRX8fLSq # client secret from your provider
logoutUrl: ''
issuer: '' # set to any Provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) endpoint
clientId: '' # client ID from your Provider
clientSecret: '' # client secret from your Provider
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
useAsUsername: username # default is 'email'; custom fields can be used as the username
scopes: okta.users.read, okta.users.read.self, okta.users.manage.self, okta.groups.read # specify the scopes for which the application will request permissions
provider: google # set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
useAsUsername: email # default is 'email'; custom fields can be used as the username
scopes: openid, profile, email # specify the scopes for which the application will request permissions
provider: google # set this to your OAuth Provider's name, e.g., 'google' or 'keycloak'
saml2:
enabled: true # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true)
provider: okta
enabled: false # Only enabled for paid enterprise clients (enterpriseEdition.enabled must be true)
provider: '' The name of your Provider
autoCreateUser: true # set to 'true' to allow auto-creation of non-existing users
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
registrationId: stirlingpdf-dario-saml
idpMetadataUri: https://trial-6373896.okta.com/app/exkomkf71reALy12X697/sso/saml/metadata # todo: remove
idpSingleLoginUrl: https://trial-6373896.okta.com/app/trial-6373896_stirlingpdfsaml2_1/exkoot0g5ipqOF3Bo697/sso/saml # todo: remove
idpSingleLogoutUrl: https://trial-6373896.okta.com/app/trial-6373896_stirlingpdfsaml2_1/exkoot0g5ipqOF3Bo697/slo/saml # todo: remove
idpIssuer: http://www.okta.com/exkoot0g5ipqOF3Bo697
registrationId: stirling
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata
idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml
idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml
idpIssuer: ''
idpCert: classpath:okta.cert
privateKey: classpath:private_key.key
spCert: classpath:certificate.crt
privateKey: classpath:saml-private-key.key
spCert: classpath:saml-public-cert.crt
enterpriseEdition:
enabled: true # set to 'true' to enable enterprise edition
enabled: false # set to 'true' to enable enterprise edition
key: 00000000-0000-0000-0000-000000000000
SSOAutoLogin: true # Enable to auto login to first provided SSO
SSOAutoLogin: false # Enable to auto login to first provided SSO
CustomMetadata:
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username
@ -82,7 +81,7 @@ legal:
system:
defaultLocale: en-US # set the default language (e.g. 'de-DE', 'fr-FR', etc)
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
enableAlphaFunctionality: true # set to enable functionality which might need more testing before it fully goes live (this feature might make no changes)
enableAlphaFunctionality: false # set to enable functionality which might need more testing before it fully goes live (this feature might make no changes)
showUpdate: false # see when a new update is available
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files