浏览代码

Polish WebExpressionAuthorizationManager

- Add support for request variables
- Added additional tests

Issue gh-11105
Josh Cummings 3 年之前
父节点
当前提交
0814136ee8

+ 40 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

@@ -45,7 +45,9 @@ import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 import org.springframework.test.web.servlet.request.RequestPostProcessor;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 
@@ -474,6 +476,15 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		this.mvc.perform(requestFromOtherHost).andExpect(status().isForbidden());
 	}
 
+	@Test
+	public void requestWhenMvcMatcherPathVariablesThenMatchesOnPathVariables() throws Exception {
+		this.spring.register(MvcMatcherPathVariablesInLambdaConfig.class).autowire();
+		MockHttpServletRequestBuilder request = get("/user/user");
+		this.mvc.perform(request).andExpect(status().isOk());
+		request = get("/user/deny");
+		this.mvc.perform(request).andExpect(status().isUnauthorized());
+	}
+
 	private static RequestPostProcessor remoteAddress(String remoteAddress) {
 		return (request) -> {
 			request.setRemoteAddr(remoteAddress);
@@ -847,6 +858,35 @@ public class AuthorizeHttpRequestsConfigurerTests {
 
 	}
 
+	@EnableWebSecurity
+	@Configuration
+	@EnableWebMvc
+	static class MvcMatcherPathVariablesInLambdaConfig {
+
+		@Bean
+		SecurityFilterChain chain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.httpBasic(withDefaults())
+				.authorizeHttpRequests((requests) -> requests
+					.mvcMatchers("/user/{username}").access(new WebExpressionAuthorizationManager("#username == 'user'"))
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+		@RestController
+		static class PathController {
+
+			@RequestMapping("/user/{username}")
+			String path(@PathVariable("username") String username) {
+				return username;
+			}
+
+		}
+
+	}
+
 	@Configuration
 	static class AuthorizationEventPublisherConfig {
 

+ 4 - 0
web/src/main/java/org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.java

@@ -16,6 +16,7 @@
 
 package org.springframework.security.web.access.expression;
 
+import java.util.Map;
 import java.util.function.Supplier;
 
 import org.springframework.expression.EvaluationContext;
@@ -72,6 +73,9 @@ public final class WebExpressionAuthorizationManager implements AuthorizationMan
 	@Override
 	public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
 		EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), context);
+		for (Map.Entry<String, String> entry : context.getVariables().entrySet()) {
+			ctx.setVariable(entry.getKey(), entry.getValue());
+		}
 		boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
 		return new ExpressionAuthorizationDecision(granted, this.expression);
 	}

+ 94 - 0
web/src/test/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandlerTests.java

@@ -0,0 +1,94 @@
+/*
+ * Copyright 2002-2016 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.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.web.access.expression;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+public class DefaultHttpSecurityExpressionHandlerTests {
+
+	@Mock
+	private AuthenticationTrustResolver trustResolver;
+
+	@Mock
+	private Authentication authentication;
+
+	@Mock
+	private RequestAuthorizationContext context;
+
+	private DefaultHttpSecurityExpressionHandler handler;
+
+	@BeforeEach
+	public void setup() {
+		this.handler = new DefaultHttpSecurityExpressionHandler();
+	}
+
+	@AfterEach
+	public void cleanup() {
+		SecurityContextHolder.clearContext();
+	}
+
+	@Test
+	public void expressionPropertiesAreResolvedAgainstAppContextBeans() {
+		StaticApplicationContext appContext = new StaticApplicationContext();
+		RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class);
+		bean.getConstructorArgumentValues().addGenericArgumentValue("ROLE_A");
+		appContext.registerBeanDefinition("role", bean);
+		this.handler.setApplicationContext(appContext);
+		EvaluationContext ctx = this.handler.createEvaluationContext(mock(Authentication.class),
+				mock(RequestAuthorizationContext.class));
+		ExpressionParser parser = this.handler.getExpressionParser();
+		assertThat(parser.parseExpression("@role.getAttribute() == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
+		assertThat(parser.parseExpression("@role.attribute == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
+	}
+
+	@Test
+	public void setTrustResolverNull() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null));
+	}
+
+	@Test
+	public void createEvaluationContextCustomTrustResolver() {
+		this.handler.setTrustResolver(this.trustResolver);
+		Expression expression = this.handler.getExpressionParser().parseExpression("anonymous");
+		EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.context);
+		assertThat(expression.getValue(context, Boolean.class)).isFalse();
+		verify(this.trustResolver).isAnonymous(this.authentication);
+	}
+
+}