Spring Security with SAML and Okta

This commit is contained in:
Anshul BANSAL 2021-02-15 07:00:27 +02:00
parent 25e43334c4
commit bc4ea42b7d
40 changed files with 147 additions and 1123 deletions

View File

@ -35,6 +35,7 @@
<module>spring-security-legacy-oidc</module>
<module>spring-security-oidc</module>
<module>spring-security-okta</module>
<module>spring-security-saml</module>
<module>spring-security-web-react</module>
<module>spring-security-web-rest</module>
<module>spring-security-web-rest-basic-auth</module>

View File

@ -1,3 +0,0 @@
### Relevant Articles:
- [Spring Security With Okta](https://www.baeldung.com/spring-security-okta)

View File

@ -31,31 +31,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.extensions</groupId>
<artifactId>spring-security-saml2-core</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<version>${saml2-core.spring.version}</version>
</dependency>
</dependencies>
@ -85,6 +68,6 @@
</build>
<properties>
<saml.spring.version>1.0.10.RELEASE</saml.spring.version>
<saml2-core.spring.version>1.0.10.RELEASE</saml2-core.spring.version>
</properties>
</project>

View File

@ -3,9 +3,6 @@ package com.baeldung.saml;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author jcavazos
*/
@SpringBootApplication
public class Application {
public static void main(String... args) {

View File

@ -1,6 +0,0 @@
package com.baeldung.saml;
public class Constants {
public static final String OKTA_USERNAME_SUFFIX = "@oktaauth.com";
public static final String DB_USERNAME_SUFFIX = "@dbauth.com";
}

View File

@ -1,9 +0,0 @@
package com.baeldung.saml.auth;
/**
* @author jcavazos
*/
public enum AuthMethod {
DATABASE,
SAML
}

View File

@ -1,66 +0,0 @@
package com.baeldung.saml.auth;
import com.baeldung.saml.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.saml.SAMLCredential;
import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
import org.springframework.stereotype.Service;
import java.util.LinkedList;
import java.util.Optional;
/**
* Supplies {@link UserDetails} information for both DB and SAML users
* @author jcavazos
*/
@Service
public class CombinedUserDetailsService implements UserDetailsService, SAMLUserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(CombinedUserDetailsService.class);
private final UserRepository userRepository;
@Autowired
public CombinedUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
StoredUser storedUser = lookupUser(s);
return new CustomUserDetails(
AuthMethod.DATABASE,
storedUser.getUsername(),
storedUser.getPasswordHash(),
new LinkedList<>());
}
@Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
LOGGER.info("Loading UserDetails by SAMLCredentials: {}", credential.getNameID());
StoredUser storedUser = lookupUser(credential.getNameID().getValue());
return new CustomUserDetails(
AuthMethod.SAML,
storedUser.getUsername(),
storedUser.getPasswordHash(),
new LinkedList<>());
}
private StoredUser lookupUser(String username) {
LOGGER.info("Loading UserDetails by username: {}", username);
Optional<StoredUser> user = userRepository.findByUsernameIgnoreCase(username);
if (!user.isPresent()) {
LOGGER.error("User not found in database: {}", user);
throw new UsernameNotFoundException(username);
}
return user.get();
}
}

View File

@ -1,12 +0,0 @@
package com.baeldung.saml.auth;
import java.lang.annotation.*;
/**
* Represents the current authenticated user during HTTP requests
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentUser {
}

View File

@ -1,32 +0,0 @@
package com.baeldung.saml.auth;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
@Getter
public class CustomUserDetails extends User {
private final AuthMethod authMethod;
public CustomUserDetails(AuthMethod authMethod,
String username,
String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.authMethod = authMethod;
}
public CustomUserDetails(AuthMethod authMethod,
String username,
String password,
boolean enabled,
boolean accountNonExpired,
boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.authMethod = authMethod;
}
}

View File

@ -1,64 +0,0 @@
package com.baeldung.saml.auth;
import com.baeldung.saml.Constants;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Collections;
/**
* Provides authentication for "database" users, i.e. users whose credentials
* are authenticated against the database instead of Okta/SAML
* @author jcavazos
*/
@Component
public class DbAuthProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(DbAuthProvider.class);
private final CombinedUserDetailsService combinedUserDetailsService;
private final PasswordEncoder passwordEncoder;
@Autowired
public DbAuthProvider(CombinedUserDetailsService combinedUserDetailsService, PasswordEncoder passwordEncoder) {
this.combinedUserDetailsService = combinedUserDetailsService;
this.passwordEncoder = passwordEncoder;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!StringUtils.endsWithIgnoreCase(authentication.getPrincipal().toString(), Constants.DB_USERNAME_SUFFIX)) {
// this user is not supported by DB authentication
return null;
}
UserDetails user = combinedUserDetailsService.loadUserByUsername(authentication.getPrincipal().toString());
String rawPw = authentication.getCredentials() == null ? null : authentication.getCredentials().toString();
if (passwordEncoder.matches(rawPw, user.getPassword())) {
LOGGER.warn("User successfully logged in: {}", user.getUsername());
return new UsernamePasswordAuthenticationToken(
user.getUsername(),
rawPw,
Collections.emptyList());
} else {
LOGGER.error("User failed to log in: {}", user.getUsername());
throw new BadCredentialsException("Bad password");
}
}
@Override
public boolean supports(Class<?> aClass) {
return aClass.isAssignableFrom(UsernamePasswordAuthenticationToken.class);
}
}

View File

@ -1,52 +0,0 @@
package com.baeldung.saml.auth;
import lombok.EqualsAndHashCode;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.UUID;
@Entity
@Table(name = "USER")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class StoredUser implements Serializable {
@Id
@EqualsAndHashCode.Include
private String id = UUID.randomUUID().toString();
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String passwordHash;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(String passwordHash) {
this.passwordHash = passwordHash;
}
}

View File

@ -0,0 +1,28 @@
package com.baeldung.saml.authentication;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
import org.springframework.security.saml.SAMLAuthenticationProvider;
import org.springframework.security.saml.SAMLCredential;
public class CustomSAMLAuthenticationProvider extends SAMLAuthenticationProvider {
@Override
public Collection<? extends GrantedAuthority> getEntitlements(SAMLCredential credential, Object userDetail) {
if(userDetail instanceof ExpiringUsernameAuthenticationToken) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.addAll(((ExpiringUsernameAuthenticationToken) userDetail).getAuthorities());
return authorities;
} else {
return Collections.emptyList();
}
}
}

View File

@ -1,17 +0,0 @@
package com.baeldung.saml.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author jcavazos
*/
@Configuration
public class DbSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -1,40 +0,0 @@
package com.baeldung.saml.config;
import com.baeldung.saml.web.CurrentUserHandlerMethodArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
final CurrentUserHandlerMethodArgumentResolver currentUserHandlerMethodArgumentResolver;
@Autowired
public MvcConfig(CurrentUserHandlerMethodArgumentResolver currentUserHandlerMethodArgumentResolver) {
this.currentUserHandlerMethodArgumentResolver = currentUserHandlerMethodArgumentResolver;
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!registry.hasMappingForPattern("/static/**")) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static/");
}
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(currentUserHandlerMethodArgumentResolver);
}
}

View File

@ -1,16 +1,16 @@
package com.baeldung.saml.config;
import com.baeldung.saml.auth.CombinedUserDetailsService;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.velocity.app.VelocityEngine;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.util.resource.ResourceException;
import org.opensaml.xml.parse.ParserPool;
import org.opensaml.xml.parse.StaticBasicParserPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
@ -25,8 +25,6 @@ import org.springframework.security.saml.log.SAMLDefaultLogger;
import org.springframework.security.saml.metadata.CachingMetadataManager;
import org.springframework.security.saml.metadata.ExtendedMetadata;
import org.springframework.security.saml.metadata.ExtendedMetadataDelegate;
import org.springframework.security.saml.metadata.MetadataDisplayFilter;
import org.springframework.security.saml.parser.ParserPoolHolder;
import org.springframework.security.saml.processor.*;
import org.springframework.security.saml.util.VelocityFactory;
import org.springframework.security.saml.websso.*;
@ -36,12 +34,8 @@ import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import java.io.File;
import java.util.*;
import com.baeldung.saml.authentication.CustomSAMLAuthenticationProvider;
/**
* Configure settings and information required for SAML 2.0 authentication with Okta
*/
@Configuration
public class SamlSecurityConfig {
@ -57,105 +51,52 @@ public class SamlSecurityConfig {
@Value("${saml.idp}")
private String defaultIdp;
@Value("${saml.metadataUrl}")
private String metadataUrl;
@Autowired
private CombinedUserDetailsService combinedUserDetailsService;
private final Timer backgroundTaskTimer = new Timer(true);
private final MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager
= new MultiThreadedHttpConnectionManager();
@Bean
@Qualifier("saml")
public Timer getBackgroundTaskTimer() {
return backgroundTaskTimer;
}
@Bean
@Qualifier("saml")
public MultiThreadedHttpConnectionManager getMultiThreadedHttpConnectionManager() {
return multiThreadedHttpConnectionManager;
}
// Initialization of the velocity engine
@Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
// XML parser pool needed for OpenSAML parsing
@Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
@Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
// Bindings, encoders and decoders used for creating and parsing messages
@Bean
public HttpClient httpClient() {
return new HttpClient(this.multiThreadedHttpConnectionManager);
}
// SAML Authentication Provider responsible for validating of received SAML
// messages
@Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(combinedUserDetailsService);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
return new CustomSAMLAuthenticationProvider();
}
// Provider of default SAML Context
@Bean
public SAMLContextProviderImpl contextProvider() {
return new SAMLContextProviderImpl();
}
// Initialization of OpenSAML library
@Bean
public static SAMLBootstrap sAMLBootstrap() {
public static SAMLBootstrap samlBootstrap() {
return new SAMLBootstrap();
}
// Logger for SAML messages and events
@Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
// SAML 2.0 WebSSO Assertion Consumer
@Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
// SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
@Bean
@Qualifier("hokWebSSOprofileConsumer")
public WebSSOProfileConsumerHoKImpl hokWebSSOProfileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
// SAML 2.0 Web SSO profile
@Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
// SAML 2.0 Holder-of-Key Web SSO profile
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
// SAML 2.0 ECP profile
@Bean
public WebSSOProfileECPImpl ecpProfile() {
return new WebSSOProfileECPImpl();
@ -166,7 +107,6 @@ public class SamlSecurityConfig {
return new SingleLogoutProfileImpl();
}
// Central storage of cryptographic keys
@Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
@ -183,91 +123,68 @@ public class SamlSecurityConfig {
return webSSOProfileOptions;
}
// Entry point to initialize authentication, default values taken from
// properties file
@Bean
public SAMLEntryPoint samlEntryPoint() {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
return samlEntryPoint;
}
// Setup advanced info about metadata
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(true);
extendedMetadata.setSigningAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
extendedMetadata.setSignMetadata(true);
extendedMetadata.setEcpEnabled(true);
return extendedMetadata;
}
// IDP Discovery Service
@Bean
public SAMLDiscovery samlIDPDiscovery() {
SAMLDiscovery idpDiscovery = new SAMLDiscovery();
idpDiscovery.setIdpSelectionPath("/saml/discovery");
return idpDiscovery;
}
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
extendedMetadata.setSignMetadata(false);
return extendedMetadata;
}
@Bean
@Qualifier("okta")
public ExtendedMetadataDelegate oktaExtendedMetadataProvider() throws MetadataProviderException {
File metadata = null;
try {
metadata = new File("/Users/anshulbansal/eclipse-workspace/tutorials/spring-security-modules/spring-security-saml/src/main/resources/saml/metadata/sso.xml");
metadata = new File("./src/main/resources/saml/metadata/sso.xml");
} catch (Exception e) {
e.printStackTrace();
}
FilesystemMetadataProvider provider = null;
provider = new FilesystemMetadataProvider(metadata);
FilesystemMetadataProvider provider = new FilesystemMetadataProvider(metadata);
provider.setParserPool(parserPool());
return new ExtendedMetadataDelegate(provider, extendedMetadata());
}
// IDP Metadata configuration - paths to metadata of IDPs in circle of trust
// is here
// Do no forget to call initialize method on providers
@Bean
@Qualifier("metadata")
public CachingMetadataManager metadata() throws MetadataProviderException, ResourceException {
List<MetadataProvider> providers = new ArrayList<>();
List<MetadataProvider> providers = new ArrayList<>();
providers.add(oktaExtendedMetadataProvider());
CachingMetadataManager metadataManager = new CachingMetadataManager(providers);
metadataManager.setDefaultIDP(defaultIdp);
return metadataManager;
}
// The filter is waiting for connections on URL suffixed with filterSuffix
// and presents SP metadata there
@Bean
public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter();
}
// Handler deciding where to redirect user after successful login
@Bean
@Qualifier("saml")
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/home");
return successRedirectHandler;
}
// Handler deciding where to redirect user after failed login
@Bean
@Qualifier("saml")
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler =
new SimpleUrlAuthenticationFailureHandler();
SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl("/error");
return failureHandler;
}
// Handler for successful logout
@Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
@ -275,27 +192,19 @@ public class SamlSecurityConfig {
return successLogoutHandler;
}
// Logout handler terminating local session
@Bean
public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler logoutHandler =
new SecurityContextLogoutHandler();
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
// Filter processing incoming logout messages
// First argument determines URL user will be redirected to after successful
// global logout
@Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
return new SAMLLogoutProcessingFilter(successLogoutHandler(),
logoutHandler());
return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
}
// Overrides default logout processing filter with the one processing SAML
// messages
@Bean
public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(),
@ -303,53 +212,21 @@ public class SamlSecurityConfig {
new LogoutHandler[] { logoutHandler() });
}
// Bindings
private ArtifactResolutionProfile artifactResolutionProfile() {
final ArtifactResolutionProfileImpl artifactResolutionProfile =
new ArtifactResolutionProfileImpl(httpClient());
artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(soapBinding()));
return artifactResolutionProfile;
}
@Bean
public HTTPArtifactBinding artifactBinding(ParserPool parserPool, VelocityEngine velocityEngine) {
return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile());
}
@Bean
public HTTPSOAP11Binding soapBinding() {
return new HTTPSOAP11Binding(parserPool());
}
@Bean
public HTTPPostBinding httpPostBinding() {
return new HTTPPostBinding(parserPool(), velocityEngine());
return new HTTPPostBinding(parserPool(), VelocityFactory.getEngine());
}
@Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
return new HTTPRedirectDeflateBinding(parserPool());
}
@Bean
public HTTPSOAP11Binding httpSOAP11Binding() {
return new HTTPSOAP11Binding(parserPool());
}
@Bean
public HTTPPAOS11Binding httpPAOS11Binding() {
return new HTTPPAOS11Binding(parserPool());
}
// Processor
@Bean
public SAMLProcessorImpl processor() {
Collection<SAMLBinding> bindings = new ArrayList<>();
ArrayList<SAMLBinding> bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding());
bindings.add(artifactBinding(parserPool(), velocityEngine()));
bindings.add(httpSOAP11Binding());
bindings.add(httpPAOS11Binding());
return new SAMLProcessorImpl(bindings);
}
}

View File

@ -1,12 +1,9 @@
package com.baeldung.saml.config;
import com.baeldung.saml.auth.AuthMethod;
import com.baeldung.saml.auth.CustomUserDetails;
import com.baeldung.saml.auth.DbAuthProvider;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
@ -18,8 +15,6 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.saml.*;
import org.springframework.security.saml.key.KeyManager;
import org.springframework.security.saml.metadata.*;
@ -33,19 +28,10 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
/**
* Main configuration class for wiring together DB + SAML authentication
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${saml.sp}")
private String samlAudience;
@ -58,23 +44,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements D
@Qualifier("saml")
private SimpleUrlAuthenticationFailureHandler samlAuthFailureHandler;
@Autowired
@Qualifier("saml")
private Timer samlBackgroundTaskTimer;
@Autowired
@Qualifier("saml")
private MultiThreadedHttpConnectionManager samlMultiThreadedHttpConnectionManager;
@Autowired
private SAMLEntryPoint samlEntryPoint;
@Autowired
private SAMLLogoutFilter samlLogoutFilter;
@Autowired
private MetadataDisplayFilter metadataDisplayFilter;
@Autowired
private SAMLLogoutProcessingFilter samlLogoutProcessingFilter;
@ -87,15 +62,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements D
@Autowired
private ExtendedMetadata extendedMetadata;
@Autowired
private CachingMetadataManager cachingMetadataManager;
@Autowired
private KeyManager keyManager;
@Autowired
private DbAuthProvider dbAuthProvider;
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId(samlAudience);
@ -105,16 +74,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements D
return metadataGenerator;
}
@Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(samlAuthSuccessHandler);
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(samlAuthFailureHandler);
return samlWebSSOHoKProcessingFilter;
}
// Processing filter for WebSSO profile messages
@Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
@ -124,36 +83,22 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements D
return samlWebSSOProcessingFilter;
}
/**
* Define the security filter chain in order to support SSO Auth by using SAML 2.0
*
* @return Filter chain proxy
*/
@Bean
public FilterChainProxy samlFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
samlEntryPoint));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
samlLogoutFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
metadataDisplayFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
samlWebSSOProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
samlWebSSOHoKProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter));
samlWebSSOProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),
samlDiscovery));
samlDiscovery));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
samlEntryPoint));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
samlLogoutFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter));
return new FilterChainProxy(chains);
}
/**
* Returns the authentication manager currently used by Spring.
* It represents a bean definition with the aim allow wiring from
* other classes performing the Inversion of Control (IoC).
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
@ -165,90 +110,40 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements D
return new MetadataGeneratorFilter(metadataGenerator());
}
/**
* Defines the web based security configuration.
*
* @param http Allows configuring web based security for specific http requests.
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable();
.csrf()
.disable();
http
.httpBasic()
.authenticationEntryPoint((request, response, authException) -> {
/*
Unauthenticated requests will be routed through this class.
If a request is intended to begin the SAML auth workflow, it will be
initiated here {@see IndexController.preAuth()}.
Otherwise, the user will be redirected to the pre-auth landing page.
*/
if (request.getRequestURI().endsWith("doSaml")) {
samlEntryPoint.commence(request, response, authException);
} else {
response.sendRedirect("/");
}
});
.httpBasic()
.authenticationEntryPoint(samlEntryPoint);
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(samlFilter(), CsrfFilter.class);
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(samlFilter(), CsrfFilter.class);
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/pre-auth**").permitAll()
.antMatchers("/form-login**").permitAll()
.antMatchers("/error").permitAll()
.antMatchers("/saml/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/img/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/sw.js").permitAll()
.anyRequest().authenticated();
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated();
http
.logout()
.addLogoutHandler((request, response, authentication) -> {
/*
If the user is authenticated via SAML, we need to direct them to the appropriate
SAML logout flow
*/
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof CustomUserDetails) {
CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal();
if (userDetails.getAuthMethod() == AuthMethod.SAML) {
try {
response.sendRedirect("/saml/logout");
} catch (Exception e) {
LOGGER.error("Error processing logout for SAML user", e);
throw new RuntimeException(e);
}
}
}
});
.logout()
.addLogoutHandler((request, response, authentication) -> {
try {
response.sendRedirect("/saml/logout");
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* Define a chain of authentication providers, starting with DB auth.
*
* If the user is not supported by DB authentication, it will fall through
* to the SAML auth provider.
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(dbAuthProvider);
auth.authenticationProvider(samlAuthenticationProvider);
}
@Override
public void destroy() throws Exception {
this.samlBackgroundTaskTimer.purge();
this.samlBackgroundTaskTimer.cancel();
this.samlMultiThreadedHttpConnectionManager.shutdown();
}
}

View File

@ -1,63 +0,0 @@
package com.baeldung.saml.controller;
import com.baeldung.saml.controller.model.DbAuthCredentials;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
* Handles standard form-based logins used to authenticate users against the database.
* @author jcavazos
*/
@Controller
public class DbLoginController {
private final AuthenticationManager authenticationManager;
@Autowired
public DbLoginController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@GetMapping("/form-login")
public String formLogin(@RequestParam(required = false) String username, Model model) {
DbAuthCredentials credentials = new DbAuthCredentials();
if (StringUtils.isNotBlank(username)) {
credentials.setUsername(username);
}
model.addAttribute("credentials", credentials);
return "form-login";
}
@PostMapping("/form-login")
public String doLogin(@ModelAttribute DbAuthCredentials credentials,
RedirectAttributes redirectAttributes) {
try {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
credentials.getUsername(), credentials.getPassword()));
if (authentication.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
throw new Exception("Unauthenticated");
}
return "redirect:/landing";
} catch (Exception e) {
redirectAttributes.addFlashAttribute("error", "Login Failed");
return "redirect:/form-login?username="+credentials.getUsername();
}
}
}

View File

@ -0,0 +1,35 @@
package com.baeldung.saml.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@RequestMapping("/")
public String index() {
return "index";
}
@GetMapping(value = "/auth")
public String handleSamlAuth() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
return "redirect:/home";
} else {
return "/";
}
}
@RequestMapping("/home")
public String home(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("username", authentication.getPrincipal());
return "home";
}
}

View File

@ -1,47 +0,0 @@
package com.baeldung.saml.controller;
import com.baeldung.saml.Constants;
import com.baeldung.saml.controller.model.PreAuthUsername;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
* "Pre-auth" landing page in which the user will enter their username. Depending on
* the username suffix, the user will either be directed to provide a password for
* database authentication, or the SAML auth flow will be initiated.
*
* The redirect to /doSaml will be picked up by the custom auth entry point defined
* in {@link com.baeldung.saml.config.WebSecurityConfig}.
*
* @author jcavazos
*/
@Controller
public class IndexController {
@GetMapping
public String index(Model model) {
model.addAttribute("username", new PreAuthUsername());
return "index";
}
@PostMapping("/pre-auth")
public String preAuth(@ModelAttribute PreAuthUsername username,
Model model,
RedirectAttributes redirectAttributes) {
if (StringUtils.endsWithIgnoreCase(username.getUsername(), Constants.OKTA_USERNAME_SUFFIX)) {
// redirect to SAML
return "redirect:/doSaml";
} else if (StringUtils.endsWithIgnoreCase(username.getUsername(), Constants.DB_USERNAME_SUFFIX)) {
// redirect to DB/form login
return "redirect:/form-login?username="+username.getUsername();
} else {
redirectAttributes.addFlashAttribute("error", "Invalid Username");
return "redirect:/";
}
}
}

View File

@ -1,30 +0,0 @@
package com.baeldung.saml.controller;
import com.baeldung.saml.auth.CurrentUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* This page should be guarded by the app's combined authentication. Thus, only users who
* have successfully authenticated via DB or SAML auth should have access
*
*/
@Controller
public class LandingController {
private static final Logger LOGGER = LoggerFactory.getLogger(LandingController.class);
@RequestMapping("/landing")
public String landing(@CurrentUser User user, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LOGGER.info("Current auth: {}", authentication==null?"NULL":authentication.getPrincipal());
model.addAttribute("username", user.getUsername());
return "landing";
}
}

View File

@ -1,48 +0,0 @@
package com.baeldung.saml.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.saml.metadata.MetadataManager;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Set;
/**
* Page to select which Identity Provider (IDP) to authenticate against when beginning SAML auth flow.
*
*/
@Controller
@RequestMapping("/saml")
public class SSOController {
private static final Logger LOGGER = LoggerFactory.getLogger(SSOController.class);
private final MetadataManager metadata;
@Autowired
public SSOController(MetadataManager metadata) {
this.metadata = metadata;
}
@RequestMapping(value = "/discovery", method = RequestMethod.GET)
public String idpSelection(Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
LOGGER.info("Current auth: {}", auth==null?"NULL":auth.getPrincipal());
if (auth == null || (auth instanceof AnonymousAuthenticationToken)) {
Set<String> idps = metadata.getIDPEntityNames();
idps.forEach(idp -> LOGGER.info("Configured Identity Provider for SSO: {}", idp));
model.addAttribute("idps", idps);
return "discovery";
} else {
LOGGER.warn("The current user is already logged in: {}", auth);
return "redirect:/landing";
}
}
}

View File

@ -1,37 +0,0 @@
package com.baeldung.saml.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* The SAML auth workflow is initiated by redirecting the request to the /doSaml
* endpoint. When the SAML flow is complete, the browser will be directed back to /doSaml.
*
* This class handles the result of that flow. If the user is successfully authenticated,
* they should be directed to the guarded landing page.
*
* @see IndexController
* @see com.baeldung.saml.config.WebSecurityConfig
*
* @author jcavazos
*/
@Controller
public class SamlResponseController {
private static final Logger LOGGER = LoggerFactory.getLogger(SamlResponseController.class);
@GetMapping(value = "/doSaml")
public String handleSamlAuth() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
LOGGER.info("doSaml auth result: {}", auth);
if (auth != null) {
return "redirect:/landing";
} else {
return "/";
}
}
}

View File

@ -1,20 +0,0 @@
package com.baeldung.saml.controller.model;
public class DbAuthCredentials {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -1,15 +0,0 @@
package com.baeldung.saml.controller.model;
public class PreAuthUsername {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@ -1,12 +0,0 @@
package com.baeldung.saml.repository;
import com.baeldung.saml.auth.StoredUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<StoredUser, String> {
Optional<StoredUser> findByUsernameIgnoreCase(String username);
}

View File

@ -1,53 +0,0 @@
package com.baeldung.saml.web;
import com.baeldung.saml.auth.CombinedUserDetailsService;
import com.baeldung.saml.auth.CurrentUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
*/
@Component
public class CurrentUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final CombinedUserDetailsService combinedUserDetailsService;
@Autowired
public CurrentUserHandlerMethodArgumentResolver(CombinedUserDetailsService combinedUserDetailsService) {
this.combinedUserDetailsService = combinedUserDetailsService;
}
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(CurrentUser.class) != null
&& methodParameter.getParameterType().equals(User.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
if (this.supportsParameter(methodParameter)) {
Authentication authentication = (Authentication) nativeWebRequest.getUserPrincipal();
if (authentication.getPrincipal() instanceof User) {
return authentication.getPrincipal();
} else if (authentication.getPrincipal() instanceof String) {
return combinedUserDetailsService.loadUserByUsername((String)authentication.getPrincipal());
}
throw new RuntimeException("Unsupported principal: "+authentication.getPrincipal());
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}

View File

@ -2,8 +2,5 @@ saml.keystore.location=classpath:/saml/samlKeystore.jks
saml.keystore.password=oktaiscool
saml.keystore.alias=oktasaml
saml.metadataUrl=https://dev-926666.okta.com/app/dev-926666_baeldungspringsecuritysaml_1/exk26fxqrz8LLk9dV4x7/sso/saml
saml.idp=http://www.okta.com/exk26fxqrz8LLk9dV4x7
saml.sp=http://localhost:8080/saml/metadata
logging.level.root: info
saml.sp=http://localhost:8080/saml/metadata

View File

@ -1,4 +0,0 @@
-- PW: oktaiscool
INSERT INTO user (ID, USERNAME, PASSWORD_HASH) VALUES
('17e3d83c-6e09-41b8-b4ee-b4b14cb8a797', 'dbuser@dbauth.com', '$2y$12$0uXgosmcpIRWLxcAhCx0s.dUEZbrAHa0F0pkn6MO4Y57PCJ5fDjfa'),
('17e3d83c-6e09-41b8-b4ee-b4b14cb8a798', 'samluser@oktaauth.com', '$2y$12$0uXgosmcpIRWLxcAhCx0s.dUEZbrAHa0F0pkn6MO4Y57PCJ5fDjfa');

File diff suppressed because one or more lines are too long

View File

@ -1,7 +0,0 @@
body {
padding-top: 5rem;
}
.starter-template {
padding: 3rem 1.5rem;
text-align: center;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,32 +0,0 @@
<!doctype html>
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}"
>
<body>
<!-- Content starts -->
<section layout:fragment="content">
<h6 class="border-bottom border-gray pb-2 mb-0">Select your Identity Provider:</h6>
<form th:action="${idpDiscoReturnURL}" method="get">
<fieldset class="form-group">
<div class="form-check" th:each="idp : ${idps}">
<label class="form-check-label">
<input type="radio" class="form-check-input" th:name="${idpDiscoReturnParam}" th:id="'idp_' + ${idp}" th:value="${idp}" />
<span class="badge badge-dark" th:text="${idp}">null</span>
</label>
</div>
</fieldset>
<small class="d-block text-right mt-3" id="sso-btn">
<button type="submit" class="btn btn-spring btn-sm">
<i class="fas fa-handshake"></i> Start 3rd Party Login
</button>
</small>
</form>
</section>
<!-- Content ends -->
</body>
</html>

View File

@ -1,12 +1,6 @@
<!doctype html>
<html
lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}"
>
<html>
<body>
<!-- Content starts -->
<section layout:fragment="content">
<h6 class="border-bottom border-gray pb-2 mb-0">Something went wrong</h6>
<div class="media text-muted pt-3">
<i class="fas fa-door-closed fa-2x fa-fw mr-2 spring-green" data-fa-transform="shrink-4"></i>
@ -14,8 +8,6 @@
An error occurred
</p>
</div>
</section>
<!-- Content ends -->
<body>
</html>

View File

@ -1,22 +0,0 @@
<!doctype html>
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}"
>
<body>
<section layout:fragment="content">
<h6 class="border-bottom border-gray pb-2 mb-0">Database Login:</h6>
<div class="media text-muted pt-3">
<form action="#" th:action="@{/form-login}" th:object="${credentials}" method="post">
<p>Username: <input type="text" th:field="*{username}" /></p>
<p>Password: <input type="password" th:field="*{password}" /></p>
<p><input type="submit" value="Submit" /></p>
</form>
<br/>
<p th:text="${error}" style="color: red"></p>
</div>
</section>
<body>
</html>

View File

@ -1,14 +1,9 @@
<!doctype html>
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}"
xmlns="http://www.w3.org/1999/html">
<html>
<head>
<title>Baeldung Spring Security SAML: Home</title>
</head>
<body>
<!-- Content starts -->
<section layout:fragment="content">
<h3><Strong>Welcome!</strong><br/>You are successfully logged in!</h3>
<p>You are logged as <span class="badge badge-dark" th:text="${username}">null</span>.</p>
<small class="d-block text-right mt-3" id="sso-btn">
@ -16,8 +11,5 @@
<i class="far fa-user-circle"></i> Logout
</a>
</small>
</section>
<!-- Content ends -->
</body>
</html>

View File

@ -1,21 +1,12 @@
<!doctype html>
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}"
>
<body>
<section layout:fragment="content">
<h6 class="border-bottom border-gray pb-2 mb-0">Please Log In:</h6>
<div class="media text-muted pt-3">
<form action="#" th:action="@{/pre-auth}" th:object="${username}" method="post">
<p>Username: <input type="text" th:field="*{username}" /></p>
<p><input type="submit" value="Submit" /></p>
</form>
<br/>
<p th:text="${error}" style="color: red"></p>
</div>
</section>
<html>
<head>
<title>Baeldung Spring Security SAML</title>
</head>
<body>
<h3><Strong>Welcome to Baeldung Spring Security SAML</strong></h3>
<a th:href="@{/auth}" class="btn btn-spring btn-sm">
<i class="far fa-user-circle"></i> Login
</a>
</body>
</html>

View File

@ -1,57 +0,0 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Spring Boot - SAML 2.0 Service Provider">
<meta name="author" content="Joe Cavazos">
<link rel="icon" th:href="@{/img/favicon.ico}">
<title>Spring Boot &mdash; SAML 2.0 Service Provider</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Font Awesome CSS -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css">
<!-- Custom styles for this template -->
<link th:href="@{/css/site.css}" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="#">Spring Boot + SAML + Okta</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/landing">Landing</a>
</li>
</ul>
</div>
</nav>
<main role="main" class="container">
<div class="starter-template">
<section role="content" layout:fragment="content">
<!-- Page content goes here! -->
</section>
</div>
</main><!-- /.container -->
<!-- Main ends -->
<!-- Bootstrap scripts -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script th:src="@{/js/bootstrap.min.js}"></script>
<!-- Font Awesome scripts -->
<script defer src="https://use.fontawesome.com/releases/v5.0.13/js/all.js"></script>
</body>
</html>