Skip to content

Commit 11589cd

Browse files
committed
Add Not Support
Closes gh-14058
1 parent e49ae09 commit 11589cd

File tree

4 files changed

+100
-1
lines changed

4 files changed

+100
-1
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.security.authorization.AuthorizationDecision;
3131
import org.springframework.security.authorization.AuthorizationEventPublisher;
3232
import org.springframework.security.authorization.AuthorizationManager;
33+
import org.springframework.security.authorization.AuthorizationManagers;
3334
import org.springframework.security.authorization.ObservationAuthorizationManager;
3435
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
3536
import org.springframework.security.config.annotation.ObjectPostProcessor;
@@ -244,11 +245,14 @@ public H and() {
244245
* {@link RequestMatcher}s.
245246
*
246247
* @author Evgeniy Cheban
248+
* @author Josh Cummings
247249
*/
248250
public class AuthorizedUrl {
249251

250252
private final List<? extends RequestMatcher> matchers;
251253

254+
private boolean not;
255+
252256
/**
253257
* Creates an instance.
254258
* @param matchers the {@link RequestMatcher} instances to map
@@ -261,6 +265,16 @@ protected List<? extends RequestMatcher> getMatchers() {
261265
return this.matchers;
262266
}
263267

268+
/**
269+
* Negates the following authorization rule.
270+
* @return the {@link AuthorizedUrl} for further customization
271+
* @since 6.3
272+
*/
273+
public AuthorizedUrl not() {
274+
this.not = true;
275+
return this;
276+
}
277+
264278
/**
265279
* Specify that URLs are allowed by anyone.
266280
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
@@ -382,7 +396,9 @@ public AuthorizationManagerRequestMatcherRegistry anonymous() {
382396
public AuthorizationManagerRequestMatcherRegistry access(
383397
AuthorizationManager<RequestAuthorizationContext> manager) {
384398
Assert.notNull(manager, "manager cannot be null");
385-
return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
399+
return (this.not)
400+
? AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, AuthorizationManagers.not(manager))
401+
: AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
386402
}
387403

388404
}

config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,20 @@ public void getWhenAnonymousConfiguredAndLoggedInUserThenRespondsWithForbidden()
596596
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
597597
}
598598

599+
@Test
600+
public void getWhenNotConfigAndAuthenticatedThenRespondsWithForbidden() throws Exception {
601+
this.spring.register(NotConfig.class, BasicController.class).autowire();
602+
MockHttpServletRequestBuilder requestWithUser = get("/").with(user("user"));
603+
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
604+
}
605+
606+
@Test
607+
public void getWhenNotConfigAndNotAuthenticatedThenRespondsWithOk() throws Exception {
608+
this.spring.register(NotConfig.class, BasicController.class).autowire();
609+
MockHttpServletRequestBuilder requestWithUser = get("/");
610+
this.mvc.perform(requestWithUser).andExpect(status().isOk());
611+
}
612+
599613
@Configuration
600614
@EnableWebSecurity
601615
static class GrantedAuthorityDefaultHasRoleConfig {
@@ -1136,6 +1150,24 @@ SecurityFilterChain chain(HttpSecurity http) throws Exception {
11361150

11371151
}
11381152

1153+
@Configuration
1154+
@EnableWebSecurity
1155+
static class NotConfig {
1156+
1157+
@Bean
1158+
SecurityFilterChain chain(HttpSecurity http) throws Exception {
1159+
// @formatter:off
1160+
http
1161+
.httpBasic(withDefaults())
1162+
.authorizeHttpRequests((requests) -> requests
1163+
.anyRequest().not().authenticated()
1164+
);
1165+
// @formatter:on
1166+
return http.build();
1167+
}
1168+
1169+
}
1170+
11391171
@Configuration
11401172
static class AuthorizationEventPublisherConfig {
11411173

core/src/main/java/org/springframework/security/authorization/AuthorizationManagers.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* A factory class to create an {@link AuthorizationManager} instances.
2424
*
2525
* @author Evgeniy Cheban
26+
* @author Josh Cummings
2627
* @since 5.8
2728
*/
2829
public final class AuthorizationManagers {
@@ -119,6 +120,25 @@ public static <T> AuthorizationManager<T> allOf(AuthorizationDecision allAbstain
119120
};
120121
}
121122

123+
/**
124+
* Creates an {@link AuthorizationManager} that reverses whatever decision the given
125+
* {@link AuthorizationManager} granted. If the given {@link AuthorizationManager}
126+
* abstains, then the returned manager also abstains.
127+
* @param <T> the type of object that is being authorized
128+
* @param manager the {@link AuthorizationManager} to reverse
129+
* @return the reversing {@link AuthorizationManager}
130+
* @since 6.3
131+
*/
132+
public static <T> AuthorizationManager<T> not(AuthorizationManager<T> manager) {
133+
return (authentication, object) -> {
134+
AuthorizationDecision decision = manager.check(authentication, object);
135+
if (decision == null) {
136+
return null;
137+
}
138+
return new NotAuthorizationDecision(decision);
139+
};
140+
}
141+
122142
private AuthorizationManagers() {
123143
}
124144

@@ -138,4 +158,20 @@ public String toString() {
138158

139159
}
140160

161+
private static final class NotAuthorizationDecision extends AuthorizationDecision {
162+
163+
private final AuthorizationDecision decision;
164+
165+
private NotAuthorizationDecision(AuthorizationDecision decision) {
166+
super(!decision.isGranted());
167+
this.decision = decision;
168+
}
169+
170+
@Override
171+
public String toString() {
172+
return "NotAuthorizationDecision [decision=" + this.decision + ']';
173+
}
174+
175+
}
176+
141177
}

core/src/test/java/org/springframework/security/authorization/AuthorizationManagersTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,19 @@ void checkAllOfWhenAllAbstainDefaultDecisionIsAbstainAndAllManagersAbstainThenAb
224224
assertThat(decision).isNull();
225225
}
226226

227+
@Test
228+
void checkNotWhenEmptyThenAbstainedDecision() {
229+
AuthorizationManager<?> negated = AuthorizationManagers.not((a, o) -> null);
230+
AuthorizationDecision decision = negated.check(null, null);
231+
assertThat(decision).isNull();
232+
}
233+
234+
@Test
235+
void checkNotWhenGrantedThenDeniedDecision() {
236+
AuthorizationManager<?> negated = AuthorizationManagers.not((a, o) -> new AuthorizationDecision(true));
237+
AuthorizationDecision decision = negated.check(null, null);
238+
assertThat(decision).isNotNull();
239+
assertThat(decision.isGranted()).isFalse();
240+
}
241+
227242
}

0 commit comments

Comments
 (0)