Add AuthenticationReactorContextFilter

Fixes gh-4501
This commit is contained in:
Rob Winch 2017-08-14 15:55:29 -05:00
parent e16b8e7976
commit b0b9b32c0c
4 changed files with 202 additions and 1 deletions

View File

@ -29,6 +29,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.security.web.server.authorization.AuthorizationWebFilter;
import org.springframework.security.web.server.authorization.DelegatingReactiveAuthorizationManager;
import org.springframework.security.web.server.context.AuthenticationReactorContextFilter;
import org.springframework.security.web.server.context.SecurityContextRepositoryWebFilter;
import org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter;
import org.springframework.security.web.server.context.SecurityContextRepository;
@ -114,6 +115,7 @@ public class HttpSecurity {
securityContextRepository.ifPresent( scr -> httpBasic.securityContextRepository(scr)) ;
filters.add(httpBasic.build());
}
filters.add(new AuthenticationReactorContextFilter());
if(authorizeExchangeBuilder != null) {
filters.add(new ExceptionTranslationWebFilter());
filters.add(authorizeExchangeBuilder.build());

View File

@ -25,7 +25,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.web.server.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.MapUserDetailsRepository;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsRepository;
@ -35,8 +39,14 @@ import org.springframework.security.web.server.WebFilterChainFilter;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import static org.mockito.Mockito.mock;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
/**
* @author Rob Winch
@ -60,6 +70,50 @@ public class EnableWebFluxSecurityTests {
.expectBody().isEmpty();
}
@Test
public void defaultPopulatesReactorContext() {
Principal currentPrincipal = new TestingAuthenticationToken("user", "password", "ROLE_USER");
WebTestClient client = WebTestClientBuilder.bindToWebFilters(
(exchange, chain) ->
chain.filter(exchange.mutate().principal(Mono.just(currentPrincipal)).build()),
springSecurityFilterChain,
(exchange,chain) ->
Mono.currentContext()
.flatMap( c -> c.<Mono<Principal>>get(Authentication.class))
.flatMap( principal -> exchange.getResponse()
.writeWith(Mono.just(toDataBuffer(principal.getName()))))
).build();
client
.get()
.uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).consumeWith( result -> assertThat(result.getResponseBody()).isEqualTo(currentPrincipal.getName()));
}
@Test
public void defaultPopulatesReactorContextWhenAuthenticating() {
WebTestClient client = WebTestClientBuilder.bindToWebFilters(
springSecurityFilterChain,
(exchange,chain) ->
Mono.currentContext()
.flatMap( c -> c.<Mono<Principal>>get(Authentication.class))
.flatMap( principal -> exchange.getResponse()
.writeWith(Mono.just(toDataBuffer(principal.getName()))))
)
.filter(basicAuthentication())
.build();
client
.get()
.uri("/")
.attributes(basicAuthenticationCredentials("user","password"))
.exchange()
.expectStatus().isOk()
.expectBody(String.class).consumeWith( result -> assertThat(result.getResponseBody()).isEqualTo("user"));
}
@EnableWebFluxSecurity
static class Config {
@Bean
@ -121,4 +175,10 @@ public class EnableWebFluxSecurityTests {
}
}
}
private static DataBuffer toDataBuffer(String body) {
DataBuffer buffer = new DefaultDataBufferFactory().allocateBuffer();
buffer.write(body.getBytes(StandardCharsets.UTF_8));
return buffer;
}
}

View File

@ -0,0 +1,45 @@
/*
*
* * 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.context;
import org.springframework.security.core.Authentication;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import java.security.Principal;
/**
* Populate the {@link Principal} from {@link ServerWebExchange#getPrincipal()} into the
* Reactor {@link Context}.
*
* @author Rob Winch
* @since 5.0
*/
public class AuthenticationReactorContextFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.contextStart((Context context) -> context.put(Authentication.class, exchange.getPrincipal()));
}
}

View File

@ -0,0 +1,94 @@
/*
*
* * 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.context;
import org.junit.Test;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.handler.DefaultWebFilterChain;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.util.context.Context;
import java.security.Principal;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 5.0
*/
public class AuthenticationReactorContextFilterTests {
AuthenticationReactorContextFilter filter = new AuthenticationReactorContextFilter();
Principal principal = new TestingAuthenticationToken("user","password", "ROLE_USER");
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
@Test
public void filterWhenExistingContextAndPrincipalNotNullThenContextPopulated() {
exchange = exchange.mutate().principal(Mono.just(principal)).build();
StepVerifier.create(filter.filter(exchange,
new DefaultWebFilterChain( e ->
Mono.currentContext().doOnSuccess( context -> {
Principal contextPrincipal = context.<Mono<Principal>>get(Authentication.class).block();
assertThat(contextPrincipal).isEqualTo(principal);
assertThat(context.<String>get("foo")).isEqualTo("bar");
})
.then()
)
)
.contextStart( context -> context.put("foo", "bar")))
.verifyComplete();
}
@Test
public void filterWhenPrincipalNotNullThenContextPopulated() {
exchange = exchange.mutate().principal(Mono.just(principal)).build();
StepVerifier.create(filter.filter(exchange,
new DefaultWebFilterChain( e ->
Mono.currentContext().doOnSuccess( context -> {
Principal contextPrincipal = context.<Mono<Principal>>get(Authentication.class).block();
assertThat(contextPrincipal).isEqualTo(principal);
})
.then()
)
))
.verifyComplete();
}
@Test
public void filterWhenPrincipalNullThenContextEmpty() {
Context defaultContext = Context.empty();
StepVerifier.create(filter.filter(exchange,
new DefaultWebFilterChain( e ->
Mono.currentContext()
.defaultIfEmpty(defaultContext)
.doOnSuccess( context -> {
Principal contextPrincipal = context.<Mono<Principal>>get(Authentication.class).block();
assertThat(contextPrincipal).isNull();
})
.then()
)
))
.verifyComplete();
}
}