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.oauth2;
20  
21  import java.net.URI;
22  import java.nio.charset.StandardCharsets;
23  import org.apache.syncope.sra.security.AbstractServerLogoutSuccessHandler;
24  import org.springframework.beans.factory.annotation.Autowired;
25  import org.springframework.beans.factory.annotation.Qualifier;
26  import org.springframework.security.core.Authentication;
27  import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
28  import org.springframework.security.oauth2.client.registration.ClientRegistration;
29  import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
30  import org.springframework.security.oauth2.core.oidc.user.OidcUser;
31  import org.springframework.security.web.server.WebFilterExchange;
32  import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler;
33  import org.springframework.util.Assert;
34  import org.springframework.web.util.UriComponentsBuilder;
35  import reactor.core.publisher.Mono;
36  
37  /**
38   * A reactive logout success handler for initiating OIDC logout through the user agent.
39   *
40   * @see <a href="https://openid.net/specs/openid-connect-session-1_0.html#RPLogout">RP-Initiated Logout</a>
41   * @see org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler
42   */
43  public class OidcClientInitiatedServerLogoutSuccessHandler extends AbstractServerLogoutSuccessHandler {
44  
45      @Autowired
46      @Qualifier("oidcClientRegistrationRepository")
47      private ReactiveClientRegistrationRepository clientRegistrationRepository;
48  
49      protected final RedirectServerLogoutSuccessHandler serverLogoutSuccessHandler =
50              new RedirectServerLogoutSuccessHandler();
51  
52      /**
53       * The URL to redirect to after successfully logging out when not originally an OIDC login
54       *
55       * @param logoutSuccessUrl the url to redirect to. Default is "/login?logout".
56       */
57      public void setLogoutSuccessUrl(final URI logoutSuccessUrl) {
58          Assert.notNull(logoutSuccessUrl, "logoutSuccessUrl cannot be null");
59          this.serverLogoutSuccessHandler.setLogoutSuccessUrl(logoutSuccessUrl);
60      }
61  
62      @Override
63      public Mono<Void> onLogoutSuccess(final WebFilterExchange exchange, final Authentication authentication) {
64          return Mono.just(authentication).
65                  filter(OAuth2AuthenticationToken.class::isInstance).
66                  filter(token -> authentication.getPrincipal() instanceof OidcUser).
67                  map(OAuth2AuthenticationToken.class::cast).
68                  flatMap(this::endSessionEndpoint).
69                  map(endSessionEndpoint -> endpointUri(exchange, endSessionEndpoint, authentication)).
70                  switchIfEmpty(serverLogoutSuccessHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty())).
71                  flatMap(endpointUri -> redirectStrategy.sendRedirect(exchange.getExchange(), endpointUri));
72      }
73  
74      private Mono<URI> endSessionEndpoint(final OAuth2AuthenticationToken token) {
75          String registrationId = token.getAuthorizedClientRegistrationId();
76          return clientRegistrationRepository.findByRegistrationId(registrationId).
77                  map(ClientRegistration::getProviderDetails).
78                  map(ClientRegistration.ProviderDetails::getConfigurationMetadata).
79                  flatMap(configurationMetadata -> Mono.justOrEmpty(configurationMetadata.get("end_session_endpoint"))).
80                  map(Object::toString).
81                  map(URI::create);
82      }
83  
84      private URI endpointUri(
85              final WebFilterExchange exchange,
86              final URI endSessionEndpoint,
87              final Authentication authentication) {
88  
89          UriComponentsBuilder builder = UriComponentsBuilder.fromUri(endSessionEndpoint);
90          builder.queryParam("id_token_hint", idToken(authentication));
91  
92          URI postLogout = getPostLogout(exchange);
93          builder.queryParam("post_logout_redirect_uri", postLogout);
94  
95          return builder.encode(StandardCharsets.UTF_8).build().toUri();
96      }
97  
98      private String idToken(final Authentication authentication) {
99          return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue();
100     }
101 }