parent
cca35bdd93
commit
d538423f98
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -19,8 +19,6 @@ package org.springframework.security.config.annotation.web.configurers.saml2;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
import org.opensaml.core.Version;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
|
@ -50,6 +48,7 @@ import org.springframework.security.saml2.provider.service.web.RelyingPartyRegis
|
|||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||
|
@ -115,9 +114,11 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
|
||||
private String loginPage;
|
||||
|
||||
private String loginProcessingUrl = Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
|
||||
private String authenticationRequestUri = "/saml2/authenticate/{registrationId}";
|
||||
|
||||
private AuthenticationRequestEndpointConfig authenticationRequestEndpoint = new AuthenticationRequestEndpointConfig();
|
||||
private Saml2AuthenticationRequestResolver authenticationRequestResolver;
|
||||
|
||||
private String loginProcessingUrl = Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
|
||||
|
||||
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||
|
||||
|
@ -176,6 +177,20 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link Saml2AuthenticationRequestResolver} for generating SAML 2.0
|
||||
* Authentication Requests.
|
||||
* @param authenticationRequestResolver
|
||||
* @return the {@link Saml2LoginConfigurer} for further configuration
|
||||
* @since 5.7
|
||||
*/
|
||||
public Saml2LoginConfigurer<B> authenticationRequestResolver(
|
||||
Saml2AuthenticationRequestResolver authenticationRequestResolver) {
|
||||
Assert.notNull(authenticationRequestResolver, "authenticationRequestResolver cannot be null");
|
||||
this.authenticationRequestResolver = authenticationRequestResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the URL to validate the credentials. If specified a custom URL, consider
|
||||
* specifying a custom {@link AuthenticationConverter} via
|
||||
|
@ -200,7 +215,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* Initializes this filter chain for SAML 2 Login. The following actions are taken:
|
||||
* <ul>
|
||||
* <li>The WebSSO endpoint has CSRF disabled, typically {@code /login/saml2/sso}</li>
|
||||
|
@ -226,8 +241,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
super.init(http);
|
||||
}
|
||||
else {
|
||||
Map<String, String> providerUrlMap = getIdentityProviderUrlMap(
|
||||
this.authenticationRequestEndpoint.filterProcessingUrl, this.relyingPartyRegistrationRepository);
|
||||
Map<String, String> providerUrlMap = getIdentityProviderUrlMap(this.authenticationRequestUri,
|
||||
this.relyingPartyRegistrationRepository);
|
||||
boolean singleProvider = providerUrlMap.size() == 1;
|
||||
if (singleProvider) {
|
||||
// Setup auto-redirect to provider login page
|
||||
|
@ -247,14 +262,16 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* During the {@code configure} phase, a
|
||||
* {@link Saml2WebSsoAuthenticationRequestFilter} is added to handle SAML 2.0
|
||||
* AuthNRequest redirects
|
||||
*/
|
||||
@Override
|
||||
public void configure(B http) throws Exception {
|
||||
http.addFilter(this.authenticationRequestEndpoint.build(http));
|
||||
Saml2WebSsoAuthenticationRequestFilter filter = getAuthenticationRequestFilter(http);
|
||||
filter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
|
||||
http.addFilter(postProcess(filter));
|
||||
super.configure(http);
|
||||
if (this.authenticationManager == null) {
|
||||
registerDefaultAuthenticationProvider(http);
|
||||
|
@ -264,6 +281,11 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
}
|
||||
}
|
||||
|
||||
private RelyingPartyRegistrationResolver relyingPartyRegistrationResolver(B http) {
|
||||
RelyingPartyRegistrationRepository registrations = relyingPartyRegistrationRepository(http);
|
||||
return new DefaultRelyingPartyRegistrationResolver(registrations);
|
||||
}
|
||||
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(B http) {
|
||||
if (this.relyingPartyRegistrationRepository == null) {
|
||||
this.relyingPartyRegistrationRepository = getSharedOrBean(http, RelyingPartyRegistrationRepository.class);
|
||||
|
@ -276,6 +298,46 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
saml2WebSsoAuthenticationFilter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
|
||||
}
|
||||
|
||||
private Saml2WebSsoAuthenticationRequestFilter getAuthenticationRequestFilter(B http) {
|
||||
Saml2AuthenticationRequestResolver authenticationRequestResolver = getAuthenticationRequestResolver(http);
|
||||
if (authenticationRequestResolver != null) {
|
||||
return new Saml2WebSsoAuthenticationRequestFilter(authenticationRequestResolver);
|
||||
}
|
||||
return new Saml2WebSsoAuthenticationRequestFilter(getAuthenticationRequestContextResolver(http),
|
||||
getAuthenticationRequestFactory(http));
|
||||
}
|
||||
|
||||
private Saml2AuthenticationRequestResolver getAuthenticationRequestResolver(B http) {
|
||||
if (this.authenticationRequestResolver != null) {
|
||||
return this.authenticationRequestResolver;
|
||||
}
|
||||
return getBeanOrNull(http, Saml2AuthenticationRequestResolver.class);
|
||||
}
|
||||
|
||||
private Saml2AuthenticationRequestFactory getAuthenticationRequestFactory(B http) {
|
||||
Saml2AuthenticationRequestFactory resolver = getSharedOrBean(http, Saml2AuthenticationRequestFactory.class);
|
||||
if (resolver != null) {
|
||||
return resolver;
|
||||
}
|
||||
if (version().startsWith("4")) {
|
||||
return new OpenSaml4AuthenticationRequestFactory();
|
||||
}
|
||||
else {
|
||||
return new OpenSamlAuthenticationRequestFactory();
|
||||
}
|
||||
}
|
||||
|
||||
private Saml2AuthenticationRequestContextResolver getAuthenticationRequestContextResolver(B http) {
|
||||
Saml2AuthenticationRequestContextResolver resolver = getBeanOrNull(http,
|
||||
Saml2AuthenticationRequestContextResolver.class);
|
||||
if (resolver != null) {
|
||||
return resolver;
|
||||
}
|
||||
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
|
||||
this.relyingPartyRegistrationRepository);
|
||||
return new DefaultSaml2AuthenticationRequestContextResolver(registrationResolver);
|
||||
}
|
||||
|
||||
private AuthenticationConverter getAuthenticationConverter(B http) {
|
||||
if (this.authenticationConverter != null) {
|
||||
return this.authenticationConverter;
|
||||
|
@ -324,8 +386,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
return;
|
||||
}
|
||||
loginPageGeneratingFilter.setSaml2LoginEnabled(true);
|
||||
loginPageGeneratingFilter.setSaml2AuthenticationUrlToProviderName(this.getIdentityProviderUrlMap(
|
||||
this.authenticationRequestEndpoint.filterProcessingUrl, this.relyingPartyRegistrationRepository));
|
||||
loginPageGeneratingFilter.setSaml2AuthenticationUrlToProviderName(
|
||||
this.getIdentityProviderUrlMap(this.authenticationRequestUri, this.relyingPartyRegistrationRepository));
|
||||
loginPageGeneratingFilter.setLoginPageUrl(this.getLoginPage());
|
||||
loginPageGeneratingFilter.setFailureUrl(this.getFailureUrl());
|
||||
}
|
||||
|
@ -379,46 +441,4 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
}
|
||||
}
|
||||
|
||||
private final class AuthenticationRequestEndpointConfig {
|
||||
|
||||
private String filterProcessingUrl = "/saml2/authenticate/{registrationId}";
|
||||
|
||||
private AuthenticationRequestEndpointConfig() {
|
||||
}
|
||||
|
||||
private Filter build(B http) {
|
||||
Saml2AuthenticationRequestFactory authenticationRequestResolver = getResolver(http);
|
||||
Saml2AuthenticationRequestContextResolver contextResolver = getContextResolver(http);
|
||||
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> repository = getAuthenticationRequestRepository(
|
||||
http);
|
||||
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(contextResolver,
|
||||
authenticationRequestResolver);
|
||||
filter.setAuthenticationRequestRepository(repository);
|
||||
return postProcess(filter);
|
||||
}
|
||||
|
||||
private Saml2AuthenticationRequestFactory getResolver(B http) {
|
||||
Saml2AuthenticationRequestFactory resolver = getSharedOrBean(http, Saml2AuthenticationRequestFactory.class);
|
||||
if (resolver == null) {
|
||||
if (version().startsWith("4")) {
|
||||
return new OpenSaml4AuthenticationRequestFactory();
|
||||
}
|
||||
return new OpenSamlAuthenticationRequestFactory();
|
||||
}
|
||||
return resolver;
|
||||
}
|
||||
|
||||
private Saml2AuthenticationRequestContextResolver getContextResolver(B http) {
|
||||
Saml2AuthenticationRequestContextResolver resolver = getBeanOrNull(http,
|
||||
Saml2AuthenticationRequestContextResolver.class);
|
||||
if (resolver == null) {
|
||||
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(
|
||||
Saml2LoginConfigurer.this.relyingPartyRegistrationRepository);
|
||||
return new DefaultSaml2AuthenticationRequestContextResolver(relyingPartyRegistrationResolver);
|
||||
}
|
||||
return resolver;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -80,9 +80,13 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
|
|||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
|
||||
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
|
@ -104,6 +108,7 @@ import static org.mockito.ArgumentMatchers.anyString;
|
|||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
|
@ -211,6 +216,41 @@ public class Saml2LoginConfigurerTests {
|
|||
assertThat(inflated).contains("ForceAuthn=\"true\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception {
|
||||
this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire();
|
||||
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
|
||||
UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build();
|
||||
String samlRequest = components.getQueryParams().getFirst("SAMLRequest");
|
||||
String decoded = URLDecoder.decode(samlRequest, "UTF-8");
|
||||
String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded));
|
||||
assertThat(inflated).contains("ForceAuthn=\"true\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticationRequestWhenAuthenticationRequestResolverDslThenUses() throws Exception {
|
||||
this.spring.register(CustomAuthenticationRequestResolverDsl.class).autowire();
|
||||
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
|
||||
UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build();
|
||||
String samlRequest = components.getQueryParams().getFirst("SAMLRequest");
|
||||
String decoded = URLDecoder.decode(samlRequest, "UTF-8");
|
||||
String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded));
|
||||
assertThat(inflated).contains("ForceAuthn=\"true\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticationRequestWhenAuthenticationRequestResolverAndFactoryThenResolverTakesPrecedence()
|
||||
throws Exception {
|
||||
this.spring.register(CustomAuthenticationRequestResolverPrecedence.class).autowire();
|
||||
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
|
||||
UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build();
|
||||
String samlRequest = components.getQueryParams().getFirst("SAMLRequest");
|
||||
String decoded = URLDecoder.decode(samlRequest, "UTF-8");
|
||||
String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded));
|
||||
assertThat(inflated).contains("ForceAuthn=\"true\"");
|
||||
verifyNoInteractions(this.spring.getContext().getBean(Saml2AuthenticationRequestFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenCustomAuthenticationConverterThenUses() throws Exception {
|
||||
this.spring.register(CustomAuthenticationConverter.class).autowire();
|
||||
|
@ -506,6 +546,103 @@ public class Saml2LoginConfigurerTests {
|
|||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
static class CustomAuthenticationRequestResolverBean {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authz) -> authz
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Saml2AuthenticationRequestResolver authenticationRequestResolver(
|
||||
RelyingPartyRegistrationRepository registrations) {
|
||||
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
|
||||
registrations);
|
||||
OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver(
|
||||
registrationResolver);
|
||||
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
static class CustomAuthenticationRequestResolverDsl {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http, RelyingPartyRegistrationRepository registrations)
|
||||
throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authz) -> authz
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login((saml2) -> saml2
|
||||
.authenticationRequestResolver(authenticationRequestResolver(registrations))
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
Saml2AuthenticationRequestResolver authenticationRequestResolver(
|
||||
RelyingPartyRegistrationRepository registrations) {
|
||||
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
|
||||
registrations);
|
||||
OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver(
|
||||
registrationResolver);
|
||||
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
static class CustomAuthenticationRequestResolverPrecedence {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authz) -> authz
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.saml2Login(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Saml2AuthenticationRequestFactory authenticationRequestFactory() {
|
||||
return mock(Saml2AuthenticationRequestFactory.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Saml2AuthenticationRequestResolver authenticationRequestResolver(
|
||||
RelyingPartyRegistrationRepository registrations) {
|
||||
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
|
||||
registrations);
|
||||
OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver(
|
||||
registrationResolver);
|
||||
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
|
||||
return delegate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
static class CustomAuthenticationConverter extends WebSecurityConfigurerAdapter {
|
||||
|
|
|
@ -176,92 +176,21 @@ var relyingPartyRegistration: RelyingPartyRegistration? =
|
|||
There are a number of reasons that you may want to adjust an `AuthnRequest`.
|
||||
For example, you may want `ForceAuthN` to be set to `true`, which Spring Security sets to `false` by default.
|
||||
|
||||
If you don't need information from the `HttpServletRequest` to make your decision, then the easiest way is to xref:servlet/saml2/login/overview.adoc#servlet-saml2login-opensaml-customization[register a custom `AuthnRequestMarshaller` with OpenSAML].
|
||||
This will give you access to post-process the `AuthnRequest` instance before it's serialized.
|
||||
|
||||
But, if you do need something from the request, then you can use create a custom `Saml2AuthenticationRequestContext` implementation and then a `Converter<Saml2AuthenticationRequestContext, AuthnRequest>` to build an `AuthnRequest` yourself, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class AuthnRequestConverter implements
|
||||
Converter<Saml2AuthenticationRequestContext, AuthnRequest> {
|
||||
|
||||
private final AuthnRequestBuilder authnRequestBuilder;
|
||||
private final IssuerBuilder issuerBuilder;
|
||||
|
||||
// ... constructor
|
||||
|
||||
public AuthnRequest convert(Saml2AuthenticationRequestContext context) {
|
||||
MySaml2AuthenticationRequestContext myContext = (MySaml2AuthenticationRequestContext) context;
|
||||
Issuer issuer = issuerBuilder.buildObject();
|
||||
issuer.setValue(myContext.getIssuer());
|
||||
|
||||
AuthnRequest authnRequest = authnRequestBuilder.buildObject();
|
||||
authnRequest.setIssuer(issuer);
|
||||
authnRequest.setDestination(myContext.getDestination());
|
||||
authnRequest.setAssertionConsumerServiceURL(myContext.getAssertionConsumerServiceUrl());
|
||||
|
||||
// ... additional settings
|
||||
|
||||
authRequest.setForceAuthn(myContext.getForceAuthn());
|
||||
return authnRequest;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class AuthnRequestConverter : Converter<Saml2AuthenticationRequestContext, AuthnRequest> {
|
||||
private val authnRequestBuilder: AuthnRequestBuilder? = null
|
||||
private val issuerBuilder: IssuerBuilder? = null
|
||||
|
||||
// ... constructor
|
||||
override fun convert(context: Saml2AuthenticationRequestContext): AuthnRequest {
|
||||
val myContext: MySaml2AuthenticationRequestContext = context
|
||||
val issuer: Issuer = issuerBuilder.buildObject()
|
||||
issuer.value = myContext.getIssuer()
|
||||
val authnRequest: AuthnRequest = authnRequestBuilder.buildObject()
|
||||
authnRequest.issuer = issuer
|
||||
authnRequest.destination = myContext.getDestination()
|
||||
authnRequest.assertionConsumerServiceURL = myContext.getAssertionConsumerServiceUrl()
|
||||
|
||||
// ... additional settings
|
||||
authRequest.setForceAuthn(myContext.getForceAuthn())
|
||||
return authnRequest
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Then, you can construct your own `Saml2AuthenticationRequestContextResolver` and `Saml2AuthenticationRequestFactory` and publish them as ``@Bean``s:
|
||||
You can customize elements of OpenSAML's `AuthnRequest` by publishing an `OpenSaml4AuthenticationRequestResolver` as a `@Bean`, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver() {
|
||||
Saml2AuthenticationRequestContextResolver resolver =
|
||||
new DefaultSaml2AuthenticationRequestContextResolver();
|
||||
return request -> {
|
||||
Saml2AuthenticationRequestContext context = resolver.resolve(request);
|
||||
return new MySaml2AuthenticationRequestContext(context, request.getParameter("force") != null);
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
Saml2AuthenticationRequestFactory authenticationRequestFactory(
|
||||
AuthnRequestConverter authnRequestConverter) {
|
||||
|
||||
OpenSaml4AuthenticationRequestFactory authenticationRequestFactory =
|
||||
new OpenSaml4AuthenticationRequestFactory();
|
||||
authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter);
|
||||
return authenticationRequestFactory;
|
||||
Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
|
||||
RelyingPartyRegistrationResolver registrationResolver =
|
||||
new DefaultRelyingPartyRegistrationResolver(registrations);
|
||||
OpenSaml4AuthenticationRequestResolver authenticationRequestResolver =
|
||||
new OpenSaml4AuthenticationRequestResolver(registrationResolver);
|
||||
authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context
|
||||
.getAuthnRequest().setForceAuthn(true));
|
||||
return authenticationRequestResolver;
|
||||
}
|
||||
----
|
||||
|
||||
|
@ -269,24 +198,14 @@ Saml2AuthenticationRequestFactory authenticationRequestFactory(
|
|||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
open fun authenticationRequestContextResolver(): Saml2AuthenticationRequestContextResolver {
|
||||
val resolver: Saml2AuthenticationRequestContextResolver = DefaultSaml2AuthenticationRequestContextResolver()
|
||||
return Saml2AuthenticationRequestContextResolver { request: HttpServletRequest ->
|
||||
val context = resolver.resolve(request)
|
||||
MySaml2AuthenticationRequestContext(
|
||||
context,
|
||||
request.getParameter("force") != null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun authenticationRequestFactory(
|
||||
authnRequestConverter: AuthnRequestConverter?
|
||||
): Saml2AuthenticationRequestFactory? {
|
||||
val authenticationRequestFactory = OpenSaml4AuthenticationRequestFactory()
|
||||
authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter)
|
||||
return authenticationRequestFactory
|
||||
fun authenticationRequestResolver(registrations : RelyingPartyRegistrationRepository) : Saml2AuthenticationRequestResolver {
|
||||
val registrationResolver : RelyingPartyRegistrationResolver =
|
||||
new DefaultRelyingPartyRegistrationResolver(registrations)
|
||||
val authenticationRequestResolver : OpenSaml4AuthenticationRequestResolver =
|
||||
new OpenSaml4AuthenticationRequestResolver(registrationResolver)
|
||||
authenticationRequestResolver.setAuthnRequestCustomizer((context) -> context
|
||||
.getAuthnRequest().setForceAuthn(true))
|
||||
return authenticationRequestResolver
|
||||
}
|
||||
----
|
||||
====
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
/**
|
||||
|
@ -54,6 +55,17 @@ public class Saml2PostAuthenticationRequest extends AbstractSaml2AuthenticationR
|
|||
return new Builder().authenticationRequestUri(context.getDestination()).relayState(context.getRelayState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link Builder} from a {@link RelyingPartyRegistration} object.
|
||||
* @param registration a relying party registration
|
||||
* @return a modifiable builder object
|
||||
* @since 5.7
|
||||
*/
|
||||
public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) {
|
||||
String location = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
|
||||
return new Builder().authenticationRequestUri(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for a {@link Saml2PostAuthenticationRequest} object.
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.security.saml2.provider.service.authentication;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
|
||||
/**
|
||||
|
@ -77,6 +78,18 @@ public final class Saml2RedirectAuthenticationRequest extends AbstractSaml2Authe
|
|||
return new Builder().authenticationRequestUri(context.getDestination()).relayState(context.getRelayState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link Saml2PostAuthenticationRequest.Builder} from a
|
||||
* {@link RelyingPartyRegistration} object.
|
||||
* @param registration a relying party registration
|
||||
* @return a modifiable builder object
|
||||
* @since 5.7
|
||||
*/
|
||||
public static Builder withRelyingPartyRegistration(RelyingPartyRegistration registration) {
|
||||
String location = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
|
||||
return new Builder().authenticationRequestUri(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for a {@link Saml2RedirectAuthenticationRequest} object.
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -42,6 +42,7 @@ import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2A
|
|||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher.MatchResult;
|
||||
|
@ -78,11 +79,7 @@ import org.springframework.web.util.UriUtils;
|
|||
*/
|
||||
public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter {
|
||||
|
||||
private final Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver;
|
||||
|
||||
private Saml2AuthenticationRequestFactory authenticationRequestFactory;
|
||||
|
||||
private RequestMatcher redirectMatcher = new AntPathRequestMatcher("/saml2/authenticate/{registrationId}");
|
||||
private final Saml2AuthenticationRequestResolver authenticationRequestResolver;
|
||||
|
||||
private Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository = new HttpSessionSaml2AuthenticationRequestRepository();
|
||||
|
||||
|
@ -129,11 +126,20 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
|||
public Saml2WebSsoAuthenticationRequestFilter(
|
||||
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver,
|
||||
Saml2AuthenticationRequestFactory authenticationRequestFactory) {
|
||||
this(new FactorySaml2AuthenticationRequestResolver(authenticationRequestContextResolver,
|
||||
authenticationRequestFactory));
|
||||
}
|
||||
|
||||
Assert.notNull(authenticationRequestContextResolver, "authenticationRequestContextResolver cannot be null");
|
||||
Assert.notNull(authenticationRequestFactory, "authenticationRequestFactory cannot be null");
|
||||
this.authenticationRequestContextResolver = authenticationRequestContextResolver;
|
||||
this.authenticationRequestFactory = authenticationRequestFactory;
|
||||
/**
|
||||
* Construct a {@link Saml2WebSsoAuthenticationRequestFilter} with the strategy for
|
||||
* resolving the {@code AuthnRequest}
|
||||
* @param authenticationRequestResolver the strategy for resolving the
|
||||
* {@code AuthnRequest}
|
||||
* @since 5.7
|
||||
*/
|
||||
public Saml2WebSsoAuthenticationRequestFilter(Saml2AuthenticationRequestResolver authenticationRequestResolver) {
|
||||
Assert.notNull(authenticationRequestResolver, "authenticationRequestResolver cannot be null");
|
||||
this.authenticationRequestResolver = authenticationRequestResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,16 +152,23 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
|||
@Deprecated
|
||||
public void setAuthenticationRequestFactory(Saml2AuthenticationRequestFactory authenticationRequestFactory) {
|
||||
Assert.notNull(authenticationRequestFactory, "authenticationRequestFactory cannot be null");
|
||||
this.authenticationRequestFactory = authenticationRequestFactory;
|
||||
Assert.isInstanceOf(FactorySaml2AuthenticationRequestResolver.class, this.authenticationRequestResolver,
|
||||
"You cannot supply both a Saml2AuthenticationRequestResolver and a Saml2AuthenticationRequestFactory");
|
||||
((FactorySaml2AuthenticationRequestResolver) this.authenticationRequestResolver).authenticationRequestFactory = authenticationRequestFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given {@link RequestMatcher} that activates this filter for a given request
|
||||
* @param redirectMatcher the {@link RequestMatcher} to use
|
||||
* @deprecated Configure the request matcher in an implementation of
|
||||
* {@link Saml2AuthenticationRequestResolver} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRedirectMatcher(RequestMatcher redirectMatcher) {
|
||||
Assert.notNull(redirectMatcher, "redirectMatcher cannot be null");
|
||||
this.redirectMatcher = redirectMatcher;
|
||||
Assert.isInstanceOf(FactorySaml2AuthenticationRequestResolver.class, this.authenticationRequestResolver,
|
||||
"You cannot supply a Saml2AuthenticationRequestResolver and a redirect matcher");
|
||||
((FactorySaml2AuthenticationRequestResolver) this.authenticationRequestResolver).redirectMatcher = redirectMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -174,30 +187,21 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
|||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
MatchResult matcher = this.redirectMatcher.matcher(request);
|
||||
if (!matcher.isMatch()) {
|
||||
AbstractSaml2AuthenticationRequest authenticationRequest = this.authenticationRequestResolver.resolve(request);
|
||||
if (authenticationRequest == null) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
Saml2AuthenticationRequestContext context = this.authenticationRequestContextResolver.resolve(request);
|
||||
if (context == null) {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
RelyingPartyRegistration relyingParty = context.getRelyingPartyRegistration();
|
||||
if (relyingParty.getAssertingPartyDetails().getSingleSignOnServiceBinding() == Saml2MessageBinding.REDIRECT) {
|
||||
sendRedirect(request, response, context);
|
||||
if (authenticationRequest instanceof Saml2RedirectAuthenticationRequest) {
|
||||
sendRedirect(request, response, (Saml2RedirectAuthenticationRequest) authenticationRequest);
|
||||
}
|
||||
else {
|
||||
sendPost(request, response, context);
|
||||
sendPost(request, response, (Saml2PostAuthenticationRequest) authenticationRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendRedirect(HttpServletRequest request, HttpServletResponse response,
|
||||
Saml2AuthenticationRequestContext context) throws IOException {
|
||||
Saml2RedirectAuthenticationRequest authenticationRequest = this.authenticationRequestFactory
|
||||
.createRedirectAuthenticationRequest(context);
|
||||
Saml2RedirectAuthenticationRequest authenticationRequest) throws IOException {
|
||||
this.authenticationRequestRepository.saveAuthenticationRequest(authenticationRequest, request, response);
|
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
||||
.fromUriString(authenticationRequest.getAuthenticationRequestUri());
|
||||
|
@ -218,9 +222,7 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
|||
}
|
||||
|
||||
private void sendPost(HttpServletRequest request, HttpServletResponse response,
|
||||
Saml2AuthenticationRequestContext context) throws IOException {
|
||||
Saml2PostAuthenticationRequest authenticationRequest = this.authenticationRequestFactory
|
||||
.createPostAuthenticationRequest(context);
|
||||
Saml2PostAuthenticationRequest authenticationRequest) throws IOException {
|
||||
this.authenticationRequestRepository.saveAuthenticationRequest(authenticationRequest, request, response);
|
||||
String html = createSamlPostRequestFormData(authenticationRequest);
|
||||
response.setContentType(MediaType.TEXT_HTML_VALUE);
|
||||
|
@ -269,4 +271,41 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
|
|||
return html.toString();
|
||||
}
|
||||
|
||||
private static class FactorySaml2AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver {
|
||||
|
||||
private final Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver;
|
||||
|
||||
private RequestMatcher redirectMatcher = new AntPathRequestMatcher("/saml2/authenticate/{registrationId}");
|
||||
|
||||
private Saml2AuthenticationRequestFactory authenticationRequestFactory;
|
||||
|
||||
FactorySaml2AuthenticationRequestResolver(
|
||||
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver,
|
||||
Saml2AuthenticationRequestFactory authenticationRequestFactory) {
|
||||
Assert.notNull(authenticationRequestContextResolver, "authenticationRequestContextResolver cannot be null");
|
||||
Assert.notNull(authenticationRequestFactory, "authenticationRequestFactory cannot be null");
|
||||
this.authenticationRequestContextResolver = authenticationRequestContextResolver;
|
||||
this.authenticationRequestFactory = authenticationRequestFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractSaml2AuthenticationRequest resolve(HttpServletRequest request) {
|
||||
MatchResult matcher = this.redirectMatcher.matcher(request);
|
||||
if (!matcher.isMatch()) {
|
||||
return null;
|
||||
}
|
||||
Saml2AuthenticationRequestContext context = this.authenticationRequestContextResolver.resolve(request);
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
Saml2MessageBinding binding = context.getRelyingPartyRegistration().getAssertingPartyDetails()
|
||||
.getSingleSignOnServiceBinding();
|
||||
if (binding == Saml2MessageBinding.REDIRECT) {
|
||||
return this.authenticationRequestFactory.createRedirectAuthenticationRequest(context);
|
||||
}
|
||||
return this.authenticationRequestFactory.createPostAuthenticationRequest(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
|
||||
import org.opensaml.core.config.ConfigurationService;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
|
||||
import org.opensaml.core.xml.io.MarshallingException;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml.saml2.core.Issuer;
|
||||
import org.opensaml.saml.saml2.core.NameID;
|
||||
import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
|
||||
import org.opensaml.saml.saml2.core.impl.AuthnRequestMarshaller;
|
||||
import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
|
||||
import org.opensaml.saml.saml2.core.impl.NameIDBuilder;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||
import org.springframework.security.saml2.core.Saml2ParameterNames;
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* For internal use only. Intended for consolidating common behavior related to minting a
|
||||
* SAML 2.0 Authn Request.
|
||||
*/
|
||||
class OpenSamlAuthenticationRequestResolver {
|
||||
|
||||
static {
|
||||
OpenSamlInitializationService.initialize();
|
||||
}
|
||||
|
||||
private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/saml2/authenticate/{registrationId}");
|
||||
|
||||
private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver;
|
||||
|
||||
private final AuthnRequestBuilder authnRequestBuilder;
|
||||
|
||||
private final AuthnRequestMarshaller marshaller;
|
||||
|
||||
private final IssuerBuilder issuerBuilder;
|
||||
|
||||
private final NameIDBuilder nameIdBuilder;
|
||||
|
||||
/**
|
||||
* Construct a {@link OpenSamlAuthenticationRequestResolver} using the provided
|
||||
* parameters
|
||||
* @param relyingPartyRegistrationResolver a strategy for resolving the
|
||||
* {@link RelyingPartyRegistration} from the {@link HttpServletRequest}
|
||||
*/
|
||||
OpenSamlAuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
|
||||
Assert.notNull(relyingPartyRegistrationResolver, "relyingPartyRegistrationResolver cannot be null");
|
||||
this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver;
|
||||
XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
||||
this.marshaller = (AuthnRequestMarshaller) registry.getMarshallerFactory()
|
||||
.getMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME);
|
||||
Assert.notNull(this.marshaller, "logoutRequestMarshaller must be configured in OpenSAML");
|
||||
this.authnRequestBuilder = (AuthnRequestBuilder) XMLObjectProviderRegistrySupport.getBuilderFactory()
|
||||
.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
|
||||
Assert.notNull(this.authnRequestBuilder, "authnRequestBuilder must be configured in OpenSAML");
|
||||
this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
|
||||
Assert.notNull(this.issuerBuilder, "issuerBuilder must be configured in OpenSAML");
|
||||
this.nameIdBuilder = (NameIDBuilder) registry.getBuilderFactory().getBuilder(NameID.DEFAULT_ELEMENT_NAME);
|
||||
Assert.notNull(this.nameIdBuilder, "nameIdBuilder must be configured in OpenSAML");
|
||||
}
|
||||
|
||||
<T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) {
|
||||
return resolve(request, (registration, logoutRequest) -> {
|
||||
});
|
||||
}
|
||||
|
||||
<T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request,
|
||||
BiConsumer<RelyingPartyRegistration, AuthnRequest> authnRequestConsumer) {
|
||||
RequestMatcher.MatchResult result = this.requestMatcher.matcher(request);
|
||||
if (!result.isMatch()) {
|
||||
return null;
|
||||
}
|
||||
String registrationId = result.getVariables().get("registrationId");
|
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationResolver.resolve(request, registrationId);
|
||||
if (registration == null) {
|
||||
return null;
|
||||
}
|
||||
AuthnRequest authnRequest = this.authnRequestBuilder.buildObject();
|
||||
authnRequest.setForceAuthn(Boolean.FALSE);
|
||||
authnRequest.setIsPassive(Boolean.FALSE);
|
||||
authnRequest.setProtocolBinding(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
Issuer iss = this.issuerBuilder.buildObject();
|
||||
iss.setValue(registration.getEntityId());
|
||||
authnRequest.setIssuer(iss);
|
||||
authnRequest.setDestination(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
authnRequest.setAssertionConsumerServiceURL(registration.getAssertionConsumerServiceLocation());
|
||||
authnRequestConsumer.accept(registration, authnRequest);
|
||||
if (authnRequest.getID() == null) {
|
||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||
}
|
||||
String relayState = UUID.randomUUID().toString();
|
||||
Saml2MessageBinding binding = registration.getAssertingPartyDetails().getSingleSignOnServiceBinding();
|
||||
if (binding == Saml2MessageBinding.POST) {
|
||||
if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
|
||||
OpenSamlSigningUtils.sign(authnRequest, registration);
|
||||
}
|
||||
String xml = serialize(authnRequest);
|
||||
String encoded = Saml2Utils.samlEncode(xml.getBytes(StandardCharsets.UTF_8));
|
||||
return (T) Saml2PostAuthenticationRequest.withRelyingPartyRegistration(registration).samlRequest(encoded)
|
||||
.relayState(relayState).build();
|
||||
}
|
||||
else {
|
||||
String xml = serialize(authnRequest);
|
||||
String deflatedAndEncoded = Saml2Utils.samlEncode(Saml2Utils.samlDeflate(xml));
|
||||
Saml2RedirectAuthenticationRequest.Builder builder = Saml2RedirectAuthenticationRequest
|
||||
.withRelyingPartyRegistration(registration).samlRequest(deflatedAndEncoded).relayState(relayState);
|
||||
if (registration.getAssertingPartyDetails().getWantAuthnRequestsSigned()) {
|
||||
Map<String, String> parameters = OpenSamlSigningUtils.sign(registration)
|
||||
.param(Saml2ParameterNames.SAML_REQUEST, deflatedAndEncoded)
|
||||
.param(Saml2ParameterNames.RELAY_STATE, relayState).parameters();
|
||||
builder.sigAlg(parameters.get(Saml2ParameterNames.SIG_ALG))
|
||||
.signature(parameters.get(Saml2ParameterNames.SIGNATURE));
|
||||
}
|
||||
return (T) builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
private String serialize(AuthnRequest authnRequest) {
|
||||
try {
|
||||
Element element = this.marshaller.marshall(authnRequest);
|
||||
return SerializeSupport.nodeToString(element);
|
||||
}
|
||||
catch (MarshallingException ex) {
|
||||
throw new Saml2Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
|
||||
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
|
||||
import org.opensaml.core.xml.XMLObject;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
|
||||
import org.opensaml.core.xml.io.Marshaller;
|
||||
import org.opensaml.core.xml.io.MarshallingException;
|
||||
import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver;
|
||||
import org.opensaml.security.SecurityException;
|
||||
import org.opensaml.security.credential.BasicCredential;
|
||||
import org.opensaml.security.credential.Credential;
|
||||
import org.opensaml.security.credential.CredentialSupport;
|
||||
import org.opensaml.security.credential.UsageType;
|
||||
import org.opensaml.xmlsec.SignatureSigningParameters;
|
||||
import org.opensaml.xmlsec.SignatureSigningParametersResolver;
|
||||
import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion;
|
||||
import org.opensaml.xmlsec.crypto.XMLSigningUtil;
|
||||
import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration;
|
||||
import org.opensaml.xmlsec.signature.SignableXMLObject;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureSupport;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for signing SAML components with OpenSAML
|
||||
*
|
||||
* For internal use only.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
final class OpenSamlSigningUtils {
|
||||
|
||||
static String serialize(XMLObject object) {
|
||||
try {
|
||||
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object);
|
||||
Element element = marshaller.marshall(object);
|
||||
return SerializeSupport.nodeToString(element);
|
||||
}
|
||||
catch (MarshallingException ex) {
|
||||
throw new Saml2Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
static <O extends SignableXMLObject> O sign(O object, RelyingPartyRegistration relyingPartyRegistration) {
|
||||
SignatureSigningParameters parameters = resolveSigningParameters(relyingPartyRegistration);
|
||||
try {
|
||||
SignatureSupport.signObject(object, parameters);
|
||||
return object;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new Saml2Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
static QueryParametersPartial sign(RelyingPartyRegistration registration) {
|
||||
return new QueryParametersPartial(registration);
|
||||
}
|
||||
|
||||
private static SignatureSigningParameters resolveSigningParameters(
|
||||
RelyingPartyRegistration relyingPartyRegistration) {
|
||||
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
|
||||
List<String> algorithms = relyingPartyRegistration.getAssertingPartyDetails().getSigningAlgorithms();
|
||||
List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
|
||||
String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
|
||||
SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();
|
||||
CriteriaSet criteria = new CriteriaSet();
|
||||
BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration();
|
||||
signingConfiguration.setSigningCredentials(credentials);
|
||||
signingConfiguration.setSignatureAlgorithms(algorithms);
|
||||
signingConfiguration.setSignatureReferenceDigestMethods(digests);
|
||||
signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization);
|
||||
criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration));
|
||||
try {
|
||||
SignatureSigningParameters parameters = resolver.resolveSingle(criteria);
|
||||
Assert.notNull(parameters, "Failed to resolve any signing credential");
|
||||
return parameters;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new Saml2Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Credential> resolveSigningCredentials(RelyingPartyRegistration relyingPartyRegistration) {
|
||||
List<Credential> credentials = new ArrayList<>();
|
||||
for (Saml2X509Credential x509Credential : relyingPartyRegistration.getSigningX509Credentials()) {
|
||||
X509Certificate certificate = x509Credential.getCertificate();
|
||||
PrivateKey privateKey = x509Credential.getPrivateKey();
|
||||
BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, privateKey);
|
||||
credential.setEntityId(relyingPartyRegistration.getEntityId());
|
||||
credential.setUsageType(UsageType.SIGNING);
|
||||
credentials.add(credential);
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
|
||||
private OpenSamlSigningUtils() {
|
||||
|
||||
}
|
||||
|
||||
static class QueryParametersPartial {
|
||||
|
||||
final RelyingPartyRegistration registration;
|
||||
|
||||
final Map<String, String> components = new LinkedHashMap<>();
|
||||
|
||||
QueryParametersPartial(RelyingPartyRegistration registration) {
|
||||
this.registration = registration;
|
||||
}
|
||||
|
||||
QueryParametersPartial param(String key, String value) {
|
||||
this.components.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
Map<String, String> parameters() {
|
||||
SignatureSigningParameters parameters = resolveSigningParameters(this.registration);
|
||||
Credential credential = parameters.getSigningCredential();
|
||||
String algorithmUri = parameters.getSignatureAlgorithm();
|
||||
this.components.put("SigAlg", algorithmUri);
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
|
||||
for (Map.Entry<String, String> component : this.components.entrySet()) {
|
||||
builder.queryParam(component.getKey(),
|
||||
UriUtils.encode(component.getValue(), StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
String queryString = builder.build(true).toString().substring(1);
|
||||
try {
|
||||
byte[] rawSignature = XMLSigningUtil.signWithURI(credential, algorithmUri,
|
||||
queryString.getBytes(StandardCharsets.UTF_8));
|
||||
String b64Signature = Saml2Utils.samlEncode(rawSignature);
|
||||
this.components.put("Signature", b64Signature);
|
||||
}
|
||||
catch (SecurityException ex) {
|
||||
throw new Saml2Exception(ex);
|
||||
}
|
||||
return this.components;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
|
||||
import org.opensaml.core.criterion.EntityIdCriterion;
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml.criterion.ProtocolCriterion;
|
||||
import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion;
|
||||
import org.opensaml.saml.saml2.core.Issuer;
|
||||
import org.opensaml.saml.saml2.core.RequestAbstractType;
|
||||
import org.opensaml.saml.saml2.core.StatusResponseType;
|
||||
import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
|
||||
import org.opensaml.security.credential.Credential;
|
||||
import org.opensaml.security.credential.CredentialResolver;
|
||||
import org.opensaml.security.credential.UsageType;
|
||||
import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion;
|
||||
import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion;
|
||||
import org.opensaml.security.credential.impl.CollectionCredentialResolver;
|
||||
import org.opensaml.security.criteria.UsageCriterion;
|
||||
import org.opensaml.security.x509.BasicX509Credential;
|
||||
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
|
||||
import org.opensaml.xmlsec.signature.Signature;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
|
||||
import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
|
||||
|
||||
import org.springframework.security.saml2.core.Saml2Error;
|
||||
import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2ResponseValidatorResult;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for verifying SAML component signatures with OpenSAML
|
||||
*
|
||||
* For internal use only.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
|
||||
final class OpenSamlVerificationUtils {
|
||||
|
||||
static VerifierPartial verifySignature(StatusResponseType object, RelyingPartyRegistration registration) {
|
||||
return new VerifierPartial(object, registration);
|
||||
}
|
||||
|
||||
static VerifierPartial verifySignature(RequestAbstractType object, RelyingPartyRegistration registration) {
|
||||
return new VerifierPartial(object, registration);
|
||||
}
|
||||
|
||||
private OpenSamlVerificationUtils() {
|
||||
|
||||
}
|
||||
|
||||
static class VerifierPartial {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final CriteriaSet criteria;
|
||||
|
||||
private final SignatureTrustEngine trustEngine;
|
||||
|
||||
VerifierPartial(StatusResponseType object, RelyingPartyRegistration registration) {
|
||||
this.id = object.getID();
|
||||
this.criteria = verificationCriteria(object.getIssuer());
|
||||
this.trustEngine = trustEngine(registration);
|
||||
}
|
||||
|
||||
VerifierPartial(RequestAbstractType object, RelyingPartyRegistration registration) {
|
||||
this.id = object.getID();
|
||||
this.criteria = verificationCriteria(object.getIssuer());
|
||||
this.trustEngine = trustEngine(registration);
|
||||
}
|
||||
|
||||
Saml2ResponseValidatorResult redirect(HttpServletRequest request, String objectParameterName) {
|
||||
RedirectSignature signature = new RedirectSignature(request, objectParameterName);
|
||||
if (signature.getAlgorithm() == null) {
|
||||
return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
|
||||
"Missing signature algorithm for object [" + this.id + "]"));
|
||||
}
|
||||
if (!signature.hasSignature()) {
|
||||
return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
|
||||
"Missing signature for object [" + this.id + "]"));
|
||||
}
|
||||
Collection<Saml2Error> errors = new ArrayList<>();
|
||||
String algorithmUri = signature.getAlgorithm();
|
||||
try {
|
||||
if (!this.trustEngine.validate(signature.getSignature(), signature.getContent(), algorithmUri,
|
||||
this.criteria, null)) {
|
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
|
||||
"Invalid signature for object [" + this.id + "]"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
|
||||
"Invalid signature for object [" + this.id + "]: "));
|
||||
}
|
||||
return Saml2ResponseValidatorResult.failure(errors);
|
||||
}
|
||||
|
||||
Saml2ResponseValidatorResult post(Signature signature) {
|
||||
Collection<Saml2Error> errors = new ArrayList<>();
|
||||
SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
|
||||
try {
|
||||
profileValidator.validate(signature);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
|
||||
"Invalid signature for object [" + this.id + "]: "));
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.trustEngine.validate(signature, this.criteria)) {
|
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
|
||||
"Invalid signature for object [" + this.id + "]"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
|
||||
"Invalid signature for object [" + this.id + "]: "));
|
||||
}
|
||||
|
||||
return Saml2ResponseValidatorResult.failure(errors);
|
||||
}
|
||||
|
||||
private CriteriaSet verificationCriteria(Issuer issuer) {
|
||||
CriteriaSet criteria = new CriteriaSet();
|
||||
criteria.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())));
|
||||
criteria.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)));
|
||||
criteria.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)));
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private SignatureTrustEngine trustEngine(RelyingPartyRegistration registration) {
|
||||
Set<Credential> credentials = new HashSet<>();
|
||||
Collection<Saml2X509Credential> keys = registration.getAssertingPartyDetails()
|
||||
.getVerificationX509Credentials();
|
||||
for (Saml2X509Credential key : keys) {
|
||||
BasicX509Credential cred = new BasicX509Credential(key.getCertificate());
|
||||
cred.setUsageType(UsageType.SIGNING);
|
||||
cred.setEntityId(registration.getAssertingPartyDetails().getEntityId());
|
||||
credentials.add(cred);
|
||||
}
|
||||
CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
|
||||
return new ExplicitKeySignatureTrustEngine(credentialsResolver,
|
||||
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
|
||||
}
|
||||
|
||||
private static class RedirectSignature {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
private final String objectParameterName;
|
||||
|
||||
RedirectSignature(HttpServletRequest request, String objectParameterName) {
|
||||
this.request = request;
|
||||
this.objectParameterName = objectParameterName;
|
||||
}
|
||||
|
||||
String getAlgorithm() {
|
||||
return this.request.getParameter("SigAlg");
|
||||
}
|
||||
|
||||
byte[] getContent() {
|
||||
String query = String.format("%s=%s&SigAlg=%s", this.objectParameterName,
|
||||
UriUtils.encode(this.request.getParameter(this.objectParameterName),
|
||||
StandardCharsets.ISO_8859_1),
|
||||
UriUtils.encode(getAlgorithm(), StandardCharsets.ISO_8859_1));
|
||||
return query.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
byte[] getSignature() {
|
||||
return Saml2Utils.samlDecode(this.request.getParameter("Signature"));
|
||||
}
|
||||
|
||||
boolean hasSignature() {
|
||||
return this.request.getParameter("Signature") != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
|
||||
|
||||
/**
|
||||
* A strategy for resolving a SAML 2.0 Authentication Request from the
|
||||
* {@link HttpServletRequest}.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.7
|
||||
*/
|
||||
public interface Saml2AuthenticationRequestResolver {
|
||||
|
||||
<T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request);
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterOutputStream;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
|
||||
/**
|
||||
* Utility methods for working with serialized SAML messages.
|
||||
*
|
||||
* For internal use only.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
final class Saml2Utils {
|
||||
|
||||
private static Base64 BASE64 = new Base64(0, new byte[] { '\n' });
|
||||
|
||||
private Saml2Utils() {
|
||||
}
|
||||
|
||||
static String samlEncode(byte[] b) {
|
||||
return BASE64.encodeAsString(b);
|
||||
}
|
||||
|
||||
static byte[] samlDecode(String s) {
|
||||
return BASE64.decode(s);
|
||||
}
|
||||
|
||||
static byte[] samlDeflate(String s) {
|
||||
try {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(Deflater.DEFLATED, true));
|
||||
deflater.write(s.getBytes(StandardCharsets.UTF_8));
|
||||
deflater.finish();
|
||||
return b.toByteArray();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new Saml2Exception("Unable to deflate string", ex);
|
||||
}
|
||||
}
|
||||
|
||||
static String samlInflate(byte[] b) {
|
||||
try {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
|
||||
iout.write(b);
|
||||
iout.finish();
|
||||
return new String(out.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new Saml2Exception("Unable to inflate string", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml.saml2.core.LogoutRequest;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A strategy for resolving a SAML 2.0 Authentication Request from the
|
||||
* {@link HttpServletRequest} using OpenSAML.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.7
|
||||
* @deprecated OpenSAML 3 has reached end-of-life so this version is no longer recommended
|
||||
*/
|
||||
@Deprecated
|
||||
public final class OpenSaml3AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver {
|
||||
|
||||
private final OpenSamlAuthenticationRequestResolver authnRequestResolver;
|
||||
|
||||
private Consumer<AuthnRequestContext> contextConsumer = (parameters) -> {
|
||||
};
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Construct a {@link OpenSaml3AuthenticationRequestResolver}
|
||||
*/
|
||||
public OpenSaml3AuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
|
||||
this.authnRequestResolver = new OpenSamlAuthenticationRequestResolver(relyingPartyRegistrationResolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) {
|
||||
return this.authnRequestResolver.resolve(request, (registration, authnRequest) -> {
|
||||
authnRequest.setIssueInstant(new DateTime(this.clock.millis()));
|
||||
this.contextConsumer.accept(new AuthnRequestContext(request, registration, authnRequest));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link LogoutRequest}
|
||||
* @param contextConsumer a consumer that accepts an {@link AuthnRequestContext}
|
||||
*/
|
||||
public void setAuthnRequestCustomizer(Consumer<AuthnRequestContext> contextConsumer) {
|
||||
Assert.notNull(contextConsumer, "contextConsumer cannot be null");
|
||||
this.contextConsumer = contextConsumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link Clock} for generating the issued {@link DateTime}
|
||||
* @param clock the {@link Clock} to use
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock must not be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
public static final class AuthnRequestContext {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
private final RelyingPartyRegistration registration;
|
||||
|
||||
private final AuthnRequest authnRequest;
|
||||
|
||||
public AuthnRequestContext(HttpServletRequest request, RelyingPartyRegistration registration,
|
||||
AuthnRequest authnRequest) {
|
||||
this.request = request;
|
||||
this.registration = registration;
|
||||
this.authnRequest = authnRequest;
|
||||
}
|
||||
|
||||
public HttpServletRequest getRequest() {
|
||||
return this.request;
|
||||
}
|
||||
|
||||
public RelyingPartyRegistration getRelyingPartyRegistration() {
|
||||
return this.registration;
|
||||
}
|
||||
|
||||
public AuthnRequest getAuthnRequest() {
|
||||
return this.authnRequest;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A strategy for resolving a SAML 2.0 Authentication Request from the
|
||||
* {@link HttpServletRequest} using OpenSAML.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.7
|
||||
*/
|
||||
public final class OpenSaml4AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver {
|
||||
|
||||
private final OpenSamlAuthenticationRequestResolver authnRequestResolver;
|
||||
|
||||
private Consumer<AuthnRequestContext> contextConsumer = (parameters) -> {
|
||||
};
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Construct a {@link OpenSaml4AuthenticationRequestResolver}
|
||||
*/
|
||||
public OpenSaml4AuthenticationRequestResolver(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
|
||||
this.authnRequestResolver = new OpenSamlAuthenticationRequestResolver(relyingPartyRegistrationResolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends AbstractSaml2AuthenticationRequest> T resolve(HttpServletRequest request) {
|
||||
return this.authnRequestResolver.resolve(request, (registration, authnRequest) -> {
|
||||
authnRequest.setIssueInstant(Instant.now(this.clock));
|
||||
this.contextConsumer.accept(new AuthnRequestContext(request, registration, authnRequest));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link Consumer} for modifying the OpenSAML {@link AuthnRequest}
|
||||
* @param contextConsumer a consumer that accepts an {@link AuthnRequestContext}
|
||||
*/
|
||||
public void setAuthnRequestCustomizer(Consumer<AuthnRequestContext> contextConsumer) {
|
||||
Assert.notNull(contextConsumer, "contextConsumer cannot be null");
|
||||
this.contextConsumer = contextConsumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link Clock} for generating the issued {@link Instant}
|
||||
* @param clock the {@link Clock} to use
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock must not be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
public static final class AuthnRequestContext {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
private final RelyingPartyRegistration registration;
|
||||
|
||||
private final AuthnRequest authnRequest;
|
||||
|
||||
public AuthnRequestContext(HttpServletRequest request, RelyingPartyRegistration registration,
|
||||
AuthnRequest authnRequest) {
|
||||
this.request = request;
|
||||
this.registration = registration;
|
||||
this.authnRequest = authnRequest;
|
||||
}
|
||||
|
||||
public HttpServletRequest getRequest() {
|
||||
return this.request;
|
||||
}
|
||||
|
||||
public RelyingPartyRegistration getRelyingPartyRegistration() {
|
||||
return this.registration;
|
||||
}
|
||||
|
||||
public AuthnRequest getAuthnRequest() {
|
||||
return this.authnRequest;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,6 +20,9 @@ import java.io.IOException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -43,6 +46,7 @@ import org.springframework.security.saml2.provider.service.web.DefaultSaml2Authe
|
|||
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
@ -69,6 +73,9 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
|
|||
|
||||
private Saml2AuthenticationRequestContextResolver resolver = mock(Saml2AuthenticationRequestContextResolver.class);
|
||||
|
||||
private Saml2AuthenticationRequestResolver authenticationRequestResolver = mock(
|
||||
Saml2AuthenticationRequestResolver.class);
|
||||
|
||||
private Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository = mock(
|
||||
Saml2AuthenticationRequestRepository.class);
|
||||
|
||||
|
@ -86,7 +93,12 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
|
|||
this.request = new MockHttpServletRequest();
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.request.setPathInfo("/saml2/authenticate/registration-id");
|
||||
this.filterChain = new MockFilterChain();
|
||||
this.filterChain = new MockFilterChain() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response) {
|
||||
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
};
|
||||
this.rpBuilder = RelyingPartyRegistration.withRegistrationId("registration-id")
|
||||
.providerDetails((c) -> c.entityId("idp-entity-id")).providerDetails((c) -> c.webSsoUrl(IDP_SSO_URL))
|
||||
.assertionConsumerServiceUrlTemplate("template")
|
||||
|
@ -114,6 +126,12 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
|
|||
.authenticationRequestUri(IDP_SSO_URL);
|
||||
}
|
||||
|
||||
private static Saml2RedirectAuthenticationRequest.Builder redirectAuthenticationRequest(
|
||||
RelyingPartyRegistration registration) {
|
||||
return Saml2RedirectAuthenticationRequest.withRelyingPartyRegistration(registration).samlRequest("request")
|
||||
.authenticationRequestUri(IDP_SSO_URL);
|
||||
}
|
||||
|
||||
private static Saml2PostAuthenticationRequest.Builder postAuthenticationRequest(
|
||||
Saml2AuthenticationRequestContext context) {
|
||||
return Saml2PostAuthenticationRequest.withAuthenticationRequestContext(context).samlRequest("request")
|
||||
|
@ -287,4 +305,15 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
|
|||
verify(this.repository).findByRegistrationId("registration-id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenCustomAuthenticationRequestResolverThenUses() throws Exception {
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
|
||||
Saml2RedirectAuthenticationRequest authenticationRequest = redirectAuthenticationRequest(registration).build();
|
||||
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(
|
||||
this.authenticationRequestResolver);
|
||||
given(this.authenticationRequestResolver.resolve(any())).willReturn(authenticationRequest);
|
||||
filter.doFilterInternal(this.request, this.response, this.filterChain);
|
||||
verify(this.authenticationRequestResolver).resolve(any());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.saml2.provider.service.web.authentication;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.saml2.Saml2Exception;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link OpenSamlAuthenticationRequestResolver}
|
||||
*/
|
||||
public class OpenSamlAuthenticationRequestResolverTests {
|
||||
|
||||
private RelyingPartyRegistration.Builder relyingPartyRegistrationBuilder;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.relyingPartyRegistrationBuilder = TestRelyingPartyRegistrations.relyingPartyRegistration();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveAuthenticationRequestWhenSignedRedirectThenSignsAndRedirects() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setPathInfo("/saml2/authenticate/registration-id");
|
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder.build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
Saml2RedirectAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> {
|
||||
assertThat(authnRequest.getAssertionConsumerServiceURL())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation());
|
||||
assertThat(authnRequest.getProtocolBinding())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
assertThat(authnRequest.getDestination())
|
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId());
|
||||
});
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isNotNull();
|
||||
assertThat(result.getSigAlg()).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
|
||||
assertThat(result.getSignature()).isNotEmpty();
|
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveAuthenticationRequestWhenUnsignedRedirectThenRedirectsAndNoSignature() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setPathInfo("/saml2/authenticate/registration-id");
|
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder
|
||||
.assertingPartyDetails((party) -> party.wantAuthnRequestsSigned(false)).build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
Saml2RedirectAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> {
|
||||
assertThat(authnRequest.getAssertionConsumerServiceURL())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation());
|
||||
assertThat(authnRequest.getProtocolBinding())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
assertThat(authnRequest.getDestination())
|
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId());
|
||||
});
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isNotNull();
|
||||
assertThat(result.getSigAlg()).isNull();
|
||||
assertThat(result.getSignature()).isNull();
|
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveAuthenticationRequestWhenSignedThenCredentialIsRequired() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setPathInfo("/saml2/authenticate/registration-id");
|
||||
Saml2X509Credential credential = TestSaml2X509Credentials.relyingPartyVerifyingCredential();
|
||||
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials()
|
||||
.assertingPartyDetails((party) -> party.verificationX509Credentials((c) -> c.add(credential))).build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> resolver.resolve(request, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveAuthenticationRequestWhenUnsignedPostThenOnlyPosts() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setPathInfo("/saml2/authenticate/registration-id");
|
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder.assertingPartyDetails(
|
||||
(party) -> party.singleSignOnServiceBinding(Saml2MessageBinding.POST).wantAuthnRequestsSigned(false))
|
||||
.build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
Saml2PostAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> {
|
||||
assertThat(authnRequest.getAssertionConsumerServiceURL())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation());
|
||||
assertThat(authnRequest.getProtocolBinding())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
assertThat(authnRequest.getDestination())
|
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId());
|
||||
});
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isNotNull();
|
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST);
|
||||
assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()))).doesNotContain("Signature");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveAuthenticationRequestWhenSignedPostThenSignsAndPosts() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setPathInfo("/saml2/authenticate/registration-id");
|
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder
|
||||
.assertingPartyDetails((party) -> party.singleSignOnServiceBinding(Saml2MessageBinding.POST)).build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
Saml2PostAuthenticationRequest result = resolver.resolve(request, (r, authnRequest) -> {
|
||||
assertThat(authnRequest.getAssertionConsumerServiceURL())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceLocation());
|
||||
assertThat(authnRequest.getProtocolBinding())
|
||||
.isEqualTo(registration.getAssertionConsumerServiceBinding().getUrn());
|
||||
assertThat(authnRequest.getDestination())
|
||||
.isEqualTo(registration.getAssertingPartyDetails().getSingleSignOnServiceLocation());
|
||||
assertThat(authnRequest.getIssuer().getValue()).isEqualTo(registration.getEntityId());
|
||||
});
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isNotNull();
|
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.POST);
|
||||
assertThat(new String(Saml2Utils.samlDecode(result.getSamlRequest()))).contains("Signature");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveAuthenticationRequestWhenSHA1SignRequestThenSigns() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setPathInfo("/saml2/authenticate/registration-id");
|
||||
RelyingPartyRegistration registration = this.relyingPartyRegistrationBuilder.assertingPartyDetails(
|
||||
(party) -> party.signingAlgorithms((algs) -> algs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1)))
|
||||
.build();
|
||||
OpenSamlAuthenticationRequestResolver resolver = authenticationRequestResolver(registration);
|
||||
Saml2RedirectAuthenticationRequest result = resolver.resolve(request, null);
|
||||
assertThat(result.getSamlRequest()).isNotEmpty();
|
||||
assertThat(result.getRelayState()).isNotNull();
|
||||
assertThat(result.getSigAlg()).isEqualTo(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
|
||||
assertThat(result.getSignature()).isNotNull();
|
||||
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
||||
}
|
||||
|
||||
private OpenSamlAuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistration registration) {
|
||||
return new OpenSamlAuthenticationRequestResolver((request, id) -> registration);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue