Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Deprecated OpenSAML 3 Support #11789

Merged
merged 1 commit into from Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 0 additions & 8 deletions build.gradle
Expand Up @@ -118,14 +118,6 @@ updateDependenciesSettings {
selection.reject("nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency");
}
}
components.all { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
// Do not compare version due to multiple versions existing
// will cause opensaml 3.x to be updated to 4.x
if (candidate.getGroup().equals("org.opensaml")) {
selection.reject("org.opensaml maintains two different versions, so it must be updated manually");
}
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion config/spring-security-config.gradle
Expand Up @@ -46,8 +46,8 @@ dependencies {
testImplementation project(path : ':spring-security-ldap', configuration : 'tests')
testImplementation project(path : ':spring-security-oauth2-client', configuration : 'tests')
testImplementation project(path : ':spring-security-oauth2-resource-server', configuration : 'tests')
testImplementation project(':spring-security-saml2-service-provider')
testImplementation project(path : ':spring-security-saml2-service-provider', configuration : 'tests')
testImplementation project(path : ':spring-security-saml2-service-provider', configuration : 'opensaml4MainImplementation')
testImplementation project(path : ':spring-security-web', configuration : 'tests')
testImplementation "jakarta.inject:jakarta.inject-api"
testImplementation "org.assertj:assertj-core"
Expand Down
Expand Up @@ -19,8 +19,6 @@
import java.util.LinkedHashMap;
import java.util.Map;

import org.opensaml.core.Version;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
Expand All @@ -33,7 +31,6 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
Expand All @@ -43,7 +40,6 @@
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
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.OpenSaml3AuthenticationRequestResolver;
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.AuthenticationEntryPoint;
Expand Down Expand Up @@ -200,10 +196,6 @@ public Saml2LoginConfigurer<B> authenticationRequestResolver(
* @since 6.0
*/
public Saml2LoginConfigurer<B> authenticationRequestUri(String authenticationRequestUri) {
// OpenSAML 3 is no longer supported by spring security
if (version().startsWith("3")) {
return this;
}
Assert.state(authenticationRequestUri.contains("{registrationId}"),
"authenticationRequestUri must contain {registrationId} path variable");
this.authenticationRequestUri = authenticationRequestUri;
Expand Down Expand Up @@ -345,14 +337,11 @@ private Saml2AuthenticationRequestResolver getAuthenticationRequestResolver(B ht
if (bean != null) {
return bean;
}
if (version().startsWith("4")) {
OpenSaml4AuthenticationRequestResolver openSaml4AuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
relyingPartyRegistrationResolver(http));
openSaml4AuthenticationRequestResolver
.setRequestMatcher(new AntPathRequestMatcher(this.authenticationRequestUri));
return openSaml4AuthenticationRequestResolver;
}
return new OpenSaml3AuthenticationRequestResolver(relyingPartyRegistrationResolver(http));
OpenSaml4AuthenticationRequestResolver openSaml4AuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
relyingPartyRegistrationResolver(http));
openSaml4AuthenticationRequestResolver
.setRequestMatcher(new AntPathRequestMatcher(this.authenticationRequestUri));
return openSaml4AuthenticationRequestResolver;
}

private AuthenticationConverter getAuthenticationConverter(B http) {
Expand All @@ -370,22 +359,8 @@ private AuthenticationConverter getAuthenticationConverter(B http) {
return authenticationConverterBean;
}

private String version() {
String version = Version.getVersion();
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
}

private void registerDefaultAuthenticationProvider(B http) {
if (version().startsWith("4")) {
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
}
else {
http.authenticationProvider(postProcess(new OpenSamlAuthenticationProvider()));
}
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
}

private void registerDefaultCsrfOverride(B http) {
Expand Down
Expand Up @@ -22,8 +22,6 @@
import java.util.function.Predicate;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.opensaml.core.Version;

import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
Expand All @@ -44,8 +42,6 @@
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.authentication.logout.HttpSessionLogoutRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml3LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml3LogoutResponseResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
Expand Down Expand Up @@ -313,15 +309,6 @@ private <C> C getBeanOrNull(Class<C> clazz) {
return this.context.getBean(clazz);
}

private String version() {
String version = Version.getVersion();
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
}

/**
* A configurer for SAML 2.0 LogoutRequest components
*/
Expand Down Expand Up @@ -401,10 +388,7 @@ private Saml2LogoutRequestResolver logoutRequestResolver(
if (this.logoutRequestResolver != null) {
return this.logoutRequestResolver;
}
if (version().startsWith("4")) {
return new OpenSaml4LogoutRequestResolver(relyingPartyRegistrationResolver);
}
return new OpenSaml3LogoutRequestResolver(relyingPartyRegistrationResolver);
return new OpenSaml4LogoutRequestResolver(relyingPartyRegistrationResolver);
}

}
Expand Down Expand Up @@ -471,10 +455,7 @@ private Saml2LogoutResponseValidator logoutResponseValidator() {
private Saml2LogoutResponseResolver logoutResponseResolver(
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
if (this.logoutResponseResolver == null) {
if (version().startsWith("4")) {
return new OpenSaml4LogoutResponseResolver(relyingPartyRegistrationResolver);
}
return new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
return new OpenSaml4LogoutResponseResolver(relyingPartyRegistrationResolver);
}
return this.logoutResponseResolver;
}
Expand Down Expand Up @@ -511,12 +492,4 @@ public boolean matches(HttpServletRequest request) {

}

private static class NoopLogoutHandler implements LogoutHandler {

@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
}

}

}
Expand Up @@ -18,9 +18,7 @@

import java.io.IOException;
import java.net.URLDecoder;
import java.time.Duration;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;

import jakarta.servlet.ServletException;
Expand All @@ -32,43 +30,34 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.opensaml.saml.saml2.core.Assertion;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2Utils;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
Expand All @@ -77,7 +66,6 @@
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.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.Saml2AuthenticationRequestRepository;
Expand All @@ -91,7 +79,6 @@
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
Expand Down Expand Up @@ -121,14 +108,6 @@
@ExtendWith(SpringTestContextExtension.class)
public class Saml2LoginConfigurerTests {

private static final Converter<Assertion, Collection<? extends GrantedAuthority>> AUTHORITIES_EXTRACTOR = (
a) -> Collections.singletonList(new SimpleGrantedAuthority("TEST"));

private static final GrantedAuthoritiesMapper AUTHORITIES_MAPPER = (authorities) -> Collections
.singletonList(new SimpleGrantedAuthority("TEST CONVERTED"));

private static final Duration RESPONSE_TIME_VALIDATION_SKEW = Duration.ZERO;

private static final String SIGNED_RESPONSE = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9ycC5leGFtcGxlLm9yZy9hY3MiIElEPSJfYzE3MzM2YTAtNTM1My00MTQ5LWI3MmMtMDNkOWY5YWYzMDdlIiBJc3N1ZUluc3RhbnQ9IjIwMjAtMDgtMDRUMjI6MDQ6NDUuMDE2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8+CjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CjxkczpSZWZlcmVuY2UgVVJJPSIjX2MxNzMzNmEwLTUzNTMtNDE0OS1iNzJjLTAzZDlmOWFmMzA3ZSI+CjxkczpUcmFuc2Zvcm1zPgo8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgo8L2RzOlRyYW5zZm9ybXM+CjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz4KPGRzOkRpZ2VzdFZhbHVlPjYzTmlyenFzaDVVa0h1a3NuRWUrM0hWWU5aYWFsQW1OQXFMc1lGMlRuRDA9PC9kczpEaWdlc3RWYWx1ZT4KPC9kczpSZWZlcmVuY2U+CjwvZHM6U2lnbmVkSW5mbz4KPGRzOlNpZ25hdHVyZVZhbHVlPgpLMVlvWWJVUjBTclY4RTdVMkhxTTIvZUNTOTNoV25mOExnNnozeGZWMUlyalgzSXhWYkNvMVlYcnRBSGRwRVdvYTJKKzVOMmFNbFBHJiMxMzsKN2VpbDBZRC9xdUVRamRYbTNwQTBjZmEvY25pa2RuKzVhbnM0ZWQwanU1amo2dkpvZ2w2Smt4Q25LWUpwTU9HNzhtampmb0phengrWCYjMTM7CkM2NktQVStBYUdxeGVwUEQ1ZlhRdTFKSy9Jb3lBaitaa3k4Z2Jwc3VyZHFCSEJLRWxjdnVOWS92UGY0OGtBeFZBKzdtRGhNNUMvL1AmIzEzOwp0L084Y3NZYXB2UjZjdjZrdk45QXZ1N3FRdm9qVk1McHVxZWNJZDJwTUVYb0NSSnE2Nkd4MStNTUVPeHVpMWZZQlRoMEhhYjRmK3JyJiMxMzsKOEY2V1NFRC8xZllVeHliRkJqZ1Q4d2lEWHFBRU8wSVY4ZWRQeEE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iQWUzZjQ5OGI4LTliMTctNDA3OC05ZDM1LTg2YTA4NDA4NDk5NSIgSXNzdWVJbnN0YW50PSIyMDIwLTA4LTA0VDIyOjA0OjQ1LjA3N1oiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3Vlcj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEPnRlc3RAc2FtbC51c2VyPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90QmVmb3JlPSIyMDIwLTA4LTA0VDIxOjU5OjQ1LjA5MFoiIE5vdE9uT3JBZnRlcj0iMjA0MC0wNy0zMFQyMjowNTowNi4wODhaIiBSZWNpcGllbnQ9Imh0dHBzOi8vcnAuZXhhbXBsZS5vcmcvYWNzIi8+PC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDI6U3ViamVjdD48c2FtbDI6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjAtMDgtMDRUMjE6NTk6NDUuMDgwWiIgTm90T25PckFmdGVyPSIyMDQwLTA3LTMwVDIyOjA1OjA2LjA4N1oiLz48L3NhbWwyOkFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=";

private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class);
Expand Down Expand Up @@ -197,14 +176,6 @@ public void saml2LoginWhenDefaultAndSamlAuthenticationManagerThenSamlManagerIsUs
performSaml2Login("ROLE_AUTH_MANAGER");
}

@Test
public void saml2LoginWhenConfiguringAuthenticationDefaultsUsingCustomizerThenTheProviderIsConfigured()
throws Exception {
// setup application context
this.spring.register(Saml2LoginConfigWithAuthenticationDefaultsWithPostProcessor.class).autowire();
validateSaml2WebSsoAuthenticationFilterConfiguration();
}

@Test
public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire();
Expand Down Expand Up @@ -362,22 +333,6 @@ public void getFaviconWhenDefaultConfigurationThenDoesNotSaveAuthnRequest() thro
.andExpect(redirectedUrl("http://localhost/saml2/authenticate/registration-id"));
}

private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
// get the OpenSamlAuthenticationProvider
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
AuthenticationManager manager = (AuthenticationManager) ReflectionTestUtils.getField(filter,
"authenticationManager");
ProviderManager pm = (ProviderManager) manager;
AuthenticationProvider provider = pm.getProviders().stream()
.filter((p) -> p instanceof OpenSaml4AuthenticationProvider).findFirst().get();
assertThat(provider).isNotNull();
}

private Saml2WebSsoAuthenticationFilter getSaml2SsoFilter(FilterChainProxy chain) {
return (Saml2WebSsoAuthenticationFilter) chain.getFilters("/login/saml2/sso/test").stream()
.filter((f) -> f instanceof Saml2WebSsoAuthenticationFilter).findFirst().get();
}

private void performSaml2Login(String expected) throws IOException, ServletException {
// setup authentication parameters
this.request.setRequestURI("/login/saml2/sso/registration-id");
Expand Down Expand Up @@ -460,28 +415,6 @@ protected void configure(HttpSecurity http) throws Exception {

}

@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class Saml2LoginConfigWithAuthenticationDefaultsWithPostProcessor extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
ObjectPostProcessor<OpenSamlAuthenticationProvider> processor = new ObjectPostProcessor<OpenSamlAuthenticationProvider>() {
@Override
public <O extends OpenSamlAuthenticationProvider> O postProcess(O provider) {
provider.setResponseTimeValidationSkew(RESPONSE_TIME_VALIDATION_SKEW);
provider.setAuthoritiesMapper(AUTHORITIES_MAPPER);
provider.setAuthoritiesExtractor(AUTHORITIES_EXTRACTOR);
return provider;
}
};
http.saml2Login().addObjectPostProcessor(processor);
super.configure(http);
}

}

@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
Expand Down
8 changes: 0 additions & 8 deletions docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc
Expand Up @@ -154,14 +154,6 @@ Instead, such classes as `OpenSamlAuthenticationRequestFactory` and `OpenSamlAut

For example, once your application receives a `SAMLResponse` and delegates to `Saml2WebSsoAuthenticationFilter`, the filter delegates to `OpenSamlAuthenticationProvider`:

[NOTE]
====
For backward compatibility, Spring Security will use the latest OpenSAML 3 by default.
Note, though that OpenSAML 3 has reached it's end-of-life and updating to OpenSAML 4.x is recommended.
For that reason, Spring Security supports both OpenSAML 3.x and 4.x.
If you manage your OpenSAML dependency to 4.x, then Spring Security will select its OpenSAML 4.x implementations.
====

.Authenticating an OpenSAML `Response`
image:{figures}/opensamlauthenticationprovider.png[]

Expand Down