PermitAllSupport supports AuthorizeHttpRequestsConfigurer

PermitAllSupport supports either an ExpressionUrlAuthorizationConfigurer or an AuthorizeHttpRequestsConfigurer. If none or both are configured an error message is thrown.

Closes gh-10482
This commit is contained in:
Igor Pelesic 2021-11-18 12:48:43 +01:00 committed by Josh Cummings
parent f838b7cb1d
commit a3a9de1b9b
5 changed files with 157 additions and 10 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@ -16,6 +16,7 @@
package org.springframework.security.config.annotation.web.configurers;
import java.util.LinkedHashMap;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@ -46,6 +47,9 @@ import org.springframework.util.Assert;
public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<AuthorizeHttpRequestsConfigurer<H>, H> {
static final AuthorizationManager<RequestAuthorizationContext> permitAllAuthorizationManager = (a,
o) -> new AuthorizationDecision(true);
private final AuthorizationManagerRequestMatcherRegistry registry;
/**
@ -81,6 +85,12 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
return this.registry;
}
AuthorizationManagerRequestMatcherRegistry addFirst(RequestMatcher matcher,
AuthorizationManager<RequestAuthorizationContext> manager) {
this.registry.addFirst(matcher, manager);
return this.registry;
}
/**
* Registry for mapping a {@link RequestMatcher} to an {@link AuthorizationManager}.
*
@ -106,6 +116,19 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
this.mappingCount++;
}
private void addFirst(RequestMatcher matcher, AuthorizationManager<RequestAuthorizationContext> manager) {
this.unmappedMatchers = null;
this.managerBuilder.mappings((m) -> {
LinkedHashMap<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> reorderedMap = new LinkedHashMap<>(
m.size() + 1);
reorderedMap.put(matcher, manager);
reorderedMap.putAll(m);
m.clear();
m.putAll(reorderedMap);
});
this.mappingCount++;
}
private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
Assert.state(this.unmappedMatchers == null,
() -> "An incomplete mapping was found for " + this.unmappedMatchers
@ -209,7 +232,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry permitAll() {
return access((a, o) -> new AuthorizationDecision(true));
return access(permitAllAuthorizationManager);
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2021 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.
@ -48,12 +48,23 @@ final class PermitAllSupport {
RequestMatcher... requestMatchers) {
ExpressionUrlAuthorizationConfigurer<?> configurer = http
.getConfigurer(ExpressionUrlAuthorizationConfigurer.class);
Assert.state(configurer != null, "permitAll only works with HttpSecurity.authorizeRequests()");
AuthorizeHttpRequestsConfigurer<?> httpConfigurer = http.getConfigurer(AuthorizeHttpRequestsConfigurer.class);
boolean oneConfigurerPresent = configurer == null ^ httpConfigurer == null;
Assert.state(oneConfigurerPresent,
"permitAll only works with either HttpSecurity.authorizeRequests() or HttpSecurity.authorizeHttpRequests(). "
+ "Please define one or the other but not both.");
for (RequestMatcher matcher : requestMatchers) {
if (matcher != null) {
if (configurer != null) {
configurer.getRegistry().addMapping(0, new UrlMapping(matcher,
SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
}
else {
httpConfigurer.addFirst(matcher, AuthorizeHttpRequestsConfigurer.permitAllAuthorizationManager);
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@ -61,11 +61,32 @@ public class PermitAllSupportTests {
this.mvc.perform(getWithCsrf).andExpect(status().isFound());
}
@Test
public void performWhenUsingPermitAllExactUrlRequestMatcherThenMatchesExactUrlWithAuthorizeHttp() throws Exception {
this.spring.register(PermitAllConfigAuthorizeHttpRequests.class).autowire();
MockHttpServletRequestBuilder request = get("/app/xyz").contextPath("/app");
this.mvc.perform(request).andExpect(status().isNotFound());
MockHttpServletRequestBuilder getWithQuery = get("/app/xyz?def").contextPath("/app");
this.mvc.perform(getWithQuery).andExpect(status().isFound());
MockHttpServletRequestBuilder postWithQueryAndCsrf = post("/app/abc?def").with(csrf()).contextPath("/app");
this.mvc.perform(postWithQueryAndCsrf).andExpect(status().isNotFound());
MockHttpServletRequestBuilder getWithCsrf = get("/app/abc").with(csrf()).contextPath("/app");
this.mvc.perform(getWithCsrf).andExpect(status().isFound());
}
@Test
public void configureWhenNotAuthorizeRequestsThenException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(NoAuthorizedUrlsConfig.class).autowire())
.withMessageContaining("permitAll only works with HttpSecurity.authorizeRequests");
.isThrownBy(() -> this.spring.register(NoAuthorizedUrlsConfig.class).autowire()).withMessageContaining(
"permitAll only works with either HttpSecurity.authorizeRequests() or HttpSecurity.authorizeHttpRequests()");
}
@Test
public void configureWhenBothAuthorizeRequestsAndAuthorizeHttpRequestsThenException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(PermitAllConfigWithBothConfigs.class).autowire())
.withMessageContaining(
"permitAll only works with either HttpSecurity.authorizeRequests() or HttpSecurity.authorizeHttpRequests()");
}
@EnableWebSecurity
@ -86,6 +107,45 @@ public class PermitAllSupportTests {
}
@EnableWebSecurity
static class PermitAllConfigAuthorizeHttpRequests extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/xyz").permitAll()
.loginProcessingUrl("/abc?def").permitAll();
// @formatter:on
}
}
@EnableWebSecurity
static class PermitAllConfigWithBothConfigs extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/xyz").permitAll()
.loginProcessingUrl("/abc?def").permitAll();
// @formatter:on
}
}
@EnableWebSecurity
static class NoAuthorizedUrlsConfig extends WebSecurityConfigurerAdapter {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@ -18,6 +18,7 @@ package org.springframework.security.web.access.intercept;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletRequest;
@ -112,6 +113,20 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho
return this;
}
/**
* Allows to configure the {@link RequestMatcher} to {@link AuthorizationManager}
* mappings.
* @param mappingsConsumer used to configure the {@link RequestMatcher} to
* {@link AuthorizationManager} mappings.
* @return the {@link Builder} for further customizations
*/
public Builder mappings(
Consumer<Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>>> mappingsConsumer) {
Assert.notNull(mappingsConsumer, "mappingsConsumer cannot be null");
mappingsConsumer.accept(this.mappings);
return this;
}
/**
* Creates a {@link RequestMatcherDelegatingAuthorizationManager} instance.
* @return the {@link RequestMatcherDelegatingAuthorizationManager} instance

View File

@ -22,9 +22,11 @@ import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@ -83,4 +85,40 @@ public class RequestMatcherDelegatingAuthorizationManagerTests {
assertThat(abstain).isNull();
}
@Test
public void checkWhenMultipleMappingsConfiguredWithConsumerThenDelegatesMatchingManager() {
RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder()
.mappings((m) -> {
m.put(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true));
m.put(AnyRequestMatcher.INSTANCE, AuthorityAuthorizationManager.hasRole("ADMIN"));
m.put(new MvcRequestMatcher(null, "/deny"), (a, o) -> new AuthorizationDecision(false));
m.put(new MvcRequestMatcher(null, "/afterAny"), (a, o) -> new AuthorizationDecision(true));
}).build();
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/grant"));
assertThat(grant).isNotNull();
assertThat(grant.isGranted()).isTrue();
AuthorizationDecision deny = manager.check(authentication, new MockHttpServletRequest(null, "/deny"));
assertThat(deny).isNotNull();
assertThat(deny.isGranted()).isFalse();
AuthorizationDecision afterAny = manager.check(authentication, new MockHttpServletRequest(null, "/afterAny"));
assertThat(afterAny).isNotNull();
assertThat(afterAny.isGranted()).isFalse();
AuthorizationDecision unmapped = manager.check(authentication, new MockHttpServletRequest(null, "/unmapped"));
assertThat(unmapped).isNotNull();
assertThat(unmapped.isGranted()).isFalse();
}
@Test
public void addWhenMappingsConsumerNullThenException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RequestMatcherDelegatingAuthorizationManager.builder().mappings(null).build())
.withMessage("mappingsConsumer cannot be null");
}
}