Skip to content

Commit

Permalink
PermitAllSupport supports AuthorizeHttpRequestsConfigurer
Browse files Browse the repository at this point in the history
PermitAllSupport supports either an ExpressionUrlAuthorizationConfigurer or an AuthorizeHttpRequestsConfigurer. If none or both are configured an error message is thrown.

Closes gh-10482
  • Loading branch information
igorpele committed Nov 30, 2021
1 parent 74e3abc commit c81a047
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -16,6 +16,7 @@

package org.springframework.security.config.annotation.web.configurers;

import java.util.LinkedHashMap;
import java.util.List;

import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -46,6 +47,9 @@
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;

/**
Expand Down Expand Up @@ -81,6 +85,12 @@ private AuthorizationManagerRequestMatcherRegistry addMapping(List<? extends Req
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}.
*
Expand All @@ -106,6 +116,19 @@ private void addMapping(RequestMatcher matcher, AuthorizationManager<RequestAuth
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
Expand Down Expand Up @@ -209,7 +232,7 @@ protected List<? extends RequestMatcher> getMatchers() {
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry permitAll() {
return access((a, o) -> new AuthorizationDecision(true));
return access(permitAllAuthorizationManager);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -48,11 +48,22 @@ static void permitAll(HttpSecurityBuilder<? extends HttpSecurityBuilder<?>> http
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) {
configurer.getRegistry().addMapping(0, new UrlMapping(matcher,
SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
if (configurer != null) {
configurer.getRegistry().addMapping(0, new UrlMapping(matcher,
SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
}
else {
httpConfigurer.addFirst(matcher, AuthorizeHttpRequestsConfigurer.permitAllAuthorizationManager);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -61,11 +61,32 @@ public void performWhenUsingPermitAllExactUrlRequestMatcherThenMatchesExactUrl()
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
Expand All @@ -86,6 +107,45 @@ protected void configure(HttpSecurity http) throws Exception {

}

@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 {

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -18,6 +18,7 @@

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -112,6 +113,20 @@ public Builder add(RequestMatcher matcher, AuthorizationManager<RequestAuthoriza
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@

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;
Expand Down Expand Up @@ -83,4 +85,40 @@ public void checkWhenMultipleMappingsConfiguredThenDelegatesMatchingManager() {
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");
}

}

0 comments on commit c81a047

Please sign in to comment.