diff --git a/webflux/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java b/webflux/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java index 0b9728487e..46d85a48ba 100644 --- a/webflux/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java +++ b/webflux/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java @@ -23,6 +23,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.LinkedHashMap; @@ -41,16 +42,12 @@ public class DelegatingReactiveAuthorizationManager implements ReactiveAuthoriza @Override public Mono check(Mono authentication, ServerWebExchange exchange) { - for(Map.Entry> entry : mappings.entrySet()) { - ServerWebExchangeMatcher matcher = entry.getKey(); - ServerWebExchangeMatcher.MatchResult match = matcher.matches(exchange); - if(match.isMatch()) { - Map variables = match.getVariables(); - AuthorizationContext context = new AuthorizationContext(exchange, variables); - return entry.getValue().check(authentication, context); - } - } - return Mono.just(new AuthorizationDecision(false)); + return Flux.fromIterable(mappings.entrySet()) + .concatMap(entry -> entry.getKey().matches(exchange) + .filter(ServerWebExchangeMatcher.MatchResult::isMatch) + .flatMap(r -> entry.getValue().check(authentication, new AuthorizationContext(exchange, r.getVariables())))) + .next() + .defaultIfEmpty(new AuthorizationDecision(false)); } public static DelegatingReactiveAuthorizationManager.Builder builder() { diff --git a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcher.java b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcher.java index 0d94d77469..f3a8216462 100644 --- a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcher.java +++ b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcher.java @@ -19,6 +19,8 @@ package org.springframework.security.web.server.util.matcher; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.HashMap; @@ -45,12 +47,15 @@ public class AndServerWebExchangeMatcher implements ServerWebExchangeMatcher { * @see org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher#matches(org.springframework.web.server.ServerWebExchange) */ @Override - public MatchResult matches(ServerWebExchange exchange) { - Map variables = new HashMap<>(); - return matchers.stream() - .map(m -> m.matches(exchange)) - .peek( m -> variables.putAll(m.getVariables())) - .allMatch(m -> m.isMatch()) ? MatchResult.match(variables) : MatchResult.notMatch(); + public Mono matches(ServerWebExchange exchange) { + return Mono.defer(() -> { + Map variables = new HashMap<>(); + return Flux.fromIterable(matchers) + .flatMap(matcher -> matcher.matches(exchange)) + .doOnNext(matchResult -> variables.putAll(matchResult.getVariables())) + .all(MatchResult::isMatch) + .flatMap(allMatch -> allMatch ? MatchResult.match(variables) : MatchResult.notMatch()); + }); } @Override diff --git a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcher.java b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcher.java index 94f0519017..3b6b747751 100644 --- a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcher.java +++ b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcher.java @@ -23,6 +23,8 @@ import java.util.function.Predicate; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; /** * @author Rob Winch @@ -45,12 +47,12 @@ public class OrServerWebExchangeMatcher implements ServerWebExchangeMatcher { * @see org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher#matches(org.springframework.web.server.ServerWebExchange) */ @Override - public MatchResult matches(ServerWebExchange exchange) { - return matchers.stream() - .map(m -> m.matches(exchange)) + public Mono matches(ServerWebExchange exchange) { + return Flux.fromIterable(matchers) + .flatMap(m -> m.matches(exchange)) .filter(m -> m.isMatch()) - .findFirst() - .orElse(MatchResult.notMatch()); + .next() + .switchIfEmpty(MatchResult.notMatch()); } @Override diff --git a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcher.java b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcher.java index 9d1614a344..e8ce40a169 100644 --- a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcher.java +++ b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcher.java @@ -27,6 +27,7 @@ import org.springframework.util.Assert; import org.springframework.util.PathMatcher; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.support.HttpRequestPathHelper; +import reactor.core.publisher.Mono; /** * @author Rob Winch @@ -51,7 +52,7 @@ public final class PathMatcherServerWebExchangeMatcher implements ServerWebExcha } @Override - public MatchResult matches(ServerWebExchange exchange) { + public Mono matches(ServerWebExchange exchange) { ServerHttpRequest request = exchange.getRequest(); if(this.method != null && !this.method.equals(request.getMethod())) { return MatchResult.notMatch(); diff --git a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatcher.java b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatcher.java index 79f8523196..128965f0cb 100644 --- a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatcher.java +++ b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatcher.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.Map; import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; /** * @@ -29,7 +30,7 @@ import org.springframework.web.server.ServerWebExchange; */ public interface ServerWebExchangeMatcher { - MatchResult matches(ServerWebExchange exchange); + Mono matches(ServerWebExchange exchange); class MatchResult { private final boolean match; @@ -48,16 +49,16 @@ public interface ServerWebExchangeMatcher { return variables; } - public static MatchResult match() { + public static Mono match() { return match(Collections.emptyMap()); } - public static MatchResult match(Map variables) { - return new MatchResult(true, variables); + public static Mono match(Map variables) { + return Mono.just(new MatchResult(true, variables)); } - public static MatchResult notMatch() { - return new MatchResult(false, Collections.emptyMap()); + public static Mono notMatch() { + return Mono.just(new MatchResult(false, Collections.emptyMap())); } } } diff --git a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchers.java b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchers.java index e882529c79..a101613f5d 100644 --- a/webflux/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchers.java +++ b/webflux/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchers.java @@ -19,6 +19,7 @@ package org.springframework.security.web.server.util.matcher; import org.springframework.http.HttpMethod; import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; import java.util.ArrayList; import java.util.List; @@ -48,7 +49,7 @@ public abstract class ServerWebExchangeMatchers { public static ServerWebExchangeMatcher anyExchange() { return new ServerWebExchangeMatcher() { @Override - public MatchResult matches(ServerWebExchange exchange) { + public Mono matches(ServerWebExchange exchange) { return ServerWebExchangeMatcher.MatchResult.match(); } }; diff --git a/webflux/src/test/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManagerTests.java b/webflux/src/test/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManagerTests.java new file mode 100644 index 0000000000..d9fba16275 --- /dev/null +++ b/webflux/src/test/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManagerTests.java @@ -0,0 +1,90 @@ +/* + * + * * Copyright 2002-2017 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.springframework.security.web.server.authorization; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.security.authorization.AuthorityAuthorizationManager; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +/** + * @author Rob Winch + * @since 5.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class DelegatingReactiveAuthorizationManagerTests { + @Mock + ServerWebExchangeMatcher match1; + @Mock + ServerWebExchangeMatcher match2; + @Mock + AuthorityAuthorizationManager delegate1; + @Mock + AuthorityAuthorizationManager delegate2; + @Mock + ServerWebExchange exchange; + @Mock + Mono authentication; + @Mock + AuthorizationDecision decision; + + DelegatingReactiveAuthorizationManager manager; + + @Before + public void setup() { + manager = DelegatingReactiveAuthorizationManager.builder() + .add(match1, delegate1) + .add(match2, delegate2) + .build(); + } + + @Test + public void checkWhenFirstMatchesThenNoMoreMatchersAndNoMoreDelegatesInvoked() { + when(match1.matches(any())).thenReturn(ServerWebExchangeMatcher.MatchResult.match()); + when(delegate1.check(eq(authentication), any(AuthorizationContext.class))).thenReturn(Mono.just(decision)); + + assertThat(manager.check(authentication, exchange).block()).isEqualTo(decision); + + verifyZeroInteractions(match2, delegate2); + } + + @Test + public void checkWhenSecondMatchesThenNoMoreMatchersAndNoMoreDelegatesInvoked() { + when(match1.matches(any())).thenReturn(ServerWebExchangeMatcher.MatchResult.notMatch()); + when(match2.matches(any())).thenReturn(ServerWebExchangeMatcher.MatchResult.match()); + when(delegate2.check(eq(authentication), any(AuthorizationContext.class))).thenReturn(Mono.just(decision)); + + assertThat(manager.check(authentication, exchange).block()).isEqualTo(decision); + + verifyZeroInteractions(delegate1); + } +} diff --git a/webflux/src/test/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcherTests.java b/webflux/src/test/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcherTests.java index f1805c11fb..1d4752c1f3 100644 --- a/webflux/src/test/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcherTests.java +++ b/webflux/src/test/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcherTests.java @@ -61,7 +61,7 @@ public class AndServerWebExchangeMatcherTests { when(matcher1.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.match(params1)); when(matcher2.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.match(params2)); - ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange); + ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange).block(); assertThat(matches.isMatch()).isTrue(); assertThat(matches.getVariables()).hasSize(2); @@ -76,7 +76,7 @@ public class AndServerWebExchangeMatcherTests { public void matchesWhenFalseFalseThenFalseAndMatcher2NotInvoked() throws Exception { when(matcher1.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.notMatch()); - ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange); + ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange).block(); assertThat(matches.isMatch()).isFalse(); assertThat(matches.getVariables()).isEmpty(); @@ -91,7 +91,7 @@ public class AndServerWebExchangeMatcherTests { when(matcher1.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.match(params)); when(matcher2.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.notMatch()); - ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange); + ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange).block(); assertThat(matches.isMatch()).isFalse(); assertThat(matches.getVariables()).isEmpty(); @@ -104,7 +104,7 @@ public class AndServerWebExchangeMatcherTests { public void matchesWhenFalseTrueThenFalse() throws Exception { when(matcher1.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.notMatch()); - ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange); + ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange).block(); assertThat(matches.isMatch()).isFalse(); assertThat(matches.getVariables()).isEmpty(); diff --git a/webflux/src/test/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcherTests.java b/webflux/src/test/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcherTests.java index 719ad71448..adc25eaa19 100644 --- a/webflux/src/test/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcherTests.java +++ b/webflux/src/test/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcherTests.java @@ -59,7 +59,7 @@ public class OrServerWebExchangeMatcherTests { when(matcher1.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.notMatch()); when(matcher2.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.notMatch()); - ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange); + ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange).block(); assertThat(matches.isMatch()).isFalse(); assertThat(matches.getVariables()).isEmpty(); @@ -73,7 +73,7 @@ public class OrServerWebExchangeMatcherTests { Map params = Collections.singletonMap("foo", "bar"); when(matcher1.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.match(params)); - ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange); + ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange).block(); assertThat(matches.isMatch()).isTrue(); assertThat(matches.getVariables()).isEqualTo(params); @@ -88,7 +88,7 @@ public class OrServerWebExchangeMatcherTests { when(matcher1.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.notMatch()); when(matcher2.matches(exchange)).thenReturn(ServerWebExchangeMatcher.MatchResult.match(params)); - ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange); + ServerWebExchangeMatcher.MatchResult matches = matcher.matches(exchange).block(); assertThat(matches.isMatch()).isTrue(); assertThat(matches.getVariables()).isEqualTo(params); diff --git a/webflux/src/test/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcherTests.java b/webflux/src/test/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcherTests.java index 122992aa54..36f0bdcbfb 100644 --- a/webflux/src/test/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcherTests.java +++ b/webflux/src/test/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcherTests.java @@ -75,14 +75,14 @@ public class PathMatcherServerWebExchangeMatcherTests { public void matchesWhenPathMatcherTrueThenReturnTrue() { when(pathMatcher.match(pattern, path)).thenReturn(true); - assertThat(matcher.matches(exchange).isMatch()).isTrue(); + assertThat(matcher.matches(exchange).block().isMatch()).isTrue(); } @Test public void matchesWhenPathMatcherFalseThenReturnFalse() { when(pathMatcher.match(pattern, path)).thenReturn(false); - assertThat(matcher.matches(exchange).isMatch()).isFalse(); + assertThat(matcher.matches(exchange).block().isMatch()).isFalse(); verify(pathMatcher).match(pattern, path); } @@ -93,7 +93,7 @@ public class PathMatcherServerWebExchangeMatcherTests { matcher.setPathMatcher(pathMatcher); when(pathMatcher.match(pattern, path)).thenReturn(true); - assertThat(matcher.matches(exchange).isMatch()).isTrue(); + assertThat(matcher.matches(exchange).block().isMatch()).isTrue(); } @Test @@ -103,7 +103,7 @@ public class PathMatcherServerWebExchangeMatcherTests { matcher = new PathMatcherServerWebExchangeMatcher(pattern, method); matcher.setPathMatcher(pathMatcher); - assertThat(matcher.matches(exchange).isMatch()).isFalse(); + assertThat(matcher.matches(exchange).block().isMatch()).isFalse(); verifyZeroInteractions(pathMatcher); } diff --git a/webflux/src/test/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchersTests.java b/webflux/src/test/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchersTests.java index c42f1774f4..f21d69a29a 100644 --- a/webflux/src/test/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchersTests.java +++ b/webflux/src/test/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchersTests.java @@ -39,34 +39,34 @@ public class ServerWebExchangeMatchersTests { @Test public void antMatchersWhenSingleAndSamePatternThenMatches() throws Exception { - assertThat(antMatchers("/").matches(exchange).isMatch()).isTrue(); + assertThat(antMatchers("/").matches(exchange).block().isMatch()).isTrue(); } @Test public void antMatchersWhenSingleAndSamePatternAndMethodThenMatches() throws Exception { - assertThat(antMatchers(HttpMethod.GET, "/").matches(exchange).isMatch()).isTrue(); + assertThat(antMatchers(HttpMethod.GET, "/").matches(exchange).block().isMatch()).isTrue(); } @Test public void antMatchersWhenSingleAndSamePatternAndDiffMethodThenDoesNotMatch() throws Exception { - assertThat(antMatchers(HttpMethod.POST, "/").matches(exchange).isMatch()).isFalse(); + assertThat(antMatchers(HttpMethod.POST, "/").matches(exchange).block().isMatch()).isFalse(); } @Test public void antMatchersWhenSingleAndDifferentPatternThenDoesNotMatch() throws Exception { - assertThat(antMatchers("/foobar").matches(exchange).isMatch()).isFalse(); + assertThat(antMatchers("/foobar").matches(exchange).block().isMatch()).isFalse(); } @Test public void antMatchersWhenMultiThenMatches() throws Exception { - assertThat(antMatchers("/foobar", "/").matches(exchange).isMatch()).isTrue(); + assertThat(antMatchers("/foobar", "/").matches(exchange).block().isMatch()).isTrue(); } @Test public void anyExchangeWhenMockThenMatches() { ServerWebExchange mockExchange = mock(ServerWebExchange.class); - assertThat(anyExchange().matches(mockExchange).isMatch()).isTrue(); + assertThat(anyExchange().matches(mockExchange).block().isMatch()).isTrue(); verifyZeroInteractions(mockExchange); }