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;
20  
21  import java.util.Map;
22  import java.util.Optional;
23  import java.util.concurrent.ConcurrentHashMap;
24  import org.apache.syncope.sra.RouteProvider;
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  import org.springframework.beans.factory.annotation.Autowired;
28  import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
29  import org.springframework.cloud.gateway.route.Route;
30  import org.springframework.cloud.gateway.route.RouteLocator;
31  import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
32  import org.springframework.context.ApplicationListener;
33  import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
34  import org.springframework.web.server.ServerWebExchange;
35  import reactor.core.publisher.Mono;
36  
37  public abstract class AbstractRouteMatcher
38          implements ServerWebExchangeMatcher, ApplicationListener<RefreshRoutesEvent> {
39  
40      private static final Logger LOG = LoggerFactory.getLogger(AbstractRouteMatcher.class);
41  
42      protected static final Map<String, Map<String, Boolean>> CACHE = new ConcurrentHashMap<>();
43  
44      @Autowired
45      private RouteLocator routeLocator;
46  
47      @Autowired
48      protected RouteProvider routeProvider;
49  
50      protected abstract String getCacheName();
51  
52      protected abstract boolean routeBehavior(Route route);
53  
54      @Override
55      public void onApplicationEvent(final RefreshRoutesEvent event) {
56          Optional.ofNullable(CACHE.get(getCacheName())).ifPresent(Map::clear);
57      }
58  
59      @Override
60      public Mono<MatchResult> matches(final ServerWebExchange exchange) {
61          // see org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#lookupRoute
62          return routeLocator.getRoutes().
63                  // individually filter routes so that filterWhen error delaying is not a problem
64                  concatMap(route -> Mono.just(route).filterWhen(r -> r.getPredicate().apply(exchange)).
65                  // instead of immediately stopping main flux due to error, log and swallow it
66                  doOnError(e -> LOG.error("Error applying predicate for route: {}", route.getId(), e)).
67                  onErrorResume(e -> Mono.empty())).
68                  next().
69                  flatMap(route -> {
70                      exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, route.getId());
71                      LOG.debug("[{}] Route found: {}", getClass().getName(), route);
72  
73                      boolean cond = Optional.ofNullable(CACHE.get(getCacheName()).get(route.getId())).orElseGet(() -> {
74                          boolean result = routeBehavior(route);
75                          CACHE.get(getCacheName()).put(route.getId(), result);
76                          return result;
77                      });
78                      LOG.debug("[{}] Condition matched: {}", getClass().getName(), cond);
79  
80                      return cond ? MatchResult.match() : MatchResult.notMatch();
81                  }).switchIfEmpty(Mono.defer(() -> {
82              LOG.debug("[{}] No Route found", getClass().getName());
83              return MatchResult.notMatch();
84          }));
85      }
86  }