Skip to content

Commit af41594

Browse files
committed
Allow configuration of AuthenticationManagerResolver in saml2Login()
Fixes gh-7654 #7654
1 parent b7eebab commit af41594

File tree

7 files changed

+578
-21
lines changed

7 files changed

+578
-21
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2020
import org.springframework.context.ApplicationContext;
21-
import org.springframework.security.authentication.AuthenticationProvider;
21+
import org.springframework.security.authentication.AuthenticationManager;
2222
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2323
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2424
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
@@ -103,6 +103,25 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>> extend
103103

104104
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
105105

106+
private AuthenticationManager authenticationManager;
107+
108+
private Saml2WebSsoAuthenticationFilter saml2WebSsoAuthenticationFilter;
109+
110+
/**
111+
* Allows a configuration of a {@link AuthenticationManager} to be used during SAML 2 authentication.
112+
* If none is specified, the system will create one inject it into the {@link Saml2WebSsoAuthenticationFilter}
113+
* @param authenticationManager the authentication manager to be used
114+
* @return the {@link Saml2LoginConfigurer} for further configuration
115+
* @throws IllegalArgumentException if authenticationManager is null
116+
* configure the default manager
117+
* @since 5.3
118+
*/
119+
public Saml2LoginConfigurer<B> authenticationManager(AuthenticationManager authenticationManager) {
120+
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
121+
this.authenticationManager = authenticationManager;
122+
return this;
123+
}
124+
106125
/**
107126
* Sets the {@code RelyingPartyRegistrationRepository} of relying parties, each party representing a
108127
* service provider, SP and this host, and identity provider, IDP pair that communicate with each other.
@@ -164,11 +183,11 @@ public void init(B http) throws Exception {
164183
this.relyingPartyRegistrationRepository = getSharedOrBean(http, RelyingPartyRegistrationRepository.class);
165184
}
166185

167-
Saml2WebSsoAuthenticationFilter webSsoFilter = new Saml2WebSsoAuthenticationFilter(
186+
saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(
168187
this.relyingPartyRegistrationRepository,
169188
this.loginProcessingUrl
170189
);
171-
setAuthenticationFilter(webSsoFilter);
190+
setAuthenticationFilter(saml2WebSsoAuthenticationFilter);
172191
super.loginProcessingUrl(this.loginProcessingUrl);
173192

174193
if (hasText(this.loginPage)) {
@@ -197,7 +216,7 @@ public void init(B http) throws Exception {
197216
super.init(http);
198217
}
199218
}
200-
http.authenticationProvider(getAuthenticationProvider());
219+
201220
this.initDefaultLoginFilter(http);
202221
}
203222

@@ -211,11 +230,17 @@ public void init(B http) throws Exception {
211230
public void configure(B http) throws Exception {
212231
http.addFilter(this.authenticationRequestEndpoint.build(http));
213232
super.configure(http);
233+
if (this.authenticationManager == null) {
234+
registerDefaultAuthenticationProvider(http);
235+
}
236+
else {
237+
saml2WebSsoAuthenticationFilter.setAuthenticationManager(this.authenticationManager);
238+
}
214239
}
215240

216-
private AuthenticationProvider getAuthenticationProvider() {
217-
AuthenticationProvider provider = new OpenSamlAuthenticationProvider();
218-
return postProcess(provider);
241+
private void registerDefaultAuthenticationProvider(B http) {
242+
OpenSamlAuthenticationProvider provider = postProcess(new OpenSamlAuthenticationProvider());
243+
http.authenticationProvider(provider);
219244
}
220245

221246
private void registerDefaultCsrfOverride(B http) {
@@ -313,5 +338,4 @@ private Saml2AuthenticationRequestFactory getResolver(B http) {
313338
}
314339
}
315340

316-
317341
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.web.configurers.saml2;
18+
19+
import org.junit.After;
20+
import org.junit.Assert;
21+
import org.junit.Before;
22+
import org.junit.Rule;
23+
import org.junit.Test;
24+
import org.opensaml.saml.saml2.core.Assertion;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.context.ConfigurableApplicationContext;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Import;
29+
import org.springframework.core.convert.converter.Converter;
30+
import org.springframework.mock.web.MockFilterChain;
31+
import org.springframework.mock.web.MockHttpServletRequest;
32+
import org.springframework.mock.web.MockHttpServletResponse;
33+
import org.springframework.security.authentication.AuthenticationManager;
34+
import org.springframework.security.authentication.AuthenticationProvider;
35+
import org.springframework.security.authentication.AuthenticationServiceException;
36+
import org.springframework.security.authentication.ProviderManager;
37+
import org.springframework.security.config.annotation.ObjectPostProcessor;
38+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
39+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
40+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
41+
import org.springframework.security.config.test.SpringTestRule;
42+
import org.springframework.security.core.Authentication;
43+
import org.springframework.security.core.AuthenticationException;
44+
import org.springframework.security.core.GrantedAuthority;
45+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
46+
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
47+
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
48+
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
49+
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
50+
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
51+
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
52+
import org.springframework.security.web.FilterChainProxy;
53+
import org.springframework.security.web.context.HttpRequestResponseHolder;
54+
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
55+
import org.springframework.security.web.context.SecurityContextRepository;
56+
import org.springframework.test.util.ReflectionTestUtils;
57+
import org.springframework.test.web.servlet.MockMvc;
58+
59+
import java.io.IOException;
60+
import java.time.Duration;
61+
import java.util.Base64;
62+
import java.util.Collection;
63+
import java.util.Collections;
64+
import javax.servlet.ServletException;
65+
66+
import static org.assertj.core.api.Assertions.assertThat;
67+
import static org.codehaus.groovy.runtime.InvokerHelper.asList;
68+
import static org.mockito.ArgumentMatchers.anyString;
69+
import static org.mockito.Mockito.mock;
70+
import static org.mockito.Mockito.when;
71+
import static org.springframework.security.config.annotation.web.configurers.saml2.TestRelyingPartyRegistrations.saml2AuthenticationConfiguration;
72+
73+
/**
74+
* Tests for different Java configuration for {@link Saml2LoginConfigurer}
75+
*/
76+
public class Saml2LoginConfigurerTests {
77+
78+
private static final Converter<Assertion, Collection<? extends GrantedAuthority>>
79+
AUTHORITIES_EXTRACTOR = a -> asList(new SimpleGrantedAuthority("TEST"));
80+
private static final GrantedAuthoritiesMapper AUTHORITIES_MAPPER =
81+
authorities -> asList(new SimpleGrantedAuthority("TEST CONVERTED"));
82+
private static final Duration RESPONSE_TIME_VALIDATION_SKEW = Duration.ZERO;
83+
84+
@Autowired
85+
private ConfigurableApplicationContext context;
86+
87+
@Autowired
88+
private FilterChainProxy springSecurityFilterChain;
89+
90+
@Autowired
91+
private RelyingPartyRegistrationRepository repository;
92+
93+
@Autowired
94+
SecurityContextRepository securityContextRepository;
95+
96+
@Rule
97+
public final SpringTestRule spring = new SpringTestRule();
98+
99+
@Autowired(required = false)
100+
MockMvc mvc;
101+
102+
private MockHttpServletRequest request;
103+
private MockHttpServletResponse response;
104+
private MockFilterChain filterChain;
105+
106+
@Before
107+
public void setup() {
108+
this.request = new MockHttpServletRequest("POST", "");
109+
this.request.setServletPath("/login/saml2/sso/test-rp");
110+
this.response = new MockHttpServletResponse();
111+
this.filterChain = new MockFilterChain();
112+
}
113+
114+
@After
115+
public void cleanup() {
116+
if (this.context != null) {
117+
this.context.close();
118+
}
119+
}
120+
121+
@Test
122+
public void saml2LoginWhenConfiguringAuthenticationManagerThenTheManagerIsUsed() throws Exception {
123+
// setup application context
124+
this.spring.register(Saml2LoginConfigWithCustomAuthenticationManager.class).autowire();
125+
performSaml2Login("ROLE_AUTH_MANAGER");
126+
}
127+
128+
@Test
129+
public void saml2LoginWhenConfiguringAuthenticationDefaultsUsingCustomizerThenTheProviderIsConfigured() throws Exception {
130+
// setup application context
131+
this.spring.register(Saml2LoginConfigWithAuthenticationDefaultsWithPostProcessor.class).autowire();
132+
validateSaml2WebSsoAuthenticationFilterConfiguration();
133+
}
134+
135+
private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
136+
// get the OpenSamlAuthenticationProvider
137+
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
138+
AuthenticationManager manager =
139+
(AuthenticationManager) ReflectionTestUtils.getField(filter, "authenticationManager");
140+
ProviderManager pm = (ProviderManager) manager;
141+
AuthenticationProvider provider = pm.getProviders()
142+
.stream()
143+
.filter(p -> p instanceof OpenSamlAuthenticationProvider)
144+
.findFirst()
145+
.get();
146+
Assert.assertSame(AUTHORITIES_EXTRACTOR, ReflectionTestUtils.getField(provider, "authoritiesExtractor"));
147+
Assert.assertSame(AUTHORITIES_MAPPER, ReflectionTestUtils.getField(provider, "authoritiesMapper"));
148+
Assert.assertSame(RESPONSE_TIME_VALIDATION_SKEW, ReflectionTestUtils.getField(provider, "responseTimeValidationSkew"));
149+
}
150+
151+
private Saml2WebSsoAuthenticationFilter getSaml2SsoFilter(FilterChainProxy chain) {
152+
return (Saml2WebSsoAuthenticationFilter) chain.getFilters("/login/saml2/sso/test")
153+
.stream()
154+
.filter(f -> f instanceof Saml2WebSsoAuthenticationFilter)
155+
.findFirst()
156+
.get();
157+
}
158+
159+
private void performSaml2Login(String expected) throws IOException, ServletException {
160+
// setup authentication parameters
161+
this.request.setParameter(
162+
"SAMLResponse",
163+
Base64.getEncoder().encodeToString(
164+
"saml2-xml-response-object".getBytes()
165+
)
166+
);
167+
168+
169+
// perform test
170+
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
171+
172+
// assertions
173+
Authentication authentication = this.securityContextRepository
174+
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
175+
.getAuthentication();
176+
Assert.assertNotNull("Expected a valid authentication object.", authentication);
177+
assertThat(authentication.getAuthorities()).hasSize(1);
178+
assertThat(authentication.getAuthorities()).first()
179+
.isInstanceOf(SimpleGrantedAuthority.class).hasToString(expected);
180+
}
181+
182+
183+
@EnableWebSecurity
184+
@Import(Saml2LoginConfigBeans.class)
185+
static class Saml2LoginConfigWithCustomAuthenticationManager extends WebSecurityConfigurerAdapter {
186+
187+
@Override
188+
protected void configure(HttpSecurity http) throws Exception {
189+
http.saml2Login()
190+
.authenticationManager(
191+
getAuthenticationManagerMock("ROLE_AUTH_MANAGER")
192+
);
193+
super.configure(http);
194+
}
195+
}
196+
197+
@EnableWebSecurity
198+
@Import(Saml2LoginConfigBeans.class)
199+
static class Saml2LoginConfigWithAuthenticationDefaultsWithPostProcessor extends WebSecurityConfigurerAdapter {
200+
201+
@Override
202+
protected void configure(HttpSecurity http) throws Exception {
203+
ObjectPostProcessor<OpenSamlAuthenticationProvider> processor
204+
= new ObjectPostProcessor<OpenSamlAuthenticationProvider>() {
205+
@Override
206+
public <O extends OpenSamlAuthenticationProvider> O postProcess(O provider) {
207+
provider.setResponseTimeValidationSkew(RESPONSE_TIME_VALIDATION_SKEW);
208+
provider.setAuthoritiesMapper(AUTHORITIES_MAPPER);
209+
provider.setAuthoritiesExtractor(AUTHORITIES_EXTRACTOR);
210+
return provider;
211+
}
212+
};
213+
214+
http.saml2Login()
215+
.addObjectPostProcessor(processor)
216+
;
217+
super.configure(http);
218+
}
219+
}
220+
221+
private static AuthenticationManager getAuthenticationManagerMock(String role) {
222+
return new AuthenticationManager() {
223+
224+
@Override
225+
public Authentication authenticate(Authentication authentication)
226+
throws AuthenticationException {
227+
if (!supports(authentication.getClass())) {
228+
throw new AuthenticationServiceException("not supported");
229+
}
230+
return new Saml2Authentication(
231+
() -> "auth principal",
232+
"saml2 response",
233+
Collections.singletonList(
234+
new SimpleGrantedAuthority(role)
235+
)
236+
);
237+
}
238+
239+
public boolean supports(Class<?> authentication) {
240+
return authentication.isAssignableFrom(Saml2AuthenticationToken.class);
241+
}
242+
};
243+
}
244+
245+
static class Saml2LoginConfigBeans {
246+
247+
@Bean
248+
SecurityContextRepository securityContextRepository() {
249+
return new HttpSessionSecurityContextRepository();
250+
}
251+
252+
@Bean
253+
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
254+
RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class);
255+
when(repository.findByRegistrationId(anyString())).thenReturn(
256+
saml2AuthenticationConfiguration()
257+
);
258+
return repository;
259+
}
260+
}
261+
262+
}

0 commit comments

Comments
 (0)