Skip to content

Simplify Request Authorization Path Extraction #13574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.security.config.annotation.web.configurers;

import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

import io.micrometer.observation.ObservationRegistry;
Expand All @@ -37,6 +38,7 @@
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
Expand Down Expand Up @@ -387,6 +389,21 @@ public AuthorizationManagerRequestMatcherRegistry anonymous() {
return access(AuthenticatedAuthorizationManager.anonymous());
}

/**
* Specify that a path variable in URL to be compared.
*
* <p>
* For example, <pre>
* requestMatchers("/user/{username}").hasVariable("username").equalTo(Authentication::getName)
* </pre>
* @param variable the variable in URL template to compare.
* @return {@link AuthorizedUrlVariable} for further customization.
* @since 6.3
*/
public AuthorizedUrlVariable hasVariable(String variable) {
return new AuthorizedUrlVariable(variable);
}

/**
* Allows specifying a custom {@link AuthorizationManager}.
* @param manager the {@link AuthorizationManager} to use
Expand All @@ -401,6 +418,41 @@ public AuthorizationManagerRequestMatcherRegistry access(
: AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
}

/**
* An object that allows configuring {@link RequestMatcher}s with URI path
* variables
*
* @author Taehong Kim
* @since 6.3
*/
public final class AuthorizedUrlVariable {

private final String variable;

private AuthorizedUrlVariable(String variable) {
this.variable = variable;
}

/**
* Compares the value of a path variable in the URI with an `Authentication`
* attribute
* <p>
* For example, <pre>
* requestMatchers("/user/{username}").hasVariable("username").equalTo(Authentication::getName));
* </pre>
* @param function a function to get value from {@link Authentication}.
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
* customization.
*/
public AuthorizationManagerRequestMatcherRegistry equalTo(Function<Authentication, String> function) {
return access((auth, requestContext) -> {
String value = requestContext.getVariables().get(this.variable);
return new AuthorizationDecision(function.apply(auth.get()).equals(value));
});
}

}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
Expand Down Expand Up @@ -543,6 +545,17 @@ public void requestWhenMvcMatcherPathVariablesThenMatchesOnPathVariables() throw
this.mvc.perform(request).andExpect(status().isOk());
request = get("/user/deny");
this.mvc.perform(request).andExpect(status().isUnauthorized());

UserDetails user = TestAuthentication.withUsername("taehong").build();
Authentication authentication = TestAuthentication.authenticated(user);
request = get("/v2/user/{username}", user.getUsername()).with(authentication(authentication));
this.mvc.perform(request).andExpect(status().isOk());

request = get("/v2/user/{username}", "withNoAuthentication");
this.mvc.perform(request).andExpect(status().isUnauthorized());

request = get("/v2/user/{username}", "another").with(authentication(authentication));
this.mvc.perform(request).andExpect(status().isForbidden());
}

private static RequestPostProcessor remoteAddress(String remoteAddress) {
Expand Down Expand Up @@ -1067,6 +1080,7 @@ SecurityFilterChain chain(HttpSecurity http) throws Exception {
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/user/{username}").access(new WebExpressionAuthorizationManager("#username == 'user'"))
.requestMatchers("/v2/user/{username}").hasVariable("username").equalTo(Authentication::getName)
);
// @formatter:on
return http.build();
Expand All @@ -1080,6 +1094,11 @@ String path(@PathVariable("username") String username) {
return username;
}

@RequestMapping("/v2/user/{username}")
String pathV2(@PathVariable("username") String username) {
return username;
}

}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ public class TestAuthentication extends PasswordEncodedUser {
AuthorityUtils.createAuthorityList("ROLE_USER"));

public static Authentication authenticatedAdmin() {
return autheticated(admin());
return authenticated(admin());
}

public static Authentication authenticatedUser() {
return autheticated(user());
return authenticated(user());
}

public static Authentication autheticated(UserDetails user) {
public static Authentication authenticated(UserDetails user) {
return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities());
}

Expand Down