Reactive Resource Server Csrf Bypass
This makes requests identified as bearer token requests skip the csrf filter. Fixes: gh-5710
This commit is contained in:
parent
820fb7d828
commit
68d836d508
|
@ -43,6 +43,7 @@ import org.springframework.security.authorization.AuthenticatedReactiveAuthoriza
|
||||||
import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager;
|
import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
|
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
|
||||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||||
|
@ -114,7 +115,9 @@ import org.springframework.security.web.server.savedrequest.ServerRequestCacheWe
|
||||||
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
|
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
|
||||||
import org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter;
|
import org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter;
|
||||||
import org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter;
|
import org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter;
|
||||||
|
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
|
||||||
import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher;
|
import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher;
|
||||||
|
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
|
||||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
|
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
|
||||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcherEntry;
|
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcherEntry;
|
||||||
|
@ -130,6 +133,8 @@ import org.springframework.web.server.WebFilter;
|
||||||
import org.springframework.web.server.WebFilterChain;
|
import org.springframework.web.server.WebFilterChain;
|
||||||
|
|
||||||
import static org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry;
|
import static org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry;
|
||||||
|
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.match;
|
||||||
|
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.notMatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ServerHttpSecurity} is similar to Spring Security's {@code HttpSecurity} but for WebFlux.
|
* A {@link ServerHttpSecurity} is similar to Spring Security's {@code HttpSecurity} but for WebFlux.
|
||||||
|
@ -703,6 +708,9 @@ public class ServerHttpSecurity {
|
||||||
public class JwtSpec {
|
public class JwtSpec {
|
||||||
private ReactiveJwtDecoder jwtDecoder;
|
private ReactiveJwtDecoder jwtDecoder;
|
||||||
|
|
||||||
|
private BearerTokenServerWebExchangeMatcher bearerTokenServerWebExchangeMatcher =
|
||||||
|
new BearerTokenServerWebExchangeMatcher();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the {@link ReactiveJwtDecoder} to use
|
* Configures the {@link ReactiveJwtDecoder} to use
|
||||||
* @param jwtDecoder the decoder to use
|
* @param jwtDecoder the decoder to use
|
||||||
|
@ -740,13 +748,20 @@ public class ServerHttpSecurity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void configure(ServerHttpSecurity http) {
|
protected void configure(ServerHttpSecurity http) {
|
||||||
|
ServerBearerTokenAuthenticationConverter bearerTokenConverter =
|
||||||
|
new ServerBearerTokenAuthenticationConverter();
|
||||||
|
this.bearerTokenServerWebExchangeMatcher.setBearerTokenConverter(bearerTokenConverter);
|
||||||
|
|
||||||
|
registerDefaultCsrfOverride(http);
|
||||||
|
|
||||||
BearerTokenServerAuthenticationEntryPoint entryPoint = new BearerTokenServerAuthenticationEntryPoint();
|
BearerTokenServerAuthenticationEntryPoint entryPoint = new BearerTokenServerAuthenticationEntryPoint();
|
||||||
ReactiveJwtDecoder jwtDecoder = getJwtDecoder();
|
ReactiveJwtDecoder jwtDecoder = getJwtDecoder();
|
||||||
JwtReactiveAuthenticationManager authenticationManager = new JwtReactiveAuthenticationManager(
|
JwtReactiveAuthenticationManager authenticationManager = new JwtReactiveAuthenticationManager(
|
||||||
jwtDecoder);
|
jwtDecoder);
|
||||||
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
|
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
|
||||||
oauth2.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());
|
oauth2.setServerAuthenticationConverter(bearerTokenConverter);
|
||||||
oauth2.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint));
|
oauth2.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint));
|
||||||
|
|
||||||
http
|
http
|
||||||
.exceptionHandling()
|
.exceptionHandling()
|
||||||
.authenticationEntryPoint(entryPoint)
|
.authenticationEntryPoint(entryPoint)
|
||||||
|
@ -760,6 +775,38 @@ public class ServerHttpSecurity {
|
||||||
}
|
}
|
||||||
return this.jwtDecoder;
|
return this.jwtDecoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerDefaultCsrfOverride(ServerHttpSecurity http) {
|
||||||
|
if ( http.csrf != null && !http.csrf.specifiedRequireCsrfProtectionMatcher ) {
|
||||||
|
http
|
||||||
|
.csrf()
|
||||||
|
.requireCsrfProtectionMatcher(
|
||||||
|
new AndServerWebExchangeMatcher(
|
||||||
|
CsrfWebFilter.DEFAULT_CSRF_MATCHER,
|
||||||
|
new NegatedServerWebExchangeMatcher(
|
||||||
|
this.bearerTokenServerWebExchangeMatcher)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BearerTokenServerWebExchangeMatcher implements ServerWebExchangeMatcher {
|
||||||
|
ServerBearerTokenAuthenticationConverter bearerTokenConverter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<MatchResult> matches(ServerWebExchange exchange) {
|
||||||
|
return this.bearerTokenConverter.convert(exchange)
|
||||||
|
.flatMap(this::nullAuthentication)
|
||||||
|
.onErrorResume(e -> notMatch());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBearerTokenConverter(ServerBearerTokenAuthenticationConverter bearerTokenConverter) {
|
||||||
|
Assert.notNull(bearerTokenConverter, "bearerTokenConverter cannot be null");
|
||||||
|
this.bearerTokenConverter = bearerTokenConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<MatchResult> nullAuthentication(Authentication authentication) {
|
||||||
|
return authentication == null ? notMatch() : match();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerHttpSecurity and() {
|
public ServerHttpSecurity and() {
|
||||||
|
@ -1173,6 +1220,8 @@ public class ServerHttpSecurity {
|
||||||
public class CsrfSpec {
|
public class CsrfSpec {
|
||||||
private CsrfWebFilter filter = new CsrfWebFilter();
|
private CsrfWebFilter filter = new CsrfWebFilter();
|
||||||
|
|
||||||
|
private boolean specifiedRequireCsrfProtectionMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the {@link ServerAccessDeniedHandler} used when a CSRF token is invalid. Default is
|
* Configures the {@link ServerAccessDeniedHandler} used when a CSRF token is invalid. Default is
|
||||||
* to send an {@link org.springframework.http.HttpStatus#FORBIDDEN}.
|
* to send an {@link org.springframework.http.HttpStatus#FORBIDDEN}.
|
||||||
|
@ -1209,6 +1258,7 @@ public class ServerHttpSecurity {
|
||||||
public CsrfSpec requireCsrfProtectionMatcher(
|
public CsrfSpec requireCsrfProtectionMatcher(
|
||||||
ServerWebExchangeMatcher requireCsrfProtectionMatcher) {
|
ServerWebExchangeMatcher requireCsrfProtectionMatcher) {
|
||||||
this.filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
|
this.filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
|
||||||
|
this.specifiedRequireCsrfProtectionMatcher = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||||
import org.springframework.web.reactive.DispatcherHandler;
|
import org.springframework.web.reactive.DispatcherHandler;
|
||||||
|
@ -160,6 +161,25 @@ public class OAuth2ResourceServerSpecTests {
|
||||||
.expectStatus().isOk();
|
.expectStatus().isOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void postWhenSignedThenReturnsOk() {
|
||||||
|
this.spring.register(PublicKeyConfig.class, RootController.class).autowire();
|
||||||
|
|
||||||
|
this.client.post()
|
||||||
|
.headers(headers -> headers.setBearerAuth(this.messageReadToken))
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void postWhenMissingTokenThenReturnsForbidden() {
|
||||||
|
this.spring.register(PublicKeyConfig.class, RootController.class).autowire();
|
||||||
|
|
||||||
|
this.client.post()
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getJwtDecoderWhenBeanWiredAndDslWiredThenDslTakesPrecedence() {
|
public void getJwtDecoderWhenBeanWiredAndDslWiredThenDslTakesPrecedence() {
|
||||||
GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext();
|
GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext();
|
||||||
|
@ -301,7 +321,12 @@ public class OAuth2ResourceServerSpecTests {
|
||||||
@RestController
|
@RestController
|
||||||
static class RootController {
|
static class RootController {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
Mono<String> root() {
|
Mono<String> get() {
|
||||||
|
return Mono.just("ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
Mono<String> post() {
|
||||||
return Mono.just("ok");
|
return Mono.just("ok");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue