Introduce @CurrentSecurityContext for method arguments

This commit is contained in:
Dan Zheng
2019-02-26 13:36:43 +08:00
committed by Josh Cummings
parent 4a286be2b9
commit 678e0b19e0
7 changed files with 926 additions and 2 deletions

View File

@@ -0,0 +1,239 @@
/*
* Copyright 2002-2019 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
*
* http://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.bind.support;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.CurrentSecurityContext;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.fail;
/**
* @author Dan Zheng
* @since 5.2.x
*
*/
public class CurrentSecurityContextArgumentResolverTests {
private Object expectedPrincipal;
private CurrentSecurityContextArgumentResolver resolver;
@Before
public void setup() {
resolver = new CurrentSecurityContextArgumentResolver();
}
@After
public void cleanup() {
SecurityContextHolder.clearContext();
}
@Test
public void supportsParameterNoAnnotation() throws Exception {
assertThat(resolver.supportsParameter(showSecurityContextNoAnnotation())).isFalse();
}
@Test
public void supportsParameterAnnotation() throws Exception {
assertThat(resolver.supportsParameter(showSecurityContextAnnotation())).isTrue();
}
@Test
public void resolveArgumentNullAuthentication() throws Exception {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
context.setAuthentication(null);
assertThat(resolver.resolveArgument(showSecurityContextAuthenticationAnnotation(), null, null, null))
.isNull();
context.setAuthentication(authentication);
}
@Test
public void resolveArgumentWithAuthentication() throws Exception {
String principal = "john";
setAuthenticationPrincipal(principal);
Authentication auth1 = (Authentication) resolver.resolveArgument(showSecurityContextAuthenticationAnnotation(), null, null, null);
assertThat(auth1.getPrincipal()).isEqualTo(principal);
}
@Test
public void resolveArgumentWithNullAuthentication() throws Exception {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
context.setAuthentication(null);
assertThatExceptionOfType(SpelEvaluationException.class)
.isThrownBy(() -> {
resolver.resolveArgument(showSecurityContextAuthenticationWithPrincipal(), null, null, null);
});
context.setAuthentication(authentication);
}
@Test
public void resolveArgumentWithOptionalPrincipal() throws Exception {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
context.setAuthentication(null);
Object principalResult = resolver.resolveArgument(showSecurityContextAuthenticationWithOptionalPrincipal(), null, null, null);
assertThat(principalResult).isNull();
context.setAuthentication(authentication);
}
@Test
public void resolveArgumentWithPrincipal() throws Exception {
String principal = "smith";
setAuthenticationPrincipal(principal);
String principalResult = (String) resolver.resolveArgument(showSecurityContextAuthenticationWithPrincipal(), null, null, null);
assertThat(principalResult).isEqualTo(principal);
}
@Test
public void resolveArgumentUserDetails() throws Exception {
setAuthenticationDetail(new User("my_user", "my_password",
AuthorityUtils.createAuthorityList("ROLE_USER")));
User u = (User) resolver.resolveArgument(showSecurityContextWithUserDetail(), null, null,
null);
assertThat(u.getUsername()).isEqualTo("my_user");
}
@Test
public void resolveArgumentSecurityContextErrorOnInvalidTypeImplicit() throws Exception {
String principal = "invalid_type_implicit";
setAuthenticationPrincipal(principal);
assertThat(resolver.resolveArgument(showSecurityContextErrorOnInvalidTypeImplicit(), null, null, null))
.isNull();
}
@Test
public void resolveArgumentSecurityContextErrorOnInvalidTypeFalse() throws Exception {
String principal = "invalid_type_false";
setAuthenticationPrincipal(principal);
assertThat(resolver.resolveArgument(showSecurityContextErrorOnInvalidTypeFalse(), null, null, null))
.isNull();
}
@Test
public void resolveArgumentSecurityContextErrorOnInvalidTypeTrue() throws Exception {
String principal = "invalid_type_true";
setAuthenticationPrincipal(principal);
try {
resolver.resolveArgument(showSecurityContextErrorOnInvalidTypeTrue(), null, null, null);
fail("should not reach here");
} catch(ClassCastException ex) {}
}
private MethodParameter showSecurityContextNoAnnotation() {
return getMethodParameter("showSecurityContextNoAnnotation", String.class);
}
private MethodParameter showSecurityContextAnnotation() {
return getMethodParameter("showSecurityContextAnnotation", SecurityContext.class);
}
private MethodParameter showSecurityContextAuthenticationAnnotation() {
return getMethodParameter("showSecurityContextAuthenticationAnnotation", Authentication.class);
}
private MethodParameter showSecurityContextAuthenticationWithOptionalPrincipal() {
return getMethodParameter("showSecurityContextAuthenticationWithOptionalPrincipal", Object.class);
}
private MethodParameter showSecurityContextAuthenticationWithPrincipal() {
return getMethodParameter("showSecurityContextAuthenticationWithPrincipal", Object.class);
}
private MethodParameter showSecurityContextWithUserDetail() {
return getMethodParameter("showSecurityContextWithUserDetail", Object.class);
}
private MethodParameter showSecurityContextErrorOnInvalidTypeImplicit() {
return getMethodParameter("showSecurityContextErrorOnInvalidTypeImplicit", String.class);
}
private MethodParameter showSecurityContextErrorOnInvalidTypeFalse() {
return getMethodParameter("showSecurityContextErrorOnInvalidTypeFalse", String.class);
}
private MethodParameter showSecurityContextErrorOnInvalidTypeTrue() {
return getMethodParameter("showSecurityContextErrorOnInvalidTypeTrue", String.class);
}
private MethodParameter getMethodParameter(String methodName, Class<?>... paramTypes) {
Method method = ReflectionUtils.findMethod(TestController.class, methodName,
paramTypes);
return new MethodParameter(method, 0);
}
public static class TestController {
public void showSecurityContextNoAnnotation(String user) {
}
public void showSecurityContextAnnotation(@CurrentSecurityContext SecurityContext context) {
}
public void showSecurityContextAuthenticationAnnotation(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
}
public void showSecurityContextAuthenticationWithOptionalPrincipal(@CurrentSecurityContext(expression = "authentication?.principal") Object principal) {
}
public void showSecurityContextAuthenticationWithPrincipal(@CurrentSecurityContext(expression = "authentication.principal") Object principal) {
}
public void showSecurityContextWithUserDetail(@CurrentSecurityContext(expression = "authentication.details") Object detail) {
}
public void showSecurityContextErrorOnInvalidTypeImplicit(
@CurrentSecurityContext String implicit) {
}
public void showSecurityContextErrorOnInvalidTypeFalse(
@CurrentSecurityContext(errorOnInvalidType = false) String implicit) {
}
public void showSecurityContextErrorOnInvalidTypeTrue(
@CurrentSecurityContext(errorOnInvalidType = true) String implicit) {
}
}
private void setAuthenticationPrincipal(Object principal) {
SecurityContextHolder.getContext()
.setAuthentication(
new TestingAuthenticationToken(principal, "password",
"ROLE_USER"));
}
private void setAuthenticationDetail(Object detail) {
TestingAuthenticationToken tat = new TestingAuthenticationToken("user", "password",
"ROLE_USER");
tat.setDetails(detail);
SecurityContextHolder.getContext()
.setAuthentication(tat);
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright 2002-2019 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
*
* http://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.reactive.result.method.annotation;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.CurrentSecurityContext;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.method.ResolvableMethod;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
/**
* @author Dan Zheng
* @since 5.2.x
*/
@RunWith(MockitoJUnitRunner.class)
public class CurrentSecurityContextArgumentResolverTests {
@Mock
ServerWebExchange exchange;
@Mock
BindingContext bindingContext;
@Mock
Authentication authentication;
@Mock
BeanResolver beanResolver;
@Mock
SecurityContext securityContext;
ResolvableMethod securityContextMethod = ResolvableMethod.on(getClass()).named("securityContext").build();
ResolvableMethod securityContextWithAuthentication = ResolvableMethod.on(getClass()).named("securityContextWithAuthentication").build();
CurrentSecurityContextArgumentResolver resolver;
@Before
public void setup() {
resolver = new CurrentSecurityContextArgumentResolver(new ReactiveAdapterRegistry());
this.resolver.setBeanResolver(this.beanResolver);
}
@Test
public void supportsParameterCurrentSecurityContext() throws Exception {
assertThat(resolver.supportsParameter(this.securityContextMethod.arg(Mono.class, SecurityContext.class))).isTrue();
}
@Test
public void supportsParameterWithAuthentication() throws Exception {
assertThat(resolver.supportsParameter(this.securityContextWithAuthentication.arg(Mono.class, Authentication.class))).isTrue();
}
@Test
public void resolveArgumentWithNullSecurityContext() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContext").build().arg(Mono.class, SecurityContext.class);
Context context = ReactiveSecurityContextHolder.withSecurityContext(Mono.empty());
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
Object obj = argument.subscriberContext(context).block();
assertThat(obj).isNull();
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentWithSecurityContext() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContext").build().arg(Mono.class, SecurityContext.class);
Authentication auth = buildAuthenticationWithPrincipal("hello");
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
SecurityContext securityContext = (SecurityContext) argument.subscriberContext(context).cast(Mono.class).block().block();
assertThat(securityContext.getAuthentication()).isSameAs(auth);
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentWithNullAuthentication1() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContext").build().arg(Mono.class, SecurityContext.class);
Authentication auth = null;
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
SecurityContext securityContext = (SecurityContext) argument.subscriberContext(context).cast(Mono.class).block().block();
assertThat(securityContext.getAuthentication()).isNull();
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentWithNullAuthentication2() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContextWithAuthentication").build().arg(Mono.class, Authentication.class);
Authentication auth = null;
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
Mono<Object> r = (Mono<Object>) argument.subscriberContext(context).block();
assertThat(r.block()).isNull();
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentWithAuthentication1() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContextWithAuthentication").build().arg(Mono.class, Authentication.class);
Authentication auth = buildAuthenticationWithPrincipal("authentication1");
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
Mono<Authentication> auth1 = (Mono<Authentication>) argument.subscriberContext(context).block();
assertThat(auth1.block()).isSameAs(auth);
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentWithNullAuthenticationOptional1() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContextWithDepthPropOptional").build().arg(Mono.class, Object.class);
Authentication auth = null;
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
Mono<Object> obj = (Mono<Object>) argument.subscriberContext(context).block();
assertThat(obj.block()).isNull();
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentWithAuthenticationOptional1() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContextWithDepthPropOptional").build().arg(Mono.class, Object.class);
Authentication auth = buildAuthenticationWithPrincipal("auth_optional");
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
Mono<Object> obj = (Mono<Object>) argument.subscriberContext(context).block();
assertThat(obj.block()).isEqualTo("auth_optional");
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentWithNullDepthProp1() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContextWithDepthProp").build().arg(Mono.class, Object.class);
Authentication auth = null;
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
try {
Mono<Object> obj = (Mono<Object>) argument.subscriberContext(context).block();
fail("should not reach here");
} catch(SpelEvaluationException e) {
}
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentWithStringDepthProp() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContextWithDepthStringProp").build().arg(Mono.class, String.class);
Authentication auth = buildAuthenticationWithPrincipal("auth_string");
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
Mono<String> obj = (Mono<String>) argument.subscriberContext(context).block();
assertThat(obj.block()).isEqualTo("auth_string");
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentWhenErrorOnInvalidTypeImplicit() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("errorOnInvalidTypeWhenImplicit").build().arg(Mono.class, String.class);
Authentication auth = buildAuthenticationWithPrincipal("invalid_type_implicit");
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
Mono<String> obj = (Mono<String>) argument.subscriberContext(context).block();
assertThat(obj.block()).isNull();
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentErrorOnInvalidTypeWhenExplicitFalse() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("errorOnInvalidTypeWhenExplicitFalse").build().arg(Mono.class, String.class);
Authentication auth = buildAuthenticationWithPrincipal("error_on_invalid_type_explicit_false");
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
Mono<String> obj = (Mono<String>) argument.subscriberContext(context).block();
assertThat(obj.block()).isNull();
ReactiveSecurityContextHolder.clearContext();
}
@Test
public void resolveArgumentErrorOnInvalidTypeWhenExplicitTrue() throws Exception {
MethodParameter parameter = ResolvableMethod.on(getClass()).named("errorOnInvalidTypeWhenExplicitTrue").build().arg(Mono.class, String.class);
Authentication auth = buildAuthenticationWithPrincipal("error_on_invalid_type_explicit_true");
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange);
try {
Mono<String> obj = (Mono<String>) argument.subscriberContext(context).block();
fail("should not reach here");
} catch(ClassCastException ex) {
}
ReactiveSecurityContextHolder.clearContext();
}
void securityContext(@CurrentSecurityContext Mono<SecurityContext> monoSecurityContext) {}
void securityContextWithAuthentication(@CurrentSecurityContext(expression = "authentication") Mono<Authentication> authentication) {}
void securityContextWithDepthPropOptional(@CurrentSecurityContext(expression = "authentication?.principal") Mono<Object> principal) {}
void securityContextWithDepthProp(@CurrentSecurityContext(expression = "authentication.principal") Mono<Object> principal) {}
void securityContextWithDepthStringProp(@CurrentSecurityContext(expression = "authentication.principal") Mono<String> principal) {}
void errorOnInvalidTypeWhenImplicit(@CurrentSecurityContext Mono<String> implicit) {}
void errorOnInvalidTypeWhenExplicitFalse(@CurrentSecurityContext(errorOnInvalidType = false) Mono<String> implicit) {}
void errorOnInvalidTypeWhenExplicitTrue(@CurrentSecurityContext(errorOnInvalidType = true) Mono<String> implicit) {}
private Authentication buildAuthenticationWithPrincipal(Object principal) {
return new TestingAuthenticationToken(principal, "password",
"ROLE_USER");
}
}