Spring boot SAML 2 authentication object null - spring-boot

I've a requirement to integrate SAML authentication with rest API, so that I can make my rest services stateless, the approach which I've taken is as follows
Developed an authentication service behind zuul proxy which is running behind AWS ALB
User tries to generate token via endpoint https://my-domain/as/auth/login
Since user is not logged in, so he gets redirected to IDP where he authenticate
After authentication the IDP redirect user back to my service i.e. at URL https://my-domain/as/auth/login
I check for user authentication principal and if the user is authenticated then I generate JWT token and return it to user
SAML authentication works well, the issue is when the user is redirected back to success URL i.e. https://my-domain/as/auth/login then the authentication object is null because the SecurityContextHolder is cleared after successful authentication and 401 handler kicks in and redirect user to IDP in loop until SAML assertion is failed, please suggest where I'm mistaking
My Zuul proxy config looks like below
zuul:
ribbon:
eager-load:
enabled: true
host:
connect-timeout-millis: 5000000
socket-timeout-millis: 5000000
ignoredServices: ""
ignoredPatterns:
- /as/*
routes:
sensitiveHeaders: Cookie,Set-Cookie
import-data-service:
path: /ids/*
serviceId: IDS
stripPrefix: true
authentication-service:
path: /as/*
serviceId: AS
stripPrefix: true
customSensitiveHeaders: false
My SAML security config looks like below
#Configuration
public class SamlSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public WebSSOProfileOptions defaultWebSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
webSSOProfileOptions.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
return webSSOProfileOptions;
}
#Bean
public SAMLEntryPoint samlEntryPoint() {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
return samlEntryPoint;
}
#Bean
public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter();
}
#Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
return new SimpleUrlAuthenticationFailureHandler();
}
#Bean
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("https://my-domain/as/saml/SSO");
return successRedirectHandler;
}
#Bean
public SessionRepositoryFilter sessionFilter() {
HttpSessionStrategy cookieStrategy = new CookieHttpSessionStrategy();
MapSessionRepository repository = new MapSessionRepository();
((CookieHttpSessionStrategy) cookieStrategy).setCookieName("JSESSIONID");
SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(repository);
sessionRepositoryFilter.setHttpSessionStrategy(cookieStrategy);
return sessionRepositoryFilter;
}
#Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOProcessingFilter;
}
#Bean
public HttpStatusReturningLogoutSuccessHandler successLogoutHandler() {
return new HttpStatusReturningLogoutSuccessHandler();
}
#Bean
public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
#Bean
public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(), new LogoutHandler[] {logoutHandler()},
new LogoutHandler[] {logoutHandler()});
}
#Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
}
#Bean
public MetadataGeneratorFilter metadataGeneratorFilter() {
return new MetadataGeneratorFilter(metadataGenerator());
}
#Bean
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId("entityUniqueIdenifier");
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setRequestSigned(true);
metadataGenerator.setKeyManager(keyManager());
metadataGenerator.setEntityBaseURL("https://my-domain/as");
return metadataGenerator;
}
#Bean
public KeyManager keyManager() {
ClassPathResource storeFile = new ClassPathResource("/saml-keystore.jks");
String storePass = "samlstorepass";
Map<String, String> passwords = new HashMap<>();
passwords.put("mykeyalias", "mykeypass");
return new JKSKeyManager(storeFile, storePass, passwords, "mykeyalias");
}
#Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
extendedMetadata.setSignMetadata(false);
extendedMetadata.setSigningKey("mykeyalias");
extendedMetadata.setEncryptionKey("mykeyalias");
return extendedMetadata;
}
#Bean
public FilterChainProxy samlFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
metadataDisplayFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
samlEntryPoint()));
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/logout/**"),
samlLogoutFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter()));
return new FilterChainProxy(chains);
}
#Bean
public TLSProtocolConfigurer tlsProtocolConfigurer() {
return new TLSProtocolConfigurer();
}
#Bean
public ProtocolSocketFactory socketFactory() {
return new TLSProtocolSocketFactory(keyManager(), null, "default");
}
#Bean
public Protocol socketFactoryProtocol() {
return new Protocol("https", socketFactory(), 443);
}
#Bean
public MethodInvokingFactoryBean socketFactoryInitialization() {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setTargetClass(Protocol.class);
methodInvokingFactoryBean.setTargetMethod("registerProtocol");
Object[] args = {"https", socketFactoryProtocol()};
methodInvokingFactoryBean.setArguments(args);
return methodInvokingFactoryBean;
}
#Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
#Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
#Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
#Bean
public HTTPPostBinding httpPostBinding() {
return new HTTPPostBinding(parserPool(), velocityEngine());
}
#Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
return new HTTPRedirectDeflateBinding(parserPool());
}
#Bean
public SAMLProcessorImpl processor() {
Collection<SAMLBinding> bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding());
return new SAMLProcessorImpl(bindings);
}
#Bean
public HttpClient httpClient() {
return new HttpClient(multiThreadedHttpConnectionManager());
}
#Bean
public MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() {
return new MultiThreadedHttpConnectionManager();
}
#Bean
public static SAMLBootstrap sAMLBootstrap() {
return new CustomSamlBootStrap();
}
#Bean
public SAMLDefaultLogger samlLogger() {
SAMLDefaultLogger samlDefaultLogger = new SAMLDefaultLogger();
samlDefaultLogger.setLogAllMessages(true);
samlDefaultLogger.setLogErrors(true);
return samlDefaultLogger;
}
#Bean
public SAMLContextProviderImpl contextProvider() {
SAMLContextProviderLB samlContextProviderLB = new SAMLContextProviderLB();
samlContextProviderLB.setServerName("my-domain/as");
samlContextProviderLB.setScheme("https");
samlContextProviderLB.setServerPort(443);
samlContextProviderLB.setIncludeServerPortInRequestURL(false);
samlContextProviderLB.setContextPath("");
// samlContextProviderLB.setStorageFactory(new EmptyStorageFactory());
return samlContextProviderLB;
}
// SAML 2.0 WebSSO Assertion Consumer
#Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
// SAML 2.0 Web SSO profile
#Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
// not used but autowired...
// SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
// not used but autowired...
// SAML 2.0 Holder-of-Key Web SSO profile
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
#Bean
public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl();
}
#Bean
public ExtendedMetadataDelegate idpMetadata()
throws MetadataProviderException, ResourceException {
Timer backgroundTaskTimer = new Timer(true);
ResourceBackedMetadataProvider resourceBackedMetadataProvider =
new ResourceBackedMetadataProvider(backgroundTaskTimer,
new ClasspathResource("/IDP-metadata.xml"));
resourceBackedMetadataProvider.setParserPool(parserPool());
ExtendedMetadataDelegate extendedMetadataDelegate =
new ExtendedMetadataDelegate(resourceBackedMetadataProvider, extendedMetadata());
extendedMetadataDelegate.setMetadataTrustCheck(true);
extendedMetadataDelegate.setMetadataRequireSignature(false);
return extendedMetadataDelegate;
}
#Bean
#Qualifier("metadata")
public CachingMetadataManager metadata() throws MetadataProviderException, ResourceException {
List<MetadataProvider> providers = new ArrayList<>();
providers.add(idpMetadata());
return new CachingMetadataManager(providers);
}
#Bean
public SAMLUserDetailsService samlUserDetailsService() {
return new SamlUserDetailsServiceImpl();
}
#Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
final SAMLWebSSOHoKProcessingFilter filter = new SAMLWebSSOHoKProcessingFilter();
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successRedirectHandler());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
#Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(samlUserDetailsService());
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(samlAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(samlEntryPoint());
http.csrf().disable();
http.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
http.authorizeRequests().antMatchers("/error").permitAll().antMatchers("/saml/**").permitAll()
.anyRequest().authenticated();
http.logout().logoutSuccessUrl("/");
}

Finally found out the issue.
In Zuul, the sensitiveHeaders has got a default value of Cookie,Set-Cookie,Authorization
Now if we dont set the property itself, then these headers are going to be treated as sensitive and doesnt get flown downstream to our service.
Had to set the the sensitiveHeaders value to empty so that the cookies get passed on to the service. Cookie contains our JSESSIONID which has identifies the session.
zuul:
ribbon:
eager-load:
enabled: true
host:
connect-timeout-millis: 5000000
socket-timeout-millis: 5000000
ignoredServices: ""
sensitiveHeaders:
ignoredPatterns:
- /as/*
routes:
import-data-service:
path: /ids/*
serviceId: IDS
stripPrefix: true
authentication-service:
path: /as/*
serviceId: AS
stripPrefix: true
customSensitiveHeaders: false

Related

Spring Security CAS - After Receiving the ticket unable to land to login screen

After receiving ticket unable to login to home screen, how I can debug the spring security part in my application?
How can I debug the entry point of the application once ticket received?
#Configuration
#ComponentScan
public class CasSecurityConfiguration
{
private final String casServerLoginUrl;
private final String casServerUrl;
private final String casClientUrl;
#Autowired
public CasSecurityConfiguration(#Value("#{environment.casServerLoginUrl}") String casServerLoginUrl,
#Value("#{environment.casServerUrl}") String casServerUrl,
#Value("#{environment.casClientUrl}") String casClientUrl)
{
this.casServerLoginUrl = casServerLoginUrl;
this.casServerUrl = casServerUrl;
this.casClientUrl = casClientUrl;
}
#Bean
#SuppressWarnings("unchecked")
public CasAuthenticationProvider casAuthenticationProvider(TicketValidator ticketValidator,
ServiceProperties serviceProperties, CodesLdapUserDetailsService userDetailsService)
{
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties);
provider.setTicketValidator(ticketValidator);
provider.setAuthenticationUserDetailsService(userDetailsService);
provider.setKey("cae");
return provider;
}
#Bean
public TicketValidator ticketValidator()
{
System.out.println("In ticketValidator ");
return new Cas20ServiceTicketValidator(casServerUrl);
}
#Bean
public AuthenticationManager authenticationManager(CasAuthenticationProvider casAuthenticationProvider)
{
System.out.println("In authenticationManager ");
return new ProviderManager(List.of(casAuthenticationProvider));
}
#Bean
public CasAuthenticationFilter casAuthenticationFilter(AuthenticationManager authenticationManager,
ServiceProperties serviceProperties)
{
System.out.println("In casAuthenticationFilter ");
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager);
filter.setServiceProperties(serviceProperties);
return filter;
}
#Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint(ServiceProperties serviceProperties)
{
System.out.println("In casAuthenticationEntryPoint ");
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setServiceProperties(serviceProperties);
casAuthenticationEntryPoint.setLoginUrl(casServerLoginUrl);
return casAuthenticationEntryPoint;
}
#Bean
public ServiceProperties serviceProperties()
{
System.out.println("In serviceProperties ");
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(casClientUrl);
serviceProperties.setSendRenew(false);
return serviceProperties;
}
After receiving ticket unable to login to home screen, How I can debug the spring security part in my application?
How can I debug the entry point of the application once ticket received?

Spring Boot SAML problems using reverse proxy

I have a problem with the following scenario: multiple back-end servers process SAML requests forwarded by a reverse-proxy.
I tried to configure the contextProvider as follow: https://docs.spring.io/spring-security-saml/docs/2.0.x/reference/htmlsingle/#configuration-load-balancing
This is my SAML configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(OsiApplication.class);
private static final String PROTOCOL = "https";
private static final String BINDING = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
#Value("${saml.metadata-file}")
private String metadataFile;
#Value("${saml.idp-selection-path}")
private String idpSelectionPath;
#Value("${saml.openmind-app-id}")
private String openmindAppId;
#Value("${saml.success-redirect}")
private String successRedirect;
#Value("${saml.failure-redirect}")
private String failureRedirect;
#Value("${saml.lb-server-name}")
private String lbServerName;
#Value("${saml.lb-scheme}")
private String lbScheme;
#Value("${saml.lb-context-path}")
private String lbContextPath;
#Value("${saml.lb-server-port}")
private int lbServerPort;
#Value("${saml.lb-include-port}")
private boolean lbIncludePort;
#Value("${saml.store-path}")
private String storePath;
#Value("${saml.store-pass}")
private String storePass;
#Value("${saml.store-default-key}")
private String storeDefaultKey;
#Value("${saml.secured}")
private boolean secured;
#Value("${application.admin-code}")
private String adminCode;
#Value("${application.user-code}")
private String userCode;
#Autowired
private SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
private Timer backgroundTaskTimer;
private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
#PostConstruct
public void init() {
this.backgroundTaskTimer = new Timer(true);
this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
}
#PreDestroy
public void destroy() {
this.backgroundTaskTimer.purge();
this.backgroundTaskTimer.cancel();
this.multiThreadedHttpConnectionManager.shutdown();
}
// Initialization of the velocity engine
// 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(samlUserDetailsServiceImpl);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
// Provider of default SAML Context
#Bean
public SAMLContextProviderLB contextProvider() {
// This error may occur during SP-initiated SSO. A SAML authn request is sent to the IdP and a
// SAML response is returned. We check that the InResponseTo field in the SAML response matches
// the ID field of the authn request.
// If they don't match then we throw the error you see
// FIX This can be done by setting DisableInResponseToCheck to true in the <PartnerIdentityProvider> entry in your saml.config.
SAMLContextProviderLB sAMLContextProviderImpl = new SAMLContextProviderLB();
// configuration of reverse proxy of saml
sAMLContextProviderImpl.setServerName(lbServerName);
sAMLContextProviderImpl.setScheme(lbScheme);
sAMLContextProviderImpl.setContextPath(lbContextPath);
sAMLContextProviderImpl.setServerPort(lbServerPort);
sAMLContextProviderImpl.setIncludeServerPortInRequestURL(false);
/*EmptyStorageFactory emptyStorageFactory = new EmptyStorageFactory();
sAMLContextProviderImpl.setStorageFactory(emptyStorageFactory);*/
return sAMLContextProviderImpl;
}
// Initialization of OpenSAML library
#Bean
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() {
WebSSOProfileConsumerImpl webSSOProfileConsumer = new WebSSOProfileConsumerImpl();
webSSOProfileConsumer.setMaxAuthenticationAge(28800);
return webSSOProfileConsumer;
}
// SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
#Bean
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();
}
#Bean
public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl();
}
// Central storage of cryptographic keys
#Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader
.getResource(storePath);
String defaultKey = storeDefaultKey;
Map<String, String> passwords = new HashMap<>();
passwords.put(defaultKey, storePass);
return new JKSKeyManager(storeFile, storePass, passwords, defaultKey);
}
// Setup TLS Socket Factory
#Bean
public TLSProtocolConfigurer tlsProtocolConfigurer() {
return new TLSProtocolConfigurer();
}
#Bean
public ProtocolSocketFactory socketFactory() {
return new TLSProtocolSocketFactory(keyManager(), null, "default");
}
#Bean
public Protocol socketFactoryProtocol() {
return new Protocol(PROTOCOL, socketFactory(), 443);
}
#Bean
public MethodInvokingFactoryBean socketFactoryInitialization() {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setTargetClass(Protocol.class);
methodInvokingFactoryBean.setTargetMethod("registerProtocol");
Object[] args = { PROTOCOL, socketFactoryProtocol() };
methodInvokingFactoryBean.setArguments(args);
return methodInvokingFactoryBean;
}
#Bean
public WebSSOProfileOptions defaultWebSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
webSSOProfileOptions.setBinding(BINDING);
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(false);
extendedMetadata.setSignMetadata(true);
extendedMetadata.setEcpEnabled(false);
return extendedMetadata;
}
// IDP Discovery Service
#Bean
public SAMLDiscovery samlIDPDiscovery() {
SAMLDiscovery idpDiscovery = new SAMLDiscovery();
idpDiscovery.setIdpSelectionPath(idpSelectionPath);
return idpDiscovery;
}
#Bean
#Qualifier("idp-ssocircle")
public ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider()
throws MetadataProviderException {
File file = null;
String metadata = metadataFile;
DefaultResourceLoader loader = new DefaultResourceLoader();
try {
file = loader.getResource(metadata).getFile();
} catch (IOException e) {
log.error("IOException => {}", e);
}
FilesystemMetadataProvider filesystemMetadataProvider = new FilesystemMetadataProvider(file);
filesystemMetadataProvider.setRequireValidMetadata(true);
filesystemMetadataProvider.setParserPool(new BasicParserPool());
filesystemMetadataProvider.initialize();
//
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
extendedMetadata.setSignMetadata(true);
ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(filesystemMetadataProvider, extendedMetadata());
extendedMetadataDelegate.setMetadataTrustCheck(false);
extendedMetadataDelegate.setMetadataRequireSignature(false);
return extendedMetadataDelegate;
}
// IDP Metadata configuration - paths to metadata of IDPs in circle of trust
// is here
// Do no forget to call initalize method on providers
#Bean
#Qualifier("metadata")
public CachingMetadataManager metadata() throws MetadataProviderException {
List<MetadataProvider> providers = new ArrayList<>();
providers.add(ssoCircleExtendedMetadataProvider());
return new CachingMetadataManager(providers);
}
// Filter automatically generates default SP metadata
#Bean
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId(openmindAppId);
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager());
metadataGenerator.setEntityBaseURL(successRedirect);
return metadataGenerator;
}
// 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
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl(successRedirect);
return successRedirectHandler;
}
// Handler deciding where to redirect user after failed login
#Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler();
simpleUrlAuthenticationFailureHandler.setDefaultFailureUrl(failureRedirect);
return simpleUrlAuthenticationFailureHandler;
}
#Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOHoKProcessingFilter;
}
// Processing filter for WebSSO profile messages
#Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOProcessingFilter;
}
#Bean
public MetadataGeneratorFilter metadataGeneratorFilter() {
return new MetadataGeneratorFilter(metadataGenerator());
}
// Handler for successful logout
#Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl(lbContextPath);
return successLogoutHandler;
}
// Logout handler terminating local session
#Bean
public SecurityContextLogoutHandler logoutHandler() {
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());
}
// Overrides default logout processing filter with the one processing SAML
// messages
#Bean
public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(),
new LogoutHandler[] { logoutHandler() },
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());
}
#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<>();
bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding());
bindings.add(artifactBinding(parserPool(), velocityEngine()));
bindings.add(httpSOAP11Binding());
bindings.add(httpPAOS11Binding());
return new SAMLProcessorImpl(bindings);
}
#Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
/**
* Define the security filter chain in order to support SSO Auth by using SAML 2.0
*
* #return Filter chain proxy
* #throws Exception
*/
#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()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),
samlIDPDiscovery()));
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).
*
* #throws Exception
*/
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* Defines the web based security configuration.
*
* #param http
* It allows configuring web based security for specific http requests.
* #throws Exception
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
/*http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER);*/
http
.httpBasic()
.authenticationEntryPoint(samlEntryPoint());
http
.csrf()
.disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
http
.authorizeRequests()
.antMatchers("/error").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest()
//.permitAll();
.authenticated();
http
.exceptionHandling().accessDeniedPage("/403");
http
.logout()
.logoutSuccessUrl("/");
}
/**
* Sets a custom authentication provider.
*
* #param auth
* SecurityBuilder used to create an AuthenticationManager.
* #throws Exception
*/
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(samlAuthenticationProvider());
}
and this is my configuration file
saml.metadata-file= metadata.xml
saml.idp-selection-path= /osi/saml/login?idp=IDP
saml.openmind-app-id= com:na:app:local:dev
saml.success-redirect= https://localhost:4200/context
saml.success= https://localhost:4200/#/files
saml.failure-redirect= https://localhost:4200/#/error
saml.lb-server-name= localhost:4200
saml.lb-scheme= https
saml.lb-context-path= /context
saml.lb-server-port= 4200
saml.lb-include-port= true
saml.store-path= classpath:localhost.jks
saml.store-pass= pass
saml.store-default-key= 1
saml.secured= true
While trying to login the authentification failes:
INFO o.s.s.saml.log.SAMLDefaultLogger - AuthNResponse;FAILURE;100.83.63.69;com:na:app:local:dev;IDP;;;org.opensaml.common.SAMLException: InResponseToField of the Response doesn't correspond to sent message a87hdee46ffejbd317h793f719g64h
adding a web.xml file to the project with the tag solved the issues
<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<distributable />
<!-- ... -->
</web-app>

Customization of TokenEndpoint in Sprin OAuth2

I would like to provide a custom implmentation of the TokenEndpoint class in Spring framework.
Ive copied over the TokenEndpoint class of spring and have made my changes to the required places. But when the applications starts, I'm always getting the error
Caused by: java.lang.IllegalStateException: TokenGranter must be provided
I have provided an implementation for TokenGranter in my OAuthConfig, but spring is not picking up that
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.pathMapping("/oauth/token", "/oauth/token/v1")
.tokenServices(tokenServices())
.tokenGranter(tokenGranter())
.authenticationManager(authenticationManager).tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancer()).accessTokenConverter(accessTokenConverter());
}
#Bean
#Primary
public TokenGranter tokenGranter() {
TokenGranter tokenGranter = null;
if (tokenGranter == null) {
tokenGranter = new TokenGranter() {
private CompositeTokenGranter delegate;
#Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
if (delegate == null) {
delegate = new CompositeTokenGranter(getDefaultTokenGranters());
}
return delegate.grant(grantType, tokenRequest);
}
};
}
return tokenGranter;
}
I even tried to provide this implementation, in my custom TokenEndpoint class.
For now, the implementation of custom TokenEndpoint is exactly the same as Spring's TokenEndpoint.
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
private List<TokenGranter> getDefaultTokenGranters() {
ClientDetailsService clientDetails = clientDetailsService();
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails,
requestFactory));
}
return tokenGranters;
}
private DefaultTokenServices createDefaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setClientDetailsService(clientDetailsService());
tokenServices.setTokenEnhancer(tokenEnhancer());
addUserDetailsService(tokenServices, new CustomDetailsService());
return tokenServices;
}
private ClientDetailsService clientDetailsService() {
ClientDetailsService clientDetailsService = null;
clientDetailsService = new InMemoryClientDetailsService();
addUserDetailsService(createDefaultTokenServices(), new CustomDetailsService());
return clientDetailsService;
}
private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
if (userDetailsService != null) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(
userDetailsService));
tokenServices
.setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider> asList(provider)));
}
}
private AuthorizationCodeServices authorizationCodeServices() {
AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();
return authorizationCodeServices;
}
private OAuth2RequestFactory requestFactory() {
OAuth2RequestFactory requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService());
return requestFactory;
}
#Bean
public JwtTokenStore tokenStore() {
JwtTokenStore jwtTokenStore = new JwtTokenStore(accessTokenConverter());
return jwtTokenStore;
}
#Bean
#Primary
public AuthorizationServerTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setAccessTokenValiditySeconds(-1);
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
return accessToken;
}
};
return converter;
}
Ive been trying to figure this out for a couple of days, but without any luck. So any help would be much appreciated.
I know the question is quite old, but I encountered the same problem and didn't manage to find a complete guide on customizing TokenEndpoint. I wasn't be able to use TokenEnhancer, because I needed to change headers of the response. So, this is the version worked for me.
You define your overwritten controller as usual:
#RequestMapping(value = "/oauth/token")
public class CustomTokenEndpoint extends TokenEndpoint {
#PostMapping
public ResponseEntity<OAuth2AccessToken> postAccessToken(
Principal principal,
#RequestParam Map<String, String> parameters
) throws HttpRequestMethodNotSupportedException {
ResponseEntity<OAuth2AccessToken> defaultResponse = super.postAccessToken(principal, parameters);
// do some work
return defaultResponse;
}
}
And you need to create your own TokenEndpoint bean:
#Bean
#Primary
public TokenEndpoint tokenEndpoint(AuthorizationServerEndpointsConfiguration conf) {
TokenEndpoint tokenEndpoint = new CustomTokenEndpoint();
tokenEndpoint.setClientDetailsService(conf.getEndpointsConfigurer().getClientDetailsService());
tokenEndpoint.setProviderExceptionHandler(conf.getEndpointsConfigurer().getExceptionTranslator());
tokenEndpoint.setTokenGranter(conf.getEndpointsConfigurer().getTokenGranter());
tokenEndpoint.setOAuth2RequestFactory(conf.getEndpointsConfigurer().getOAuth2RequestFactory());
tokenEndpoint.setOAuth2RequestValidator(conf.getEndpointsConfigurer().getOAuth2RequestValidator());
tokenEndpoint.setAllowedRequestMethods(conf.getEndpointsConfigurer().getAllowedTokenEndpointRequestMethods());
return tokenEndpoint;
}
And here's the kicker. You need to allow overwriting spring beans in your application.properties:
spring.main.allow-bean-definition-overriding: true
Hope this helps someone
Why do you need to implement TokenEndpoint again?
You can create a TokenGranter bean and inject it to default endpoints.
Where is getDefaultTokenGranters() method?
It looks like you have an incomplete copy of AuthorizationServerEndpointsConfigurer source code.
Update:
If you want to customize the token response ,use TokenEnhancer.
for example:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
OurUser user = (OurUser) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
Map<String, Object> userDetails = new HashMap<>();
userDetails.put(USERID, user.getId().getId());
userDetails.put(NAME, user.getName());
userDetails.put(MOBILE, user.getMobile());
userDetails.put(EMAIL, user.getEmail());
additionalInfo.put(USERINFO, userDetails);
// Set additional information in token for retriving in #org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
in OAuth2 Config:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints.
.....
// Include additional information to OAuth2 Access token with custom token enhancer
.tokenEnhancer(tokenEnhancer());
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
https://stackoverflow.com/a/28512607/4377110

Integration of Spring session with spring security saml

I want to authenticate a user using openAm and share the session to communicate to other microservices without authenticating the user again. Session details are stored in an external database. I am using spring security saml sample application with all the java configuration to achieve this.
There are two sessions being generated . One before logging to the IDP and one after entering the credentials. If I check at openAm, user is logged in but in the application, i am getting SAMLException specifying InResponseToField of the Response doesnt correspond to sent message 12345(random id).
How to integrate spring session with saml?
Security config file of my sample application
package com.vdenotaris.spring.boot.security.saml.web.config;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
#Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
#Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
#Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
#Bean
public MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() {
return new MultiThreadedHttpConnectionManager();
}
#Bean
public HttpClient httpClient() {
return new HttpClient(multiThreadedHttpConnectionManager());
}
#Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
#Bean
public SAMLContextProviderImpl contextProvider() {
return new SAMLContextProviderImpl();
}
#Bean
public static SAMLBootstrap sAMLBootstrap() {
return new SAMLBootstrap();
}
#Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
#Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
#Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
#Bean
public WebSSOProfileECPImpl ecpprofile() {
return new WebSSOProfileECPImpl();
}
#Bean
public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl();
}
#Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader
.getResource("classpath:/saml/keystore.jks");
String storePass = "password";
Map<String, String> passwords = new HashMap<String, String>();
passwords.put("mydomain", "password");
String defaultKey = "mydomain";
return new JKSKeyManager(storeFile, storePass, passwords, defaultKey);
}
#Bean
public TLSProtocolConfigurer tlsProtocolConfigurer() {
return new TLSProtocolConfigurer();
}
#Bean
public ProtocolSocketFactory socketFactory() {
return new TLSProtocolSocketFactory(keyManager(), null, "default");
}
#Bean
public Protocol socketFactoryProtocol() {
return new Protocol("https", socketFactory(), 443);
}
#Bean
public MethodInvokingFactoryBean socketFactoryInitialization() {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setTargetClass(Protocol.class);
methodInvokingFactoryBean.setTargetMethod("registerProtocol");
Object[] args = {"https", socketFactoryProtocol()};
methodInvokingFactoryBean.setArguments(args);
return methodInvokingFactoryBean;
}
#Bean
public WebSSOProfileOptions defaultWebSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
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;
}
#Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(true);
extendedMetadata.setSignMetadata(false);
return extendedMetadata;
}
#Bean
public SAMLDiscovery samlIDPDiscovery() {
SAMLDiscovery idpDiscovery = new SAMLDiscovery();
idpDiscovery.setIdpSelectionPath("/saml/idpSelection");
return idpDiscovery;
}
#Bean
#Qualifier("idp-ssocircle")
public ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider()
throws MetadataProviderException {
String idpSSOCircleMetadataURL = "http://openam.com:8080/OpenAM/saml2/jsp/exportmetadata.jsp";
Timer backgroundTaskTimer = new Timer(true);
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(
backgroundTaskTimer, httpClient(), idpSSOCircleMetadataURL);
httpMetadataProvider.setParserPool(parserPool());
ExtendedMetadataDelegate extendedMetadataDelegate =
new ExtendedMetadataDelegate(httpMetadataProvider, extendedMetadata());
extendedMetadataDelegate.setMetadataTrustCheck(true);
extendedMetadataDelegate.setMetadataRequireSignature(false);
return extendedMetadataDelegate;
}
#Bean
#Qualifier("metadata")
public CachingMetadataManager metadata() throws MetadataProviderException {
List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
providers.add(ssoCircleExtendedMetadataProvider());
return new CachingMetadataManager(providers);
}
// Filter automatically generates default SP metadata
#Bean
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId("http://localhost:8080/spring-boot-security-saml2-sample/saml/web/metadata");
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager());
return metadataGenerator;
}
// 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
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/");
return successRedirectHandler;
}
// Handler deciding where to redirect user after failed login
#Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler =
new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl("/error");
return failureHandler;
}
#Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOHoKProcessingFilter;
}
#Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOProcessingFilter;
}
#Bean
public MetadataGeneratorFilter metadataGeneratorFilter() {
return new MetadataGeneratorFilter(metadataGenerator());
}
#Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl("/");
return successLogoutHandler;
}
#Bean
public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler logoutHandler =
new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
#Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
return new SAMLLogoutProcessingFilter(successLogoutHandler(),
logoutHandler());
}
#Bean
public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(),
new LogoutHandler[] { logoutHandler() },
new LogoutHandler[] { logoutHandler() });
}
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());
}
#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<SAMLBinding>();
bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding());
bindings.add(artifactBinding(parserPool(), velocityEngine()));
bindings.add(httpSOAP11Binding());
bindings.add(httpPAOS11Binding());
return new SAMLProcessorImpl(bindings);
}
#Bean
public FilterChainProxy samlFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
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()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),
samlIDPDiscovery()));
return new FilterChainProxy(chains);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.authenticationEntryPoint(samlEntryPoint());
http
.csrf()
.disable();
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(sessionRepositoryFilter(sessionRepository(), httpSessionStrategy()),
ChannelProcessingFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/publicUrl").permitAll()
.antMatchers("/app/**").permitAll()
.antMatchers("/error").permitAll()
.antMatchers("/saml/**").permitAll()
.antMatchers("/landing").authenticated();
http
.logout()
.logoutSuccessUrl("/");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(samlAuthenticationProvider());
}
#Bean
public HttpSessionStrategy httpSessionStrategy()
{
return new HeaderHttpSessionStrategy();
}
#Bean
public SessionRepositoryFilter<ExpiringSession> sessionRepositoryFilter(
SessionRepository<ExpiringSession> sessionRepository, HttpSessionStrategy httpSessionStrategy)
{
SessionRepositoryFilter<ExpiringSession> sessionRepositoryFilter = new SessionRepositoryFilter<>(
sessionRepository);
sessionRepositoryFilter.setHttpSessionStrategy(httpSessionStrategy);
return sessionRepositoryFilter;
}
#Bean
public SessionRepository<ExpiringSession> sessionRepository()
{
return new JPASessionRepository(1800);
}
}
JPA Session repository which implements SessionRepository is :
package com.security.repositories.session;
public class JPASessionRepository implements SessionRepository<ExpiringSession>
{
private static final Logger LOG = LogManager.getLogger(JPASessionRepository.class);
private int maxInactiveInterval = -1;
#Autowired
private SpringSessionRepository springSessionRepository;
public JPASessionRepository()
{
}
public JPASessionRepository(int maxInactiveInterval)
{
this.maxInactiveInterval = maxInactiveInterval;
}
#Override
public ExpiringSession createSession()
{
ExpiringSession result = new MapSession();
result.setMaxInactiveIntervalInSeconds(maxInactiveInterval);
return result;
}
#Transactional
#Override
public void save(ExpiringSession session)
{
springSessionRepository.save(convertToDomain(session));
}
#Transactional
#Override
public ExpiringSession getSession(String id)
{
SessionEntity sessionEntity = springSessionRepository.findOne(id);
ExpiringSession saved = null;
if(sessionEntity != null)
{
saved = convertToSession(sessionEntity);
}
if (saved == null)
{
return null;
}
if (saved.isExpired())
{
delete(saved.getId());
return null;
}
return saved;
}
#Override
public void delete(String id)
{
SessionEntity currentSession = springSessionRepository.findOne(id);
if (null != currentSession)
{
springSessionRepository.delete(id);
}
}
private SessionEntity convertToDomain(ExpiringSession session)
{
SessionEntity sessionEntity = new SessionEntity();
sessionEntity.setId(session.getId());
sessionEntity.setLastAccessedTime(session.getLastAccessedTime());
sessionEntity.setCreationTime(session.getCreationTime());
sessionEntity.setData(serializeAttributes(session));
return sessionEntity;
}
byte[] serializeAttributes(ExpiringSession session)
{
Map<String, Object> attributes = new HashMap<>();
for (String attrName : session.getAttributeNames())
{
attributes.put(attrName, session.getAttribute(attrName));
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer;
try
{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
objectOutputStream.writeObject(new SessionAttributes(attributes));
buffer = out.toByteArray();
objectOutputStream.close();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
return buffer;
}
private ExpiringSession convertToSession(SessionEntity sessionEntity)
{
MapSession mapSession = new MapSession();
mapSession.setId(sessionEntity.getId());
mapSession.setLastAccessedTime(sessionEntity.getLastAccessedTime());
mapSession.setCreationTime(sessionEntity.getCreationTime());
mapSession.setMaxInactiveIntervalInSeconds(this.maxInactiveInterval);
SessionAttributes attributes = deserializeAttributes(sessionEntity);
if (attributes != null)
{
for (Map.Entry<String, Object> attribute : attributes.getAttributes().entrySet())
{
mapSession.setAttribute(attribute.getKey(), attribute.getValue());
}
}
return mapSession;
}
private SessionAttributes deserializeAttributes(SessionEntity sessionEntity)
{
SessionAttributes attributes = null;
if (sessionEntity.getData() != null && sessionEntity.getData().length > 0)
{
try
{
ObjectInputStream objectInputStream = new ObjectInputStream(
new ByteArrayInputStream(sessionEntity.getData()));
Object obj = objectInputStream.readObject();
attributes = (SessionAttributes) obj;
objectInputStream.close();
}
catch (IOException | ClassNotFoundException e)
{
LOG.warn(e);
//FIXME:How should this exception be handled?
}
}
return attributes;
}
public Integer getDefaultMaxInactiveInterval()
{
return maxInactiveInterval;
}
public void setDefaultMaxInactiveInterval(int maxInactiveInterval)
{
this.maxInactiveInterval = maxInactiveInterval;
}
public SpringSessionRepository getSpringSessionRepository()
{
return springSessionRepository;
}
public void setSpringSessionRepository(SpringSessionRepository springSessionRepository)
{
this.springSessionRepository = springSessionRepository;
}
}
SP Metadata
<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="http___localhost_8080_spring-boot-security-saml2-sample_saml_web_metadata" entityID="http://localhost:8080/spring-boot-security-saml2-sample/saml/web/metadata"><md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>x509 certificate data</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:KeyDescriptor use="encryption"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>--x509 certificate</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8080/saml/SingleLogout"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8080/saml/SingleLogout"/><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</md:NameIDFormat><md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8080/saml/SSO" index="0" isDefault="true"/><md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="http://localhost:8080/saml/SSO" index="1"/></md:SPSSODescriptor></md:EntityDescriptor>
IDP metadata:-
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8081/OpenAM">
<IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>
sample certificate
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService index="0" isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://localhost:8081/OpenAM/ArtifactResolver/metaAlias/idp"/>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8081/OpenAM/IDPSloRedirect/metaAlias/idp" ResponseLocation="http://localhost:8081/OpenAM/IDPSloRedirect/metaAlias/idp"/>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/OpenAM/IDPSloPOST/metaAlias/idp" ResponseLocation="http://localhost:8081/OpenAM/IDPSloPOST/metaAlias/idp"/>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://localhost:8081/OpenAM/IDPSloSoap/metaAlias/idp"/>
<ManageNameIDService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8081/OpenAM/IDPMniRedirect/metaAlias/idp" ResponseLocation="http://localhost:8081/OpenAM/IDPMniRedirect/metaAlias/idp"/>
<ManageNameIDService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/OpenAM/IDPMniPOST/metaAlias/idp" ResponseLocation="http://localhost:8081/OpenAM/IDPMniPOST/metaAlias/idp"/>
<ManageNameIDService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://localhost:8081/OpenAM/IDPMniSoap/metaAlias/idp"/>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8081/OpenAM/SSORedirect/metaAlias/idp"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/OpenAM/SSOPOST/metaAlias/idp"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://localhost:8081/OpenAM/SSOSoap/metaAlias/idp"/>
<NameIDMappingService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://localhost:8081/OpenAM/NIMSoap/metaAlias/idp"/>
<AssertionIDRequestService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://localhost:8081/OpenAM/AIDReqSoap/IDPRole/metaAlias/idp"/>
<AssertionIDRequestService Binding="urn:oasis:names:tc:SAML:2.0:bindings:URI" Location="http://localhost:8081/OpenAM/AIDReqUri/IDPRole/metaAlias/idp"/>
</IDPSSODescriptor>
</EntityDescriptor>
Please help me resolve the issue
This error is usually caused by a mismatch of the EntityIDs in some way. It looks like your metadata is configured correctly, so I did some looking and found this.
Make sure that application uses the same HttpSession during sending of the request and reception of the response. Typically, this problem arises when the auhentication request is initialized from localhost address or http scheme, while response is received at a public host name or https scheme. E.g., when initializing authentication from URL https://host:port/app/saml/login, the response must be received at https://host;port/app/saml/SSO, not http://host:port/app/saml/SSO or https://localhost:port/app/saml/SSO.
The checking of the InResponseToField can be disabled by re-configuring the context provider as follows:
<bean id="contextProvider" class="org.springframework.security.saml.context.SAMLContextProviderImpl">
<property name="storageFactory">
<bean class="org.springframework.security.saml.storage.EmptyStorageFactory"/>
</property>
</bean>
You should note that this should only be used for development purposes. You should probably use Spring Profiles to enable this configuration locally only because it's less secure.

Spring Security SAML: Getting <Signature> block to appear in <AuthnRequest>

I'm having a hard time trying to get Spring Security SAML to work with ADFS 2.0.
Based on my current configuration, the generated AuthnRequest looks like this:-
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO"
Destination="https://server/adfs/ls/"
ForceAuthn="false" ID="a14edaf38ih92bi8acji5a1664a80e"
IsPassive="false" IssueInstant="2016-02-15T21:05:57.980Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
<saml2p:Scoping ProxyCount="2"/>
</saml2p:AuthnRequest>
However, it causes an error on ADFS side:-
Microsoft.IdentityModel.Protocols.XmlSignature.SignatureVerificationFailedException: MSIS0038: SAML Message has wrong signature. Issuer: 'https://localhost:8443/helix/saml/metadata'.
at Microsoft.IdentityServer.Protocols.Saml.Contract.SamlContractUtility.CreateSamlMessage(MSISSamlBindingMessage message)
at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.CreateErrorMessage(CreateErrorMessageRequest createErrorMessageRequest)
at Microsoft.IdentityServer.Service.SamlProtocol.SamlProtocolService.ProcessRequest(Message requestMessage)
I was told by my security team that my AuthnRequest should look something like this:-
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://localhost:8443/helix/saml/SSO" Destination="https://server/adfs/ls/" ID="_e082771303738e4e6872e8d5711446d4" IssueInstant="2016-02-15T19:51:50.627Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:8443/helix/saml/metadata</saml2:Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#_e082771303738e4e6872e8d5711446d4">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>S8r/XbIhlFGFSMfLoSt/7IlksiI=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>TT4n3==...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIC8z...</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<saml2p:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
<saml2p:RequestedAuthnContext Comparison="exact">
<saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
</saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>
... but, I'm having trouble trying to make the <Signature> block to appear in my AuthnRequest.
My current Spring Security SAML configuration looks like this... I apologize it is rather verbose, but I'm unsure what to paste here besides the whole configuration.
#Configuration
#EnableWebSecurity
public abstract class SecuritySAMLConfig extends WebSecurityConfigurerAdapter {
private static final String METADATA_URL = "https://server/federationmetadata/2007-06/federationmetadata.xml";
private static final String ALIAS = "apollo";
private static final String STORE_PASS = "secret";
#Autowired
private SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
#Autowired
private SAMLAuthenticationProvider samlAuthenticationProvider;
#Bean
public static SAMLBootstrap SAMLBootstrap() {
return new CustomSamlBootstrap();
}
#Bean
public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine();
}
#Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
#Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
#Bean
public MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() {
return new MultiThreadedHttpConnectionManager();
}
#Bean
public HttpClient httpClient(MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager) {
return new HttpClient(multiThreadedHttpConnectionManager);
}
#Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
#Bean
public SAMLContextProviderImpl contextProvider() {
return new SAMLContextProviderImpl();
}
#Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
#Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
#Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
#Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
#Bean
public WebSSOProfileECPImpl ecpprofile() {
return new WebSSOProfileECPImpl();
}
#Bean
public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl();
}
#Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader.getResource("classpath:keystore.jks");
Map<String, String> passwords = new HashMap<>();
passwords.put(ALIAS, STORE_PASS);
return new JKSKeyManager(storeFile, STORE_PASS, passwords, ALIAS);
}
#Bean
public WebSSOProfileOptions webSSOProfileOptions() {
return new WebSSOProfileOptions();
}
#Bean
public SAMLEntryPoint samlEntryPoint(WebSSOProfileOptions webSSOProfileOptions) {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions);
return samlEntryPoint;
}
#Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
// #######
// ####### In theory, by setting these keys, the signature block should appear,
// ####### but, that didn't work for me
// #######
extendedMetadata.setSignMetadata(true);
extendedMetadata.setSigningKey(ALIAS);
extendedMetadata.setEncryptionKey(ALIAS);
// #######
return extendedMetadata;
}
#Bean
public ExtendedMetadataDelegate extendedMetadataDelegate(HttpClient httpClient,
ParserPool parserPool,
ExtendedMetadata extendedMetadata) throws MetadataProviderException {
Timer backgroundTaskTimer = new Timer(true);
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(backgroundTaskTimer,
httpClient,
METADATA_URL);
httpMetadataProvider.setParserPool(parserPool);
ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider,
extendedMetadata);
extendedMetadataDelegate.setMetadataTrustCheck(false);
return extendedMetadataDelegate;
}
#Bean
public CachingMetadataManager metadata(ExtendedMetadataDelegate extendedMetadataDelegate) throws MetadataProviderException {
List<MetadataProvider> providers = new ArrayList<>();
providers.add(extendedMetadataDelegate);
return new CachingMetadataManager(providers);
}
#Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setExtendedMetadata(extendedMetadata);
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager);
return metadataGenerator;
}
#Bean
public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter();
}
#Bean
public SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/landing");
return successRedirectHandler;
}
#Bean
public SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl("/error");
return failureHandler;
}
#Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter(SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
AuthenticationManager authenticationManager,
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager);
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
return samlWebSSOHoKProcessingFilter;
}
#Bean
public SAMLProcessingFilter samlProcessingFilter(AuthenticationManager authenticationManager,
SavedRequestAwareAuthenticationSuccessHandler savedRequestAwareAuthenticationSuccessHandler,
SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler) throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager);
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler);
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler);
return samlWebSSOProcessingFilter;
}
#Bean
public MetadataGeneratorFilter metadataGeneratorFilter(MetadataGenerator metadataGenerator) {
return new MetadataGeneratorFilter(metadataGenerator);
}
#Bean
public SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl("/");
return successLogoutHandler;
}
#Bean
public SecurityContextLogoutHandler securityContextLogoutHandler() {
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
#Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
SecurityContextLogoutHandler securityContextLogoutHandler) {
return new SAMLLogoutProcessingFilter(simpleUrlLogoutSuccessHandler, securityContextLogoutHandler);
}
#Bean
public SAMLLogoutFilter samlLogoutFilter(SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler,
SecurityContextLogoutHandler securityContextLogoutHandler) {
return new SAMLLogoutFilter(simpleUrlLogoutSuccessHandler,
new LogoutHandler[]{securityContextLogoutHandler},
new LogoutHandler[]{securityContextLogoutHandler});
}
#Bean
public HTTPArtifactBinding artifactBinding(HTTPSOAP11Binding httpSOAP11Binding,
HttpClient httpClient,
ParserPool parserPool,
VelocityEngine velocityEngine) {
ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient);
artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(httpSOAP11Binding));
return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile);
}
#Bean
public HTTPSOAP11Binding httpSOAP11Binding(ParserPool parserPool) {
return new HTTPSOAP11Binding(parserPool);
}
#Bean
public HTTPPostBinding httpPostBinding(ParserPool parserPool, VelocityEngine velocityEngine) {
return new HTTPPostBinding(parserPool, velocityEngine);
}
#Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding(ParserPool parserPool) {
return new HTTPRedirectDeflateBinding(parserPool);
}
#Bean
public HTTPPAOS11Binding httpPAOS11Binding(ParserPool parserPool) {
return new HTTPPAOS11Binding(parserPool);
}
#Bean
public SAMLProcessorImpl processor(HTTPRedirectDeflateBinding httpRedirectDeflateBinding,
HTTPPostBinding httpPostBinding,
HTTPArtifactBinding httpArtifactBinding,
HTTPSOAP11Binding httpSOAP11Binding,
HTTPPAOS11Binding httpPAOS11Binding) {
Collection<SAMLBinding> bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding);
bindings.add(httpPostBinding);
bindings.add(httpArtifactBinding);
bindings.add(httpSOAP11Binding);
bindings.add(httpPAOS11Binding);
return new SAMLProcessorImpl(bindings);
}
#Bean
public FilterChainProxy filterChainProxy(SAMLEntryPoint samlEntryPoint,
SAMLLogoutFilter samlLogoutFilter,
MetadataDisplayFilter metadataDisplayFilter,
SAMLProcessingFilter samlProcessingFilter,
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter,
SAMLLogoutProcessingFilter samlLogoutProcessingFilter) 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/**"), samlProcessingFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
samlWebSSOHoKProcessingFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter));
return new FilterChainProxy(chains);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(samlAuthenticationProvider);
}
}
I highly appreciate it if someone could nudge me towards the right direction.
Thank you.
UPDATE
My SP metadata generated by MetadataGenerator looks like this:-
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="https___localhost_8443_helix_saml_metadata" entityID="https://localhost:8443/helix/saml/metadata">
<md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIICx....</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIICxz....</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SingleLogout"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://localhost:8443/helix/saml/SingleLogout"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:8443/helix/saml/SSO" index="0" isDefault="true"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://localhost:8443/helix/saml/SSO" index="1"/>
</md:SPSSODescriptor>
</md:EntityDescriptor>
I also want to point out that I'm using SHA256withRSA instead of SHA1withRSA. Hence, I replace the default SAMLBootstrap bean with the below:-
public final class CustomSamlBootstrap extends SAMLBootstrap {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
super.postProcessBeanFactory(beanFactory);
BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
}
}
UPDATE 2
I got it working by modifying WebSSOProfileOptions.
#Bean
public WebSSOProfileOptions webSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
// Added this line
webSSOProfileOptions.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);
return webSSOProfileOptions;
}
What binding do you use to send the Request to the IDP? In case it's HTTP-Request the SAML standard mandates that he Signature is removed before delivering the message. The signature is then performed on the serialized request and sent as a GET parameter.
I believe you should have set:
#Bean
public MetadataGenerator metadataGenerator(ExtendedMetadata extendedMetadata, KeyManager keyManager) {
MetadataGenerator metadataGenerator = new MetadataGenerator();
// ... other configuration here
metadataGenerator.setRequestSigned ( true );
return metadataGenerator;
}
in ExtendedMetadata, you were only setting Metadata file to be signed. This doesn't affect signing of AuthnRequest.

Resources