11 changed files with 400 additions and 69 deletions
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
/* |
||||
* Copyright 2012-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.boot.autoconfigure.security; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.stream.Stream; |
||||
|
||||
/** |
||||
* Common locations for static resources. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
public enum StaticResourceLocation { |
||||
|
||||
/** |
||||
* Resources under {@code "/css"}. |
||||
*/ |
||||
CSS("/css/**"), |
||||
|
||||
/** |
||||
* Resources under {@code "/js"}. |
||||
*/ |
||||
JAVA_SCRIPT("/js/**"), |
||||
|
||||
/** |
||||
* Resources under {@code "/images"}. |
||||
*/ |
||||
IMAGES("/images/**"), |
||||
|
||||
/** |
||||
* Resources under {@code "/webjars"}. |
||||
*/ |
||||
WEB_JARS("/webjars/**"), |
||||
|
||||
/** |
||||
* The {@code "favicon.ico"} resource. |
||||
*/ |
||||
FAVICON("/**/favicon.ico"); |
||||
|
||||
private String[] patterns; |
||||
|
||||
StaticResourceLocation(String... patterns) { |
||||
this.patterns = patterns; |
||||
} |
||||
|
||||
public Stream<String> getPatterns() { |
||||
return Arrays.stream(this.patterns); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,139 @@
@@ -0,0 +1,139 @@
|
||||
/* |
||||
* Copyright 2012-2018 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.boot.autoconfigure.security.reactive; |
||||
|
||||
import java.util.EnumSet; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.boot.autoconfigure.security.StaticResourceLocation; |
||||
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher; |
||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; |
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
|
||||
/** |
||||
* Factory that can be used to create a {@link ServerWebExchangeMatcher} for static resources in |
||||
* commonly used locations. |
||||
* |
||||
* @author Madhura Bhave |
||||
* @since 2.0.0 |
||||
*/ |
||||
public final class StaticResourceRequest { |
||||
|
||||
private StaticResourceRequest() { |
||||
} |
||||
|
||||
/** |
||||
* Returns a matcher that includes all commonly used {@link StaticResourceLocation Locations}. The |
||||
* {@link StaticResourceServerWebExchange#excluding(StaticResourceLocation, StaticResourceLocation...) excluding} |
||||
* method can be used to remove specific locations if required. For example: |
||||
* <pre class="code"> |
||||
* StaticResourceRequest.toCommonLocations().excluding(StaticResourceLocation.CSS) |
||||
* </pre> |
||||
* @return the configured {@link ServerWebExchangeMatcher} |
||||
*/ |
||||
public static StaticResourceServerWebExchange toCommonLocations() { |
||||
return to(EnumSet.allOf(StaticResourceLocation.class)); |
||||
} |
||||
|
||||
/** |
||||
* Returns a matcher that includes the specified {@link StaticResourceLocation Locations}. For |
||||
* example: <pre class="code"> |
||||
* to(StaticResourceLocation.CSS, StaticResourceLocation.JAVA_SCRIPT) |
||||
* </pre> |
||||
* @param first the first location to include |
||||
* @param rest additional locations to include |
||||
* @return the configured {@link ServerWebExchangeMatcher} |
||||
*/ |
||||
public static StaticResourceServerWebExchange to(StaticResourceLocation first, StaticResourceLocation... rest) { |
||||
return to(EnumSet.of(first, rest)); |
||||
} |
||||
|
||||
/** |
||||
* Returns a matcher that includes the specified {@link StaticResourceLocation Locations}. For |
||||
* example: <pre class="code"> |
||||
* to(locations) |
||||
* </pre> |
||||
* @param locations the locations to include |
||||
* @return the configured {@link ServerWebExchangeMatcher} |
||||
*/ |
||||
public static StaticResourceServerWebExchange to(Set<StaticResourceLocation> locations) { |
||||
Assert.notNull(locations, "Locations must not be null"); |
||||
return new StaticResourceServerWebExchange(new LinkedHashSet<>(locations)); |
||||
} |
||||
|
||||
/** |
||||
* The server web exchange matcher used to match against resource {@link StaticResourceLocation Locations}. |
||||
*/ |
||||
public final static class StaticResourceServerWebExchange |
||||
implements ServerWebExchangeMatcher { |
||||
|
||||
private final Set<StaticResourceLocation> locations; |
||||
|
||||
private StaticResourceServerWebExchange(Set<StaticResourceLocation> locations) { |
||||
this.locations = locations; |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@link StaticResourceServerWebExchange} based on this one but |
||||
* excluding the specified locations. |
||||
* @param first the first location to exclude |
||||
* @param rest additional locations to exclude |
||||
* @return a new {@link StaticResourceServerWebExchange} |
||||
*/ |
||||
public StaticResourceServerWebExchange excluding(StaticResourceLocation first, StaticResourceLocation... rest) { |
||||
return excluding(EnumSet.of(first, rest)); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@link StaticResourceServerWebExchange} based on this one but |
||||
* excluding the specified locations. |
||||
* @param locations the locations to exclude |
||||
* @return a new {@link StaticResourceServerWebExchange} |
||||
*/ |
||||
public StaticResourceServerWebExchange excluding(Set<StaticResourceLocation> locations) { |
||||
Assert.notNull(locations, "Locations must not be null"); |
||||
Set<StaticResourceLocation> subset = new LinkedHashSet<>(this.locations); |
||||
subset.removeAll(locations); |
||||
return new StaticResourceServerWebExchange(subset); |
||||
} |
||||
|
||||
private List<ServerWebExchangeMatcher> getDelegateMatchers() { |
||||
return getPatterns().map(PathPatternParserServerWebExchangeMatcher::new) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
private Stream<String> getPatterns() { |
||||
return this.locations.stream().flatMap(StaticResourceLocation::getPatterns); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<MatchResult> matches(ServerWebExchange exchange) { |
||||
OrServerWebExchangeMatcher matcher = new OrServerWebExchangeMatcher(getDelegateMatchers()); |
||||
return matcher.matches(exchange); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,152 @@
@@ -0,0 +1,152 @@
|
||||
/* |
||||
* Copyright 2012-2018 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.boot.autoconfigure.security.reactive; |
||||
|
||||
import org.assertj.core.api.AssertDelegateTarget; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.ExpectedException; |
||||
|
||||
import org.springframework.boot.autoconfigure.security.StaticResourceLocation; |
||||
import org.springframework.boot.autoconfigure.web.ServerProperties; |
||||
import org.springframework.context.support.StaticApplicationContext; |
||||
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||
import org.springframework.http.server.reactive.ServerHttpResponse; |
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest; |
||||
import org.springframework.mock.http.server.reactive.MockServerHttpResponse; |
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; |
||||
import org.springframework.web.context.support.StaticWebApplicationContext; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import org.springframework.web.server.WebHandler; |
||||
import org.springframework.web.server.adapter.HttpWebHandlerAdapter; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link StaticResourceRequest}. |
||||
* |
||||
* @author Madhura Bhave |
||||
*/ |
||||
public class StaticResourceRequestTests { |
||||
|
||||
@Rule |
||||
public ExpectedException thrown = ExpectedException.none(); |
||||
|
||||
@Test |
||||
public void toCommonLocationsShouldMatchCommonLocations() { |
||||
ServerWebExchangeMatcher matcher = StaticResourceRequest.toCommonLocations(); |
||||
assertMatcher(matcher).matches("/css/file.css"); |
||||
assertMatcher(matcher).matches("/js/file.js"); |
||||
assertMatcher(matcher).matches("/images/file.css"); |
||||
assertMatcher(matcher).matches("/webjars/file.css"); |
||||
assertMatcher(matcher).matches("/foo/favicon.ico"); |
||||
assertMatcher(matcher).doesNotMatch("/bar"); |
||||
} |
||||
|
||||
@Test |
||||
public void toCommonLocationsWithExcludeShouldNotMatchExcluded() { |
||||
ServerWebExchangeMatcher matcher = StaticResourceRequest.toCommonLocations() |
||||
.excluding(StaticResourceLocation.CSS); |
||||
assertMatcher(matcher).doesNotMatch("/css/file.css"); |
||||
assertMatcher(matcher).matches("/js/file.js"); |
||||
} |
||||
|
||||
@Test |
||||
public void toLocationShouldMatchLocation() { |
||||
ServerWebExchangeMatcher matcher = StaticResourceRequest.to(StaticResourceLocation.CSS); |
||||
assertMatcher(matcher).matches("/css/file.css"); |
||||
assertMatcher(matcher).doesNotMatch("/js/file.js"); |
||||
} |
||||
|
||||
@Test |
||||
public void toLocationsFromSetWhenSetIsNullShouldThrowException() { |
||||
this.thrown.expect(IllegalArgumentException.class); |
||||
this.thrown.expectMessage("Locations must not be null"); |
||||
StaticResourceRequest.to(null); |
||||
} |
||||
|
||||
@Test |
||||
public void excludeFromSetWhenSetIsNullShouldThrowException() { |
||||
this.thrown.expect(IllegalArgumentException.class); |
||||
this.thrown.expectMessage("Locations must not be null"); |
||||
StaticResourceRequest.toCommonLocations().excluding(null); |
||||
} |
||||
|
||||
private StaticResourceRequestTests.RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher) { |
||||
StaticWebApplicationContext context = new StaticWebApplicationContext(); |
||||
context.registerBean(ServerProperties.class); |
||||
return assertThat(new StaticResourceRequestTests.RequestMatcherAssert(context, matcher)); |
||||
} |
||||
|
||||
private static class RequestMatcherAssert implements AssertDelegateTarget { |
||||
|
||||
private final StaticApplicationContext context; |
||||
|
||||
private final ServerWebExchangeMatcher matcher; |
||||
|
||||
RequestMatcherAssert(StaticApplicationContext context, ServerWebExchangeMatcher matcher) { |
||||
this.context = context; |
||||
this.matcher = matcher; |
||||
} |
||||
|
||||
void matches(String path) { |
||||
ServerWebExchange exchange = webHandler().createExchange(MockServerHttpRequest.get(path).build(), new MockServerHttpResponse()); |
||||
matches(exchange); |
||||
} |
||||
|
||||
private void matches(ServerWebExchange exchange) { |
||||
assertThat(this.matcher.matches(exchange).block().isMatch()) |
||||
.as("Matches " + getRequestPath(exchange)).isTrue(); |
||||
} |
||||
|
||||
void doesNotMatch(String path) { |
||||
ServerWebExchange exchange = webHandler().createExchange(MockServerHttpRequest.get(path).build(), new MockServerHttpResponse()); |
||||
doesNotMatch(exchange); |
||||
} |
||||
|
||||
private void doesNotMatch(ServerWebExchange exchange) { |
||||
assertThat(this.matcher.matches(exchange).block().isMatch()) |
||||
.as("Does not match " + getRequestPath(exchange)).isFalse(); |
||||
} |
||||
|
||||
private TestHttpWebHandlerAdapter webHandler() { |
||||
TestHttpWebHandlerAdapter adapter = new TestHttpWebHandlerAdapter(mock(WebHandler.class)); |
||||
adapter.setApplicationContext(this.context); |
||||
return adapter; |
||||
} |
||||
|
||||
private String getRequestPath(ServerWebExchange exchange) { |
||||
return exchange.getRequest().getPath().toString(); |
||||
} |
||||
|
||||
} |
||||
|
||||
private static class TestHttpWebHandlerAdapter extends HttpWebHandlerAdapter { |
||||
|
||||
TestHttpWebHandlerAdapter(WebHandler delegate) { |
||||
super(delegate); |
||||
} |
||||
|
||||
@Override |
||||
protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttpResponse response) { |
||||
return super.createExchange(request, response); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue