Skip to content

Commit ec02c22

Browse files
kth496jzheaux
authored andcommitted
Add Request Path Extraction Support
Closes gh-13256
1 parent b82bd47 commit ec02c22

File tree

3 files changed

+74
-3
lines changed

3 files changed

+74
-3
lines changed

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.security.config.annotation.web.configurers;
1818

1919
import java.util.List;
20+
import java.util.function.Function;
2021
import java.util.function.Supplier;
2122

2223
import io.micrometer.observation.ObservationRegistry;
@@ -37,6 +38,7 @@
3738
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
3839
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
3940
import org.springframework.security.config.core.GrantedAuthorityDefaults;
41+
import org.springframework.security.core.Authentication;
4042
import org.springframework.security.web.access.intercept.AuthorizationFilter;
4143
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
4244
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
@@ -387,6 +389,21 @@ public AuthorizationManagerRequestMatcherRegistry anonymous() {
387389
return access(AuthenticatedAuthorizationManager.anonymous());
388390
}
389391

392+
/**
393+
* Specify that a path variable in URL to be compared.
394+
*
395+
* <p>
396+
* For example, <pre>
397+
* requestMatchers("/user/{username}").hasVariable("username").equalTo(Authentication::getName)
398+
* </pre>
399+
* @param variable the variable in URL template to compare.
400+
* @return {@link AuthorizedUrlVariable} for further customization.
401+
* @since 6.3
402+
*/
403+
public AuthorizedUrlVariable hasVariable(String variable) {
404+
return new AuthorizedUrlVariable(variable);
405+
}
406+
390407
/**
391408
* Allows specifying a custom {@link AuthorizationManager}.
392409
* @param manager the {@link AuthorizationManager} to use
@@ -401,6 +418,41 @@ public AuthorizationManagerRequestMatcherRegistry access(
401418
: AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
402419
}
403420

421+
/**
422+
* An object that allows configuring {@link RequestMatcher}s with URI path
423+
* variables
424+
*
425+
* @author Taehong Kim
426+
* @since 6.3
427+
*/
428+
public final class AuthorizedUrlVariable {
429+
430+
private final String variable;
431+
432+
private AuthorizedUrlVariable(String variable) {
433+
this.variable = variable;
434+
}
435+
436+
/**
437+
* Compares the value of a path variable in the URI with an `Authentication`
438+
* attribute
439+
* <p>
440+
* For example, <pre>
441+
* requestMatchers("/user/{username}").hasVariable("username").equalTo(Authentication::getName));
442+
* </pre>
443+
* @param function a function to get value from {@link Authentication}.
444+
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
445+
* customization.
446+
*/
447+
public AuthorizationManagerRequestMatcherRegistry equalTo(Function<Authentication, String> function) {
448+
return access((auth, requestContext) -> {
449+
String value = requestContext.getVariables().get(this.variable);
450+
return new AuthorizationDecision(function.apply(auth.get()).equals(value));
451+
});
452+
}
453+
454+
}
455+
404456
}
405457

406458
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@
4040
import org.springframework.security.config.core.GrantedAuthorityDefaults;
4141
import org.springframework.security.config.test.SpringTestContext;
4242
import org.springframework.security.config.test.SpringTestContextExtension;
43+
import org.springframework.security.core.Authentication;
4344
import org.springframework.security.core.authority.AuthorityUtils;
4445
import org.springframework.security.core.authority.SimpleGrantedAuthority;
46+
import org.springframework.security.core.userdetails.UserDetails;
4547
import org.springframework.security.core.userdetails.UserDetailsService;
4648
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
4749
import org.springframework.security.web.SecurityFilterChain;
@@ -543,6 +545,17 @@ public void requestWhenMvcMatcherPathVariablesThenMatchesOnPathVariables() throw
543545
this.mvc.perform(request).andExpect(status().isOk());
544546
request = get("/user/deny");
545547
this.mvc.perform(request).andExpect(status().isUnauthorized());
548+
549+
UserDetails user = TestAuthentication.withUsername("taehong").build();
550+
Authentication authentication = TestAuthentication.authenticated(user);
551+
request = get("/v2/user/{username}", user.getUsername()).with(authentication(authentication));
552+
this.mvc.perform(request).andExpect(status().isOk());
553+
554+
request = get("/v2/user/{username}", "withNoAuthentication");
555+
this.mvc.perform(request).andExpect(status().isUnauthorized());
556+
557+
request = get("/v2/user/{username}", "another").with(authentication(authentication));
558+
this.mvc.perform(request).andExpect(status().isForbidden());
546559
}
547560

548561
private static RequestPostProcessor remoteAddress(String remoteAddress) {
@@ -1067,6 +1080,7 @@ SecurityFilterChain chain(HttpSecurity http) throws Exception {
10671080
.httpBasic(withDefaults())
10681081
.authorizeHttpRequests((requests) -> requests
10691082
.requestMatchers("/user/{username}").access(new WebExpressionAuthorizationManager("#username == 'user'"))
1083+
.requestMatchers("/v2/user/{username}").hasVariable("username").equalTo(Authentication::getName)
10701084
);
10711085
// @formatter:on
10721086
return http.build();
@@ -1080,6 +1094,11 @@ String path(@PathVariable("username") String username) {
10801094
return username;
10811095
}
10821096

1097+
@RequestMapping("/v2/user/{username}")
1098+
String pathV2(@PathVariable("username") String username) {
1099+
return username;
1100+
}
1101+
10831102
}
10841103

10851104
}

core/src/test/java/org/springframework/security/authentication/TestAuthentication.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ public class TestAuthentication extends PasswordEncodedUser {
3535
AuthorityUtils.createAuthorityList("ROLE_USER"));
3636

3737
public static Authentication authenticatedAdmin() {
38-
return autheticated(admin());
38+
return authenticated(admin());
3939
}
4040

4141
public static Authentication authenticatedUser() {
42-
return autheticated(user());
42+
return authenticated(user());
4343
}
4444

45-
public static Authentication autheticated(UserDetails user) {
45+
public static Authentication authenticated(UserDetails user) {
4646
return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities());
4747
}
4848

0 commit comments

Comments
 (0)