View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.sra.security.saml2;
20  
21  import java.net.URI;
22  import org.apache.syncope.sra.security.pac4j.NoOpSessionStore;
23  import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
24  import org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
25  import org.apache.syncope.sra.session.SessionUtils;
26  import org.pac4j.saml.client.SAML2Client;
27  import org.pac4j.saml.credentials.SAML2Credentials;
28  import org.springframework.security.authentication.ReactiveAuthenticationManager;
29  import org.springframework.security.core.Authentication;
30  import org.springframework.security.web.server.ServerRedirectStrategy;
31  import org.springframework.security.web.server.WebFilterExchange;
32  import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
33  import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
34  import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
35  import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
36  import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
37  import org.springframework.web.server.ServerWebExchange;
38  import org.springframework.web.server.WebFilterChain;
39  import reactor.core.publisher.Mono;
40  
41  public class SAML2WebSsoAuthenticationWebFilter extends AuthenticationWebFilter {
42  
43      public static final String FILTER_PROCESSES_URI = "/login/saml2/sso";
44  
45      private static final ServerWebExchangeMatcher MATCHER =
46              ServerWebExchangeMatchers.pathMatchers(FILTER_PROCESSES_URI);
47  
48      private final SAML2Client saml2Client;
49  
50      public SAML2WebSsoAuthenticationWebFilter(
51              final ReactiveAuthenticationManager authenticationManager,
52              final SAML2Client saml2Client) {
53  
54          super(authenticationManager);
55  
56          this.saml2Client = saml2Client;
57  
58          setRequiresAuthenticationMatcher(matchSamlResponse());
59  
60          setServerAuthenticationConverter(convertSamlResponse());
61  
62          setAuthenticationSuccessHandler(redirectToInitialRequestURI());
63      }
64  
65      @Override
66      public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
67          return super.filter(exchange, chain).then(Mono.defer(exchange.getResponse()::setComplete));
68      }
69  
70      private ServerWebExchangeMatcher matchSamlResponse() {
71          return exchange -> exchange.getFormData().
72                  filter(form -> form.containsKey("SAMLResponse")).
73                  flatMap(form -> ServerWebExchangeMatcher.MatchResult.match()).
74                  switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
75      }
76  
77      private ServerAuthenticationConverter convertSamlResponse() {
78          return exchange -> exchange.getFormData().
79                  flatMap(form -> MATCHER.matches(exchange).
80                  flatMap(matchResult -> {
81                      ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange).setForm(form);
82  
83                      SAML2Credentials credentials = (SAML2Credentials) saml2Client.getCredentialsExtractor().
84                          extract(swec, NoOpSessionStore.INSTANCE).
85                              orElseThrow(() -> new IllegalStateException("No AuthnResponse found"));
86  
87                      saml2Client.getAuthenticator().validate(credentials, swec, NoOpSessionStore.INSTANCE);
88  
89                      return Mono.just(new SAML2AuthenticationToken(credentials));
90                  }));
91      }
92  
93      private ServerAuthenticationSuccessHandler redirectToInitialRequestURI() {
94          return new ServerAuthenticationSuccessHandler() {
95  
96              private final ServerRedirectStrategy redirectStrategy = new DoNothingIfCommittedServerRedirectStrategy();
97  
98              @Override
99              public Mono<Void> onAuthenticationSuccess(
100                     final WebFilterExchange webFilterExchange, final Authentication authentication) {
101 
102                 return webFilterExchange.getExchange().getSession().
103                         flatMap(session -> this.redirectStrategy.sendRedirect(
104                         webFilterExchange.getExchange(),
105                         session.<URI>getRequiredAttribute(SessionUtils.INITIAL_REQUEST_URI)));
106             }
107         };
108     }
109 }