mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-22 23:45:02 +00:00
saml test
This commit is contained in:
parent
2989d8d416
commit
a875ae3034
13
build.gradle
13
build.gradle
@ -32,6 +32,12 @@ java {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
|
maven {
|
||||||
|
url "https://build.shibboleth.net/nexus/content/repositories/releases/"
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url "https://build.shibboleth.net/maven/releases/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseReport {
|
licenseReport {
|
||||||
@ -114,6 +120,10 @@ configurations.all {
|
|||||||
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//security updates
|
//security updates
|
||||||
implementation "org.springframework:spring-webmvc:6.1.9"
|
implementation "org.springframework:spring-webmvc:6.1.9"
|
||||||
|
|
||||||
@ -128,7 +138,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
||||||
|
|
||||||
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
||||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
||||||
runtimeOnly "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
|
runtimeOnly "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
||||||
@ -137,6 +147,7 @@ dependencies {
|
|||||||
//2.2.x requires rebuild of DB file.. need migration path
|
//2.2.x requires rebuild of DB file.. need migration path
|
||||||
runtimeOnly "com.h2database:h2:2.1.214"
|
runtimeOnly "com.h2database:h2:2.1.214"
|
||||||
// implementation "com.h2database:h2:2.2.224"
|
// implementation "com.h2database:h2:2.2.224"
|
||||||
|
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.3.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||||
|
@ -6,37 +6,20 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
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;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
|
||||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutSuccessHandler;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutSuccessHandler;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestValidator;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestValidator;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseValidator;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseValidator;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
@ -47,19 +30,11 @@ import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationF
|
|||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||||
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler;
|
|
||||||
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.saml.SAMLUserDetailsService;
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.saml.SAMLConfig;
|
|
||||||
import stirling.software.SPDF.config.security.saml.SAMLLogoutSuccessHandler;
|
import stirling.software.SPDF.config.security.saml.SAMLLogoutSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
|
||||||
import stirling.software.SPDF.model.User;
|
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -69,6 +44,15 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
@Autowired private CustomUserDetailsService userDetailsService;
|
@Autowired private CustomUserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private GrantedAuthoritiesMapper userAuthoritiesMapper;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private OpenSaml4AuthenticationProvider samlAuthenticationProvider;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
|
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -153,6 +137,7 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
return trimmedUri.startsWith("/login")
|
return trimmedUri.startsWith("/login")
|
||||||
|| trimmedUri.startsWith("/oauth")
|
|| trimmedUri.startsWith("/oauth")
|
||||||
|
|| trimmedUri.startsWith("/saml2")
|
||||||
|| trimmedUri.endsWith(".svg")
|
|| trimmedUri.endsWith(".svg")
|
||||||
|| trimmedUri.startsWith(
|
|| trimmedUri.startsWith(
|
||||||
"/register")
|
"/register")
|
||||||
@ -202,7 +187,7 @@ public class SecurityConfiguration {
|
|||||||
userService,
|
userService,
|
||||||
loginAttemptService))
|
loginAttemptService))
|
||||||
.userAuthoritiesMapper(
|
.userAuthoritiesMapper(
|
||||||
userAuthoritiesMapper())))
|
userAuthoritiesMapper)))
|
||||||
.logout(
|
.logout(
|
||||||
logout ->
|
logout ->
|
||||||
logout.logoutSuccessHandler(
|
logout.logoutSuccessHandler(
|
||||||
@ -220,13 +205,19 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
http.saml2Login(
|
http.saml2Login(
|
||||||
saml2 ->
|
saml2 ->
|
||||||
saml2.loginPage("/saml2")
|
saml2.relyingPartyRegistrationRepository(
|
||||||
|
relyingPartyRegistrationRepository)
|
||||||
|
.loginProcessingUrl("/login/saml2/sso/stirling")
|
||||||
|
.loginPage("/saml2")
|
||||||
|
.authenticationManager(
|
||||||
|
new ProviderManager(
|
||||||
|
samlAuthenticationProvider))
|
||||||
.successHandler(
|
.successHandler(
|
||||||
new CustomSAMLAuthenticationSuccessHandler(
|
new CustomSAMLAuthenticationSuccessHandler(
|
||||||
loginAttemptService, userService))
|
loginAttemptService, userService))
|
||||||
.failureHandler(
|
.failureHandler(
|
||||||
new CustomSAMLAuthenticationFailureHandler())
|
new CustomSAMLAuthenticationFailureHandler()))
|
||||||
.userDetailsService(new SAMLUserDetailsService()))
|
.saml2Metadata(Customizer.withDefaults())
|
||||||
.logout(
|
.logout(
|
||||||
logout ->
|
logout ->
|
||||||
logout.logoutSuccessHandler(
|
logout.logoutSuccessHandler(
|
||||||
@ -240,178 +231,6 @@ public class SecurityConfiguration {
|
|||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client Registration Repository for OAUTH2 OIDC Login
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
value = "security.oauth2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
|
||||||
|
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
|
||||||
googleClientRegistration().ifPresent(registrations::add);
|
|
||||||
keycloakClientRegistration().ifPresent(registrations::add);
|
|
||||||
|
|
||||||
if (registrations.isEmpty()) {
|
|
||||||
logger.error("At least one OAuth2 provider must be configured");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new InMemoryClientRegistrationRepository(registrations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> googleClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GoogleProvider google = client.getGoogle();
|
|
||||||
return google != null && google.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(google.getName())
|
|
||||||
.clientId(google.getClientId())
|
|
||||||
.clientSecret(google.getClientSecret())
|
|
||||||
.scope(google.getScopes())
|
|
||||||
.authorizationUri(google.getAuthorizationuri())
|
|
||||||
.tokenUri(google.getTokenuri())
|
|
||||||
.userInfoUri(google.getUserinfouri())
|
|
||||||
.userNameAttributeName(google.getUseAsUsername())
|
|
||||||
.clientName(google.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
|
||||||
|
|
||||||
return keycloak != null && keycloak.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
|
||||||
.registrationId(keycloak.getName())
|
|
||||||
.clientId(keycloak.getClientId())
|
|
||||||
.clientSecret(keycloak.getClientSecret())
|
|
||||||
.scope(keycloak.getScopes())
|
|
||||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
|
||||||
.clientName(keycloak.getClientName())
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> githubClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GithubProvider github = client.getGithub();
|
|
||||||
return github != null && github.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(github.getName())
|
|
||||||
.clientId(github.getClientId())
|
|
||||||
.clientSecret(github.getClientSecret())
|
|
||||||
.scope(github.getScopes())
|
|
||||||
.authorizationUri(github.getAuthorizationuri())
|
|
||||||
.tokenUri(github.getTokenuri())
|
|
||||||
.userInfoUri(github.getUserinfouri())
|
|
||||||
.userNameAttributeName(github.getUseAsUsername())
|
|
||||||
.clientName(github.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
|
||||||
if (oauth == null
|
|
||||||
|| oauth.getIssuer() == null
|
|
||||||
|| oauth.getIssuer().isEmpty()
|
|
||||||
|| oauth.getClientId() == null
|
|
||||||
|| oauth.getClientId().isEmpty()
|
|
||||||
|| oauth.getClientSecret() == null
|
|
||||||
|| oauth.getClientSecret().isEmpty()
|
|
||||||
|| oauth.getScopes() == null
|
|
||||||
|| oauth.getScopes().isEmpty()
|
|
||||||
|| oauth.getUseAsUsername() == null
|
|
||||||
|| oauth.getUseAsUsername().isEmpty()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
|
||||||
.registrationId("oidc")
|
|
||||||
.clientId(oauth.getClientId())
|
|
||||||
.clientSecret(oauth.getClientSecret())
|
|
||||||
.scope(oauth.getScopes())
|
|
||||||
.userNameAttributeName(oauth.getUseAsUsername())
|
|
||||||
.clientName("OIDC")
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
|
||||||
This is required for the internal; 'hasRole()' function to give out the correct role.
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(
|
|
||||||
value = "security.oauth2.enabled",
|
|
||||||
havingValue = "true",
|
|
||||||
matchIfMissing = false)
|
|
||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
|
||||||
return (authorities) -> {
|
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
|
||||||
|
|
||||||
authorities.forEach(
|
|
||||||
authority -> {
|
|
||||||
// Add existing OAUTH2 Authorities
|
|
||||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
|
||||||
|
|
||||||
// Add Authorities from database for existing user, if user is present.
|
|
||||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
|
||||||
String useAsUsername =
|
|
||||||
applicationProperties
|
|
||||||
.getSecurity()
|
|
||||||
.getOAUTH2()
|
|
||||||
.getUseAsUsername();
|
|
||||||
Optional<User> userOpt =
|
|
||||||
userService.findByUsernameIgnoreCase(
|
|
||||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
|
||||||
if (userOpt.isPresent()) {
|
|
||||||
User user = userOpt.get();
|
|
||||||
if (user != null) {
|
|
||||||
mappedAuthorities.add(
|
|
||||||
new SimpleGrantedAuthority(
|
|
||||||
userService.findRole(user).getAuthority()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return mappedAuthorities;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IPRateLimitingFilter rateLimitingFilter() {
|
public IPRateLimitingFilter rateLimitingFilter() {
|
||||||
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
||||||
@ -427,42 +246,4 @@ public class SecurityConfiguration {
|
|||||||
public boolean activSecurity() {
|
public boolean activSecurity() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAML Configuration
|
|
||||||
@Bean
|
|
||||||
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
|
|
||||||
RelyingPartyRegistration registration = RelyingPartyRegistration
|
|
||||||
.withRegistrationId("saml")
|
|
||||||
.entityId(applicationProperties.getSecurity().getSAML().getEntityId())
|
|
||||||
.assertionConsumerServiceLocation(applicationProperties.getSecurity().getSAML().getSpBaseUrl() + "/saml2/acs")
|
|
||||||
.singleLogoutServiceLocation(applicationProperties.getSecurity().getSAML().getSpBaseUrl() + "/saml2/logout")
|
|
||||||
.idpWebSsoUrl(applicationProperties.getSecurity().getSAML().getIdpMetadataLocation())
|
|
||||||
.build();
|
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutRequestRepository logoutRequestRepository() {
|
|
||||||
return new OpenSaml4LogoutRequestRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutResponseRepository logoutResponseRepository() {
|
|
||||||
return new OpenSaml4LogoutResponseRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutSuccessHandler logoutSuccessHandler() {
|
|
||||||
return new OpenSaml4LogoutSuccessHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutRequestValidator logoutRequestValidator() {
|
|
||||||
return new OpenSaml4LogoutRequestValidator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutResponseValidator logoutResponseValidator() {
|
|
||||||
return new OpenSaml4LogoutResponseValidator();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
@ -13,6 +17,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
@ -23,10 +28,6 @@ import stirling.software.SPDF.model.User;
|
|||||||
import stirling.software.SPDF.repository.AuthorityRepository;
|
import stirling.software.SPDF.repository.AuthorityRepository;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class UserService implements UserServiceInterface {
|
public class UserService implements UserServiceInterface {
|
||||||
|
|
||||||
|
@ -0,0 +1,211 @@
|
|||||||
|
package stirling.software.SPDF.config.security.oauth2;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
||||||
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
|
import stirling.software.SPDF.model.User;
|
||||||
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class OAuth2Config {
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Autowired @Lazy private UserService userService;
|
||||||
|
|
||||||
|
// Client Registration Repository for OAUTH2 OIDC Login
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||||
|
List<ClientRegistration> registrations = new ArrayList<>();
|
||||||
|
|
||||||
|
githubClientRegistration().ifPresent(registrations::add);
|
||||||
|
oidcClientRegistration().ifPresent(registrations::add);
|
||||||
|
googleClientRegistration().ifPresent(registrations::add);
|
||||||
|
keycloakClientRegistration().ifPresent(registrations::add);
|
||||||
|
|
||||||
|
if (registrations.isEmpty()) {
|
||||||
|
log.error("At least one OAuth2 provider must be configured");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new InMemoryClientRegistrationRepository(registrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> googleClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
GoogleProvider google = client.getGoogle();
|
||||||
|
return google != null && google.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId(google.getName())
|
||||||
|
.clientId(google.getClientId())
|
||||||
|
.clientSecret(google.getClientSecret())
|
||||||
|
.scope(google.getScopes())
|
||||||
|
.authorizationUri(google.getAuthorizationuri())
|
||||||
|
.tokenUri(google.getTokenuri())
|
||||||
|
.userInfoUri(google.getUserinfouri())
|
||||||
|
.userNameAttributeName(google.getUseAsUsername())
|
||||||
|
.clientName(google.getClientName())
|
||||||
|
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
||||||
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
|
|
||||||
|
return keycloak != null && keycloak.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||||
|
.registrationId(keycloak.getName())
|
||||||
|
.clientId(keycloak.getClientId())
|
||||||
|
.clientSecret(keycloak.getClientSecret())
|
||||||
|
.scope(keycloak.getScopes())
|
||||||
|
.userNameAttributeName(keycloak.getUseAsUsername())
|
||||||
|
.clientName(keycloak.getClientName())
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> githubClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
GithubProvider github = client.getGithub();
|
||||||
|
return github != null && github.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId(github.getName())
|
||||||
|
.clientId(github.getClientId())
|
||||||
|
.clientSecret(github.getClientSecret())
|
||||||
|
.scope(github.getScopes())
|
||||||
|
.authorizationUri(github.getAuthorizationuri())
|
||||||
|
.tokenUri(github.getTokenuri())
|
||||||
|
.userInfoUri(github.getUserinfouri())
|
||||||
|
.userNameAttributeName(github.getUseAsUsername())
|
||||||
|
.clientName(github.getClientName())
|
||||||
|
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
||||||
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> oidcClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
|
if (oauth == null
|
||||||
|
|| oauth.getIssuer() == null
|
||||||
|
|| oauth.getIssuer().isEmpty()
|
||||||
|
|| oauth.getClientId() == null
|
||||||
|
|| oauth.getClientId().isEmpty()
|
||||||
|
|| oauth.getClientSecret() == null
|
||||||
|
|| oauth.getClientSecret().isEmpty()
|
||||||
|
|| oauth.getScopes() == null
|
||||||
|
|| oauth.getScopes().isEmpty()
|
||||||
|
|| oauth.getUseAsUsername() == null
|
||||||
|
|| oauth.getUseAsUsername().isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(
|
||||||
|
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||||
|
.registrationId("oidc")
|
||||||
|
.clientId(oauth.getClientId())
|
||||||
|
.clientSecret(oauth.getClientSecret())
|
||||||
|
.scope(oauth.getScopes())
|
||||||
|
.userNameAttributeName(oauth.getUseAsUsername())
|
||||||
|
.clientName("OIDC")
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
||||||
|
This is required for the internal; 'hasRole()' function to give out the correct role.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.oauth2.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||||
|
return (authorities) -> {
|
||||||
|
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||||
|
|
||||||
|
authorities.forEach(
|
||||||
|
authority -> {
|
||||||
|
// Add existing OAUTH2 Authorities
|
||||||
|
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
||||||
|
|
||||||
|
// Add Authorities from database for existing user, if user is present.
|
||||||
|
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
||||||
|
String useAsUsername =
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getOAUTH2()
|
||||||
|
.getUseAsUsername();
|
||||||
|
Optional<User> userOpt =
|
||||||
|
userService.findByUsernameIgnoreCase(
|
||||||
|
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
||||||
|
if (userOpt.isPresent()) {
|
||||||
|
User user = userOpt.get();
|
||||||
|
if (user != null) {
|
||||||
|
mappedAuthorities.add(
|
||||||
|
new SimpleGrantedAuthority(
|
||||||
|
userService.findRole(user).getAuthority()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return mappedAuthorities;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -41,7 +41,8 @@ public class CustomSAMLAuthenticationFailureHandler extends SimpleUrlAuthenticat
|
|||||||
}
|
}
|
||||||
if (exception instanceof Saml2AuthenticationException) {
|
if (exception instanceof Saml2AuthenticationException) {
|
||||||
log.error("SAML2 Authentication error: ", exception);
|
log.error("SAML2 Authentication error: ", exception);
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/logout?error=saml2AuthenticationError");
|
getRedirectStrategy()
|
||||||
|
.sendRedirect(request, response, "/logout?error=saml2AuthenticationError");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.error("Unhandled authentication exception", exception);
|
log.error("Unhandled authentication exception", exception);
|
||||||
|
@ -11,21 +11,26 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.security.LoginAttemptService;
|
||||||
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CustomSAMLAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
|
public class CustomSAMLAuthenticationSuccessHandler
|
||||||
|
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
private LoginAttemptService loginAttemptService;
|
private LoginAttemptService loginAttemptService;
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
public CustomSAMLAuthenticationSuccessHandler(LoginAttemptService loginAttemptService, UserService userService) {
|
public CustomSAMLAuthenticationSuccessHandler(
|
||||||
|
LoginAttemptService loginAttemptService, UserService userService) {
|
||||||
this.loginAttemptService = loginAttemptService;
|
this.loginAttemptService = loginAttemptService;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
public void onAuthenticationSuccess(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
|
|
||||||
String userName = request.getParameter("username");
|
String userName = request.getParameter("username");
|
||||||
@ -37,9 +42,14 @@ public class CustomSAMLAuthenticationSuccessHandler extends SavedRequestAwareAut
|
|||||||
|
|
||||||
// Get the saved request
|
// Get the saved request
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
SavedRequest savedRequest = (session != null) ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") : null;
|
SavedRequest savedRequest =
|
||||||
|
(session != null)
|
||||||
|
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
||||||
|
: null;
|
||||||
|
|
||||||
if (savedRequest != null && !RequestUriUtils.isStaticResource(request.getContextPath(), savedRequest.getRedirectUrl())) {
|
if (savedRequest != null
|
||||||
|
&& !RequestUriUtils.isStaticResource(
|
||||||
|
request.getContextPath(), savedRequest.getRedirectUrl())) {
|
||||||
// Redirect to the original destination
|
// Redirect to the original destination
|
||||||
super.onAuthenticationSuccess(request, response, authentication);
|
super.onAuthenticationSuccess(request, response, authentication);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutSuccessHandler;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutSuccessHandler;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestValidator;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestValidator;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseValidator;
|
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseValidator;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class SAMLConfig {
|
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
public SAMLConfig(ApplicationProperties applicationProperties) {
|
|
||||||
this.applicationProperties = applicationProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
|
|
||||||
RelyingPartyRegistration registration = RelyingPartyRegistration
|
|
||||||
.withRegistrationId("saml")
|
|
||||||
.entityId(applicationProperties.getSecurity().getSAML().getEntityId())
|
|
||||||
.assertionConsumerServiceLocation(applicationProperties.getSecurity().getSAML().getSpBaseUrl() + "/saml2/acs")
|
|
||||||
.singleLogoutServiceLocation(applicationProperties.getSecurity().getSAML().getSpBaseUrl() + "/saml2/logout")
|
|
||||||
.idpWebSsoUrl(applicationProperties.getSecurity().getSAML().getIdpMetadataLocation())
|
|
||||||
.build();
|
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutRequestRepository logoutRequestRepository() {
|
|
||||||
return new OpenSaml4LogoutRequestRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutResponseRepository logoutResponseRepository() {
|
|
||||||
return new OpenSaml4LogoutResponseRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutSuccessHandler logoutSuccessHandler() {
|
|
||||||
return new OpenSaml4LogoutSuccessHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutRequestValidator logoutRequestValidator() {
|
|
||||||
return new OpenSaml4LogoutRequestValidator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2LogoutResponseValidator logoutResponseValidator() {
|
|
||||||
return new OpenSaml4LogoutResponseValidator();
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,7 +29,9 @@ public class SAMLLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected String determineTargetUrl(
|
protected String determineTargetUrl(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
Authentication authentication) {
|
||||||
// Default to the root URL
|
// Default to the root URL
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml;
|
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class SAMLUserDetailsService implements UserDetailsService {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
|
||||||
throw new UnsupportedOperationException("This method is not supported for SAML authentication");
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserDetails loadUserBySAML(Saml2Authentication authentication) {
|
|
||||||
Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal();
|
|
||||||
String username = principal.getName();
|
|
||||||
Collection<? extends GrantedAuthority> authorities = extractAuthorities(principal);
|
|
||||||
|
|
||||||
return new org.springframework.security.core.userdetails.User(username, "", authorities);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<? extends GrantedAuthority> extractAuthorities(Saml2AuthenticatedPrincipal principal) {
|
|
||||||
List<String> roles = principal.getAttribute("roles");
|
|
||||||
return roles.stream()
|
|
||||||
.map(SimpleGrantedAuthority::new)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,216 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
|
||||||
|
import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
|
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
|
||||||
|
import org.springframework.security.saml2.provider.service.web.Saml2MetadataFilter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class SamlConfig {
|
||||||
|
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenSaml4AuthenticationProvider openSaml4AuthenticationProvider() {
|
||||||
|
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
|
||||||
|
provider.setResponseAuthenticationConverter(
|
||||||
|
responseToken -> {
|
||||||
|
Saml2AuthenticationToken token = responseToken.getToken();
|
||||||
|
log.info("Received SAML response: {}", token.getSaml2Response());
|
||||||
|
// Your custom conversion logic here
|
||||||
|
// For now, we'll just return the token as is
|
||||||
|
return token;
|
||||||
|
});
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.saml.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
|
||||||
|
RelyingPartyRegistration registration =
|
||||||
|
RelyingPartyRegistration.withRegistrationId(
|
||||||
|
applicationProperties.getSecurity().getSAML().getRegistrationId())
|
||||||
|
.entityId(applicationProperties.getSecurity().getSAML().getEntityId())
|
||||||
|
.assertionConsumerServiceLocation(
|
||||||
|
applicationProperties.getSecurity().getSAML().getSpBaseUrl()
|
||||||
|
+ "/login/saml2/sso/stirling")
|
||||||
|
.singleLogoutServiceLocation(
|
||||||
|
applicationProperties.getSecurity().getSAML().getSpBaseUrl()
|
||||||
|
+ "/logout/saml2/slo")
|
||||||
|
.singleLogoutServiceResponseLocation(
|
||||||
|
applicationProperties.getSecurity().getSAML().getSpBaseUrl()
|
||||||
|
+ "/logout/saml2/slo")
|
||||||
|
.signingX509Credentials(credentials -> credentials.add(signingCredential()))
|
||||||
|
.assertingPartyDetails(
|
||||||
|
party ->
|
||||||
|
party.entityId(
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSAML()
|
||||||
|
.getEntityId())
|
||||||
|
.singleSignOnServiceLocation(
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSAML()
|
||||||
|
.getIdpMetadataLocation())
|
||||||
|
.wantAuthnRequestsSigned(true)
|
||||||
|
.verificationX509Credentials(
|
||||||
|
c -> c.add(this.realmCertificate())))
|
||||||
|
.build();
|
||||||
|
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Saml2X509Credential signingCredential() {
|
||||||
|
log.info("Starting to load signing credential");
|
||||||
|
try {
|
||||||
|
Resource storeResource =
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSAML()
|
||||||
|
.getKeystore()
|
||||||
|
.getKeystoreResource();
|
||||||
|
log.info("Keystore resource: {}", storeResource.getDescription());
|
||||||
|
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||||
|
try (InputStream is = storeResource.getInputStream()) {
|
||||||
|
keyStore.load(
|
||||||
|
is,
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSAML()
|
||||||
|
.getKeystore()
|
||||||
|
.getKeystorePassword()
|
||||||
|
.toCharArray());
|
||||||
|
log.info("Keystore loaded successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
String keyAlias =
|
||||||
|
applicationProperties.getSecurity().getSAML().getKeystore().getKeyAlias();
|
||||||
|
log.info("Attempting to retrieve private key with alias: {}", keyAlias);
|
||||||
|
|
||||||
|
PrivateKey privateKey =
|
||||||
|
(PrivateKey)
|
||||||
|
keyStore.getKey(
|
||||||
|
keyAlias,
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSAML()
|
||||||
|
.getKeystore()
|
||||||
|
.getKeyPassword()
|
||||||
|
.toCharArray());
|
||||||
|
|
||||||
|
if (privateKey == null) {
|
||||||
|
log.error("Private key not found for alias: {}", keyAlias);
|
||||||
|
throw new RuntimeException("Private key not found in keystore");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Private key retrieved successfully");
|
||||||
|
|
||||||
|
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(keyAlias);
|
||||||
|
|
||||||
|
if (certificate == null) {
|
||||||
|
log.info("Certificate not found for alias: {}", keyAlias);
|
||||||
|
throw new RuntimeException("Certificate not found in keystore");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
"Certificate retrieved successfully. Subject: {}",
|
||||||
|
certificate.getSubjectX500Principal());
|
||||||
|
|
||||||
|
log.info("Signing credential created successfully");
|
||||||
|
return Saml2X509Credential.signing(privateKey, certificate);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error loading signing credential", e);
|
||||||
|
throw new RuntimeException("Error loading signing credential", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Saml2X509Credential realmCertificate() {
|
||||||
|
log.info("Starting to load realm certificate");
|
||||||
|
try {
|
||||||
|
Resource storeResource =
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSAML()
|
||||||
|
.getKeystore()
|
||||||
|
.getKeystoreResource();
|
||||||
|
log.info("Keystore resource: {}", storeResource.getDescription());
|
||||||
|
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||||
|
try (InputStream is = storeResource.getInputStream()) {
|
||||||
|
keyStore.load(
|
||||||
|
is,
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSAML()
|
||||||
|
.getKeystore()
|
||||||
|
.getKeystorePassword()
|
||||||
|
.toCharArray());
|
||||||
|
log.info("Keystore loaded successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
String realmCertificateAlias =
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSAML()
|
||||||
|
.getKeystore()
|
||||||
|
.getRealmCertificateAlias();
|
||||||
|
log.info(
|
||||||
|
"Attempting to retrieve realm certificate with alias: {}",
|
||||||
|
realmCertificateAlias);
|
||||||
|
|
||||||
|
X509Certificate certificate =
|
||||||
|
(X509Certificate) keyStore.getCertificate(realmCertificateAlias);
|
||||||
|
|
||||||
|
if (certificate == null) {
|
||||||
|
log.error("Realm certificate not found for alias: {}", realmCertificateAlias);
|
||||||
|
throw new RuntimeException("Realm certificate not found in keystore");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
"Realm certificate retrieved successfully. Subject: {}",
|
||||||
|
certificate.getSubjectX500Principal());
|
||||||
|
|
||||||
|
log.info("Realm certificate credential created successfully");
|
||||||
|
return Saml2X509Credential.verification(certificate);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error loading realm certificate", e);
|
||||||
|
throw new RuntimeException("Error loading realm certificate", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.saml.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public Saml2MetadataFilter metadataFilter(RelyingPartyRegistrationRepository registrations) {
|
||||||
|
DefaultRelyingPartyRegistrationResolver registrationResolver =
|
||||||
|
new DefaultRelyingPartyRegistrationResolver(registrations);
|
||||||
|
OpenSamlMetadataResolver metadataResolver = new OpenSamlMetadataResolver();
|
||||||
|
return new Saml2MetadataFilter(registrationResolver, metadataResolver);
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import java.time.Instant;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import java.time.temporal.ChronoUnit;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import java.util.*;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@ -13,6 +13,14 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
|
|||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.*;
|
import stirling.software.SPDF.model.*;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
@ -22,11 +30,6 @@ import stirling.software.SPDF.model.provider.GoogleProvider;
|
|||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Tag(name = "Account Security", description = "Account Security APIs")
|
@Tag(name = "Account Security", description = "Account Security APIs")
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -11,7 +12,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.PropertySource;
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
@ -130,6 +135,7 @@ public class ApplicationProperties {
|
|||||||
private Boolean csrfDisabled;
|
private Boolean csrfDisabled;
|
||||||
private InitialLogin initialLogin;
|
private InitialLogin initialLogin;
|
||||||
private OAUTH2 oauth2;
|
private OAUTH2 oauth2;
|
||||||
|
private SAML saml;
|
||||||
private int loginAttemptCount;
|
private int loginAttemptCount;
|
||||||
private long loginResetTimeMinutes;
|
private long loginResetTimeMinutes;
|
||||||
private String loginMethod = "all";
|
private String loginMethod = "all";
|
||||||
@ -174,6 +180,14 @@ public class ApplicationProperties {
|
|||||||
this.oauth2 = oauth2;
|
this.oauth2 = oauth2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SAML getSAML() {
|
||||||
|
return saml != null ? saml : new SAML();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSAML(SAML saml) {
|
||||||
|
this.saml = saml;
|
||||||
|
}
|
||||||
|
|
||||||
public Boolean getEnableLogin() {
|
public Boolean getEnableLogin() {
|
||||||
return enableLogin;
|
return enableLogin;
|
||||||
}
|
}
|
||||||
@ -235,6 +249,34 @@ public class ApplicationProperties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class SAML {
|
||||||
|
private Boolean enabled = false;
|
||||||
|
private String entityId;
|
||||||
|
private String registrationId;
|
||||||
|
private String spBaseUrl;
|
||||||
|
private String idpMetadataLocation;
|
||||||
|
private KeyStore keystore;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class KeyStore {
|
||||||
|
private String keystoreLocation;
|
||||||
|
private String keystorePassword;
|
||||||
|
private String keyAlias;
|
||||||
|
private String keyPassword;
|
||||||
|
private String realmCertificateAlias;
|
||||||
|
|
||||||
|
public Resource getKeystoreResource() {
|
||||||
|
if (keystoreLocation.startsWith("classpath:")) {
|
||||||
|
return new ClassPathResource(
|
||||||
|
keystoreLocation.substring("classpath:".length()));
|
||||||
|
} else {
|
||||||
|
return new FileSystemResource(keystoreLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class OAUTH2 {
|
public static class OAUTH2 {
|
||||||
private Boolean enabled = false;
|
private Boolean enabled = false;
|
||||||
private String issuer;
|
private String issuer;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -9,6 +7,8 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "users")
|
@Table(name = "users")
|
||||||
public class User implements Serializable {
|
public class User implements Serializable {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package stirling.software.SPDF.repository;
|
package stirling.software.SPDF.repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import stirling.software.SPDF.model.User;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
@ -262,5 +262,4 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,7 @@ public class RequestUriUtils {
|
|||||||
|
|
||||||
public static boolean isStaticResource(String requestURI) {
|
public static boolean isStaticResource(String requestURI) {
|
||||||
|
|
||||||
return requestURI.startsWith("/css/")
|
return isStaticResource("", requestURI);
|
||||||
|| requestURI.startsWith("/fonts/")
|
|
||||||
|| requestURI.startsWith("/js/")
|
|
||||||
|| requestURI.startsWith("/images/")
|
|
||||||
|| requestURI.startsWith("/public/")
|
|
||||||
|| requestURI.startsWith("/pdfjs/")
|
|
||||||
|| requestURI.startsWith("/pdfjs-legacy/")
|
|
||||||
|| requestURI.endsWith(".svg")
|
|
||||||
|| requestURI.endsWith(".webmanifest")
|
|
||||||
|| requestURI.startsWith("/api/v1/info/status");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isStaticResource(String contextPath, String requestURI) {
|
public static boolean isStaticResource(String contextPath, String requestURI) {
|
||||||
@ -24,6 +15,7 @@ public class RequestUriUtils {
|
|||||||
|| requestURI.startsWith(contextPath + "/images/")
|
|| requestURI.startsWith(contextPath + "/images/")
|
||||||
|| requestURI.startsWith(contextPath + "/public/")
|
|| requestURI.startsWith(contextPath + "/public/")
|
||||||
|| requestURI.startsWith(contextPath + "/pdfjs/")
|
|| requestURI.startsWith(contextPath + "/pdfjs/")
|
||||||
|
|| requestURI.startsWith(contextPath + "/saml2")
|
||||||
|| requestURI.endsWith(".svg")
|
|| requestURI.endsWith(".svg")
|
||||||
|| requestURI.endsWith(".webmanifest")
|
|| requestURI.endsWith(".webmanifest")
|
||||||
|| requestURI.startsWith(contextPath + "/api/v1/info/status");
|
|| requestURI.startsWith(contextPath + "/api/v1/info/status");
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
multipart.enabled=true
|
multipart.enabled=true
|
||||||
|
|
||||||
|
otel.metrics.exporter=prometheus
|
||||||
|
otel.exporter.prometheus.port=9464
|
||||||
|
otel.service.name=stirling-pdf
|
||||||
|
|
||||||
|
logging.level.org.springframework.security.saml2=DEBUG
|
||||||
|
logging.level.org.springframework.security=DEBUG
|
||||||
|
|
||||||
logging.level.org.springframework=WARN
|
logging.level.org.springframework=WARN
|
||||||
logging.level.org.hibernate=WARN
|
logging.level.org.hibernate=WARN
|
||||||
logging.level.org.eclipse.jetty=WARN
|
logging.level.org.eclipse.jetty=WARN
|
||||||
|
@ -49,9 +49,16 @@ security:
|
|||||||
provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||||
saml:
|
saml:
|
||||||
enabled: false # set to 'true' to enable SAML login (Note: enableLogin must also be 'true' for this to work)
|
enabled: false # set to 'true' to enable SAML login (Note: enableLogin must also be 'true' for this to work)
|
||||||
|
registrationId: stirling
|
||||||
entityId: '' # Entity ID for the Service Provider (SP)
|
entityId: '' # Entity ID for the Service Provider (SP)
|
||||||
idpMetadataLocation: '' # URL or file path to the Identity Provider (IdP) metadata
|
idpMetadataLocation: '' # URL or file path to the Identity Provider (IdP) metadata
|
||||||
spBaseUrl: '' # Base URL for the Service Provider (SP)
|
spBaseUrl: '' # Base URL for the Service Provider (SP)
|
||||||
|
keystore:
|
||||||
|
keystoreLocation: /config/keystore.jks
|
||||||
|
keystorePassword: stirlingstore
|
||||||
|
keyAlias: stirling
|
||||||
|
keyPassword: stirlingkey
|
||||||
|
realmCertificateAlias: master
|
||||||
|
|
||||||
system:
|
system:
|
||||||
defaultLocale: en-US # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
defaultLocale: en-US # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||||
|
@ -114,7 +114,7 @@
|
|||||||
<img class="my-4" th:src="@{'/favicon.svg'}" alt="favicon" width="144" height="144">
|
<img class="my-4" th:src="@{'/favicon.svg'}" alt="favicon" width="144" height="144">
|
||||||
|
|
||||||
<h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
|
<h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
|
||||||
<div th:if="${oAuth2Enabled} and (${loginMethod} == 'all' or ${loginMethod} == 'oauth2')">
|
<div th:if="(${oAuth2Enabled} or ${samlEnabled}) and (${loginMethod} == 'all' or ${loginMethod} == 'oauth2')">
|
||||||
<a href="#" class="w-100 btn btn-lg btn-primary" data-bs-toggle="modal" data-bs-target="#loginsModal" th:text="#{login.ssoSignIn}">Login Via SSO</a>
|
<a href="#" class="w-100 btn btn-lg btn-primary" data-bs-toggle="modal" data-bs-target="#loginsModal" th:text="#{login.ssoSignIn}">Login Via SSO</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
@ -184,7 +184,7 @@
|
|||||||
<a th:href="@{|/oauth2/authorization/${provider.key}|}" th:text="${provider.value}" class="w-100 btn btn-lg btn-primary">OpenID Connect</a>
|
<a th:href="@{|/oauth2/authorization/${provider.key}|}" th:text="${provider.value}" class="w-100 btn btn-lg btn-primary">OpenID Connect</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3" th:if="${samlEnabled}">
|
<div class="mb-3" th:if="${samlEnabled}">
|
||||||
<a th:href="@{'/saml2/authenticate/saml'}" class="w-100 btn btn-lg btn-primary" th:text="#{login.samlSignIn}">SAML</a>
|
<a th:href="@{'/saml2/authenticate/stirling'}" class="w-100 btn btn-lg btn-primary" th:text="#{login.ssoSignIn}">SAML</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user