1 package com.github.choonchernlim.security.adfs.saml2;
2
3 import static com.github.choonchernlim.betterPreconditions.preconditions.PreconditionFactory.expect;
4 import com.google.common.collect.ImmutableList;
5 import com.google.common.collect.ImmutableMap;
6 import org.apache.commons.httpclient.HttpClient;
7 import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
8 import org.apache.commons.httpclient.protocol.Protocol;
9 import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
10 import org.apache.velocity.app.VelocityEngine;
11 import org.opensaml.common.xml.SAMLConstants;
12 import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
13 import org.opensaml.saml2.metadata.provider.MetadataProvider;
14 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
15 import org.opensaml.xml.parse.StaticBasicParserPool;
16 import org.springframework.beans.factory.annotation.Autowired;
17 import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
18 import org.springframework.context.annotation.Bean;
19 import org.springframework.core.env.Environment;
20 import org.springframework.security.authentication.AuthenticationManager;
21 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
22 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
23 import org.springframework.security.config.annotation.web.builders.WebSecurity;
24 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
25 import org.springframework.security.core.userdetails.User;
26 import org.springframework.security.saml.SAMLAuthenticationProvider;
27 import org.springframework.security.saml.SAMLBootstrap;
28 import org.springframework.security.saml.SAMLDiscovery;
29 import org.springframework.security.saml.SAMLEntryPoint;
30 import org.springframework.security.saml.SAMLLogoutFilter;
31 import org.springframework.security.saml.SAMLLogoutProcessingFilter;
32 import org.springframework.security.saml.SAMLProcessingFilter;
33 import org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter;
34 import org.springframework.security.saml.context.SAMLContextProviderLB;
35 import org.springframework.security.saml.key.JKSKeyManager;
36 import org.springframework.security.saml.key.KeyManager;
37 import org.springframework.security.saml.log.SAMLDefaultLogger;
38 import org.springframework.security.saml.metadata.CachingMetadataManager;
39 import org.springframework.security.saml.metadata.ExtendedMetadataDelegate;
40 import org.springframework.security.saml.metadata.MetadataDisplayFilter;
41 import org.springframework.security.saml.metadata.MetadataGenerator;
42 import org.springframework.security.saml.metadata.MetadataGeneratorFilter;
43 import org.springframework.security.saml.parser.ParserPoolHolder;
44 import org.springframework.security.saml.processor.HTTPArtifactBinding;
45 import org.springframework.security.saml.processor.HTTPPAOS11Binding;
46 import org.springframework.security.saml.processor.HTTPPostBinding;
47 import org.springframework.security.saml.processor.HTTPRedirectDeflateBinding;
48 import org.springframework.security.saml.processor.HTTPSOAP11Binding;
49 import org.springframework.security.saml.processor.SAMLBinding;
50 import org.springframework.security.saml.processor.SAMLProcessorImpl;
51 import org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer;
52 import org.springframework.security.saml.trust.httpclient.TLSProtocolSocketFactory;
53 import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
54 import org.springframework.security.saml.util.VelocityFactory;
55 import org.springframework.security.saml.websso.ArtifactResolutionProfileImpl;
56 import org.springframework.security.saml.websso.SingleLogoutProfile;
57 import org.springframework.security.saml.websso.SingleLogoutProfileImpl;
58 import org.springframework.security.saml.websso.WebSSOProfile;
59 import org.springframework.security.saml.websso.WebSSOProfileConsumer;
60 import org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl;
61 import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl;
62 import org.springframework.security.saml.websso.WebSSOProfileECPImpl;
63 import org.springframework.security.saml.websso.WebSSOProfileImpl;
64 import org.springframework.security.saml.websso.WebSSOProfileOptions;
65 import org.springframework.security.web.DefaultSecurityFilterChain;
66 import org.springframework.security.web.FilterChainProxy;
67 import org.springframework.security.web.SecurityFilterChain;
68 import org.springframework.security.web.access.channel.ChannelProcessingFilter;
69 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
70 import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
71 import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
72 import org.springframework.security.web.authentication.logout.LogoutHandler;
73 import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
74 import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
75 import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
76 import org.springframework.security.web.csrf.CsrfFilter;
77 import org.springframework.security.web.csrf.CsrfTokenRepository;
78 import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
79 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
80
81 import javax.annotation.PostConstruct;
82 import java.util.Timer;
83
84
85
86
87
88 public abstract class SAMLWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
89
90
91
92
93 @Autowired
94 protected Environment env;
95
96 @Autowired
97 private SAMLAuthenticationProvider samlAuthenticationProvider;
98
99
100
101 @Bean
102 public static SAMLBootstrap samlBootstrap() {
103 return new DefaultSAMLBootstrap();
104 }
105
106
107
108
109
110
111 @Bean
112 protected abstract SAMLConfigBean samlConfigBean();
113
114
115
116
117
118
119
120
121
122
123 protected final HttpSecurity samlizedConfig(final HttpSecurity http) throws Exception {
124 http.httpBasic().authenticationEntryPoint(samlEntryPoint())
125 .and()
126 .csrf().ignoringAntMatchers("/saml/**")
127 .and()
128 .authorizeRequests().antMatchers("/saml/**").permitAll()
129 .and()
130 .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
131 .addFilterAfter(filterChainProxy(), BasicAuthenticationFilter.class);
132
133
134 if (samlConfigBean().getStoreCsrfTokenInCookie()) {
135 http.csrf()
136 .csrfTokenRepository(csrfTokenRepository())
137 .and()
138 .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
139 }
140
141 return http;
142 }
143
144
145
146
147
148
149 private CsrfTokenRepository csrfTokenRepository() {
150 HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
151 repository.setHeaderName(CsrfHeaderFilter.HEADER_NAME);
152 return repository;
153 }
154
155
156
157
158
159
160
161
162
163
164
165 protected final HttpSecurity mockSecurity(final HttpSecurity http, final User user) {
166 expect(user, "user").not().toBeNull().check();
167
168 if (samlConfigBean().getSamlUserDetailsService() == null) {
169 throw new SpringSecurityAdfsSaml2Exception(
170 "`samlConfigBean.samlUserDetailsService` cannot be null. " +
171 "When mocking security, the given user details object will be set as principal. " +
172 "Because setting `samlConfigBean.samlUserDetailsService` will set the user details object as principal, " +
173 "this property must be configured to ensure the mock security mimics the actual security configuration."
174 );
175 }
176
177 return http.addFilterBefore(new MockFilterSecurityInterceptor(user), FilterSecurityInterceptor.class);
178 }
179
180
181
182
183
184
185
186
187 protected final WebSecurity samlizedConfig(final WebSecurity web) throws Exception {
188 web.ignoring().antMatchers(samlConfigBean().getSuccessLogoutUrl());
189 return web;
190 }
191
192
193 private String getMetdataUrl() {
194 return String.format("https://%s/federationmetadata/2007-06/federationmetadata.xml",
195 samlConfigBean().getIdpServerName());
196 }
197
198
199 @Bean
200 public SAMLEntryPoint samlEntryPoint() {
201 SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
202 samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions());
203 return samlEntryPoint;
204 }
205
206
207
208
209
210
211 @Bean
212 public WebSSOProfileOptions webSSOProfileOptions() {
213 WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
214
215
216
217
218 webSSOProfileOptions.setIncludeScoping(false);
219
220
221 webSSOProfileOptions.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
222
223
224
225
226 webSSOProfileOptions.setForceAuthN(true);
227
228
229
230 if (!samlConfigBean().getAuthnContexts().isEmpty()) {
231 webSSOProfileOptions.setAuthnContexts(samlConfigBean().getAuthnContexts());
232 }
233
234 return webSSOProfileOptions;
235 }
236
237
238 @Bean
239 public MetadataGeneratorFilter metadataGeneratorFilter() {
240
241
242 StringBuilder sb = new StringBuilder();
243 sb.append("https://").append(samlConfigBean().getSpServerName());
244 if (samlConfigBean().getSpHttpsPort() != 443) {
245 sb.append(":").append(samlConfigBean().getSpHttpsPort());
246 }
247 sb.append(samlConfigBean().getSpContextPath());
248 String entityBaseUrl = sb.toString();
249
250 MetadataGenerator metadataGenerator = new MetadataGenerator();
251 metadataGenerator.setKeyManager(keyManager());
252 metadataGenerator.setEntityBaseURL(entityBaseUrl);
253 return new MetadataGeneratorFilter(metadataGenerator);
254 }
255
256
257 @Bean
258 public HttpClient httpClient() {
259 return new HttpClient(new MultiThreadedHttpConnectionManager());
260 }
261
262
263 @Bean
264 public FilterChainProxy filterChainProxy() throws Exception {
265
266 return new FilterChainProxy(ImmutableList.<SecurityFilterChain>of(
267 new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint()),
268 new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter()),
269 new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"), metadataDisplayFilter()),
270 new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), samlProcessingFilter()),
271 new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"), samlWebSSOHoKProcessingFilter()),
272 new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"), samlLogoutProcessingFilter()),
273 new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"), samlIDPDiscovery())
274 ));
275
276 }
277
278
279 @Bean
280 public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
281 SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
282 successRedirectHandler.setDefaultTargetUrl(samlConfigBean().getSuccessLoginDefaultUrl());
283 return successRedirectHandler;
284 }
285
286
287 @Bean
288 public SimpleUrlAuthenticationFailureHandler failureRedirectHandler() {
289 SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
290
291
292
293 if (!samlConfigBean().getFailedLoginDefaultUrl().isEmpty()) {
294 failureHandler.setDefaultFailureUrl(samlConfigBean().getFailedLoginDefaultUrl());
295 }
296
297 return failureHandler;
298 }
299
300
301 @Bean
302 public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
303 SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
304 successLogoutHandler.setDefaultTargetUrl(samlConfigBean().getSuccessLogoutUrl());
305 return successLogoutHandler;
306 }
307
308
309 @Bean
310 @Override
311 public AuthenticationManager authenticationManagerBean() throws Exception {
312 return super.authenticationManagerBean();
313 }
314
315
316 @Override
317 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
318 auth.authenticationProvider(samlAuthenticationProvider);
319 }
320
321
322 @Bean
323 public SAMLDefaultLogger samlLogger() {
324 return new SAMLDefaultLogger();
325 }
326
327
328 @Bean
329 public KeyManager keyManager() {
330 return new JKSKeyManager(samlConfigBean().getKeystoreResource(),
331 samlConfigBean().getKeystorePassword(),
332 ImmutableMap.of(samlConfigBean().getKeystoreAlias(),
333 samlConfigBean().getKeystorePrivateKeyPassword()),
334 samlConfigBean().getKeystoreAlias());
335 }
336
337
338 @Bean
339 public SAMLDiscovery samlIDPDiscovery() {
340 return new SAMLDiscovery();
341 }
342
343
344 @Bean
345 public MetadataDisplayFilter metadataDisplayFilter() {
346 return new MetadataDisplayFilter();
347 }
348
349
350 @Bean
351 public TLSProtocolConfigurer tlsProtocolConfigurer() {
352 return new TLSProtocolConfigurer();
353 }
354
355
356 @Bean
357 public ProtocolSocketFactory protocolSocketFactory() {
358 return new TLSProtocolSocketFactory(keyManager(), null, "default");
359 }
360
361
362 @Bean
363 public Protocol protocol() {
364 return new Protocol("https", protocolSocketFactory(), 443);
365 }
366
367
368 @PostConstruct
369 public MethodInvokingFactoryBean socketFactoryInitialization() {
370 MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
371 methodInvokingFactoryBean.setTargetClass(Protocol.class);
372 methodInvokingFactoryBean.setTargetMethod("registerProtocol");
373 methodInvokingFactoryBean.setArguments(new Object[]{"https", protocol()});
374 return methodInvokingFactoryBean;
375 }
376
377
378 @Bean
379 public CachingMetadataManager metadata() throws MetadataProviderException {
380 HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(new Timer(true),
381 httpClient(),
382 getMetdataUrl());
383 httpMetadataProvider.setParserPool(parserPool());
384
385 ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider);
386
387 extendedMetadataDelegate.setMetadataTrustCheck(false);
388
389 return new CachingMetadataManager(ImmutableList.<MetadataProvider>of(extendedMetadataDelegate));
390 }
391
392
393 @Bean
394 public SAMLAuthenticationProvider samlAuthenticationProvider() {
395 SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
396 SAMLUserDetailsService samlUserDetailsService = samlConfigBean().getSamlUserDetailsService();
397
398 if (samlUserDetailsService != null) {
399 samlAuthenticationProvider.setUserDetails(samlUserDetailsService);
400
401
402
403
404
405 samlAuthenticationProvider.setForcePrincipalAsString(false);
406 }
407
408 return samlAuthenticationProvider;
409 }
410
411
412
413
414
415
416
417
418 @Bean
419 public SAMLContextProviderLB contextProvider() {
420 SAMLContextProviderLB contextProviderLB = new SAMLContextProviderLB();
421 contextProviderLB.setScheme("https");
422 contextProviderLB.setServerName(samlConfigBean().getSpServerName());
423 contextProviderLB.setServerPort(samlConfigBean().getSpHttpsPort());
424 contextProviderLB.setIncludeServerPortInRequestURL(samlConfigBean().getSpHttpsPort() != 443);
425 contextProviderLB.setContextPath(samlConfigBean().getSpContextPath());
426 return contextProviderLB;
427 }
428
429
430 @Bean
431 public SAMLProcessingFilter samlProcessingFilter() throws Exception {
432 SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
433 samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
434 samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
435 samlWebSSOProcessingFilter.setAuthenticationFailureHandler(failureRedirectHandler());
436 return samlWebSSOProcessingFilter;
437 }
438
439
440 @Bean
441 public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
442 SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
443 samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
444 samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager());
445 samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(failureRedirectHandler());
446 return samlWebSSOHoKProcessingFilter;
447 }
448
449
450 @Bean
451 public SecurityContextLogoutHandler logoutHandler() {
452 SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
453 logoutHandler.setInvalidateHttpSession(true);
454 logoutHandler.setClearAuthentication(true);
455 return logoutHandler;
456 }
457
458
459 @Bean
460 public SAMLLogoutFilter samlLogoutFilter() {
461 return new SAMLLogoutFilter(successLogoutHandler(),
462 new LogoutHandler[]{logoutHandler()},
463 new LogoutHandler[]{logoutHandler()});
464 }
465
466
467
468 @Bean
469 public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
470 return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
471 }
472
473
474 @Bean
475 public SAMLProcessorImpl processor() {
476 return new SAMLProcessorImpl(ImmutableList.<SAMLBinding>of(redirectDeflateBinding(),
477 postBinding(),
478 artifactBinding(),
479 soapBinding(),
480 paosBinding()));
481 }
482
483
484 @Bean
485 public WebSSOProfileConsumer webSSOprofileConsumer() {
486 return new WebSSOProfileConsumerImpl();
487 }
488
489
490 @Bean
491 public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
492 return new WebSSOProfileConsumerHoKImpl();
493 }
494
495
496 @Bean
497 public WebSSOProfile webSSOprofile() {
498 return new WebSSOProfileImpl();
499 }
500
501
502 @Bean
503 public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
504 return new WebSSOProfileConsumerHoKImpl();
505 }
506
507
508 @Bean
509 public WebSSOProfileECPImpl ecpprofile() {
510 return new WebSSOProfileECPImpl();
511 }
512
513
514 @Bean
515 public SingleLogoutProfile logoutprofile() {
516 return new SingleLogoutProfileImpl();
517 }
518
519
520 @Bean
521 public HTTPPostBinding postBinding() {
522 return new HTTPPostBinding(parserPool(), velocityEngine());
523 }
524
525 @Bean
526 public HTTPRedirectDeflateBinding redirectDeflateBinding() {
527 return new HTTPRedirectDeflateBinding(parserPool());
528 }
529
530 @Bean
531 public HTTPArtifactBinding artifactBinding() {
532 ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient());
533 artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(soapBinding()));
534 return new HTTPArtifactBinding(parserPool(), velocityEngine(), artifactResolutionProfile);
535 }
536
537 @Bean
538 public HTTPSOAP11Binding soapBinding() {
539 return new HTTPSOAP11Binding(parserPool());
540 }
541
542 @Bean
543 public HTTPPAOS11Binding paosBinding() {
544 return new HTTPPAOS11Binding(parserPool());
545 }
546
547
548 @Bean
549 public VelocityEngine velocityEngine() {
550 return VelocityFactory.getEngine();
551 }
552
553
554 @Bean(initMethod = "initialize")
555 public StaticBasicParserPool parserPool() {
556 return new StaticBasicParserPool();
557 }
558
559 @Bean
560 public ParserPoolHolder parserPoolHolder() {
561 return new ParserPoolHolder();
562 }
563 }