SEC-2151: Support binding method arguments with Annotations
This allow utilizing method arguments for method access control on interfaces prior to JDK 8.
This commit is contained in:
parent
fb0a8d19e8
commit
a09756745f
|
@ -166,4 +166,37 @@ public class GlobalMethodSecurityConfigurationTests extends BaseSpringSpec {
|
|||
grantAccess
|
||||
}
|
||||
}
|
||||
|
||||
def "Method Security supports annotations on interface parameter names"() {
|
||||
setup:
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new TestingAuthenticationToken("user", "password","ROLE_USER"))
|
||||
loadConfig(MethodSecurityServiceConfig)
|
||||
MethodSecurityService service = context.getBean(MethodSecurityService)
|
||||
when: "service with annotated argument"
|
||||
service.postAnnotation('deny')
|
||||
then: "properly throws AccessDeniedException"
|
||||
thrown(AccessDeniedException)
|
||||
when: "service with annotated argument"
|
||||
service.postAnnotation('grant')
|
||||
then: "properly throws AccessDeniedException"
|
||||
noExceptionThrown()
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
static class MethodSecurityServiceConfig extends GlobalMethodSecurityConfiguration {
|
||||
|
||||
@Override
|
||||
protected void registerAuthentication(AuthenticationManagerBuilder auth)
|
||||
throws Exception {
|
||||
auth
|
||||
.inMemoryAuthentication()
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MethodSecurityService service() {
|
||||
new MethodSecurityServiceImpl()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import javax.annotation.security.DenyAll
|
|||
import javax.annotation.security.PermitAll;
|
||||
|
||||
import org.springframework.security.access.annotation.Secured
|
||||
import org.springframework.security.access.method.P
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.security.core.Authentication
|
||||
|
@ -55,4 +56,7 @@ public interface MethodSecurityService {
|
|||
|
||||
@PostAuthorize("hasPermission(#object,'read')")
|
||||
public String postHasPermission(String object);
|
||||
|
||||
@PostAuthorize("#o?.contains('grant')")
|
||||
public String postAnnotation(@P("o") String object);
|
||||
}
|
||||
|
|
|
@ -69,4 +69,9 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
|
|||
public String postHasPermission(String object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postAnnotation(String object) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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.access.method;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.security.core.parameters.AnnotationParameterNameDiscoverer;
|
||||
|
||||
/**
|
||||
* An annotation that can be used along with
|
||||
* {@link AnnotationParameterNameDiscoverer} to specify parameter names. This is
|
||||
* useful for interfaces prior to JDK 8 which cannot contain the parameter
|
||||
* names.
|
||||
*
|
||||
* @see AnnotationParameterNameDiscoverer
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface P {
|
||||
|
||||
/**
|
||||
* The parameter name
|
||||
* @return
|
||||
*/
|
||||
String value();
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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.core.parameters;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.PrioritizedParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.security.access.method.P;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Allows finding parameter names using the value attribute of any number of
|
||||
* {@link Annotation} instances. This is useful when needing to discover the
|
||||
* parameter names of interfaces with Spring Security's method level security.
|
||||
* For example, consider the following:
|
||||
*
|
||||
* <pre>
|
||||
* import org.springframework.security.access.method.P;
|
||||
*
|
||||
* @PostAuthorize("#to == returnObject.to")
|
||||
* public Message findMessageByTo(@P("to") String to);
|
||||
* </pre>
|
||||
*
|
||||
* We can make this possible using the following
|
||||
* {@link AnnotationParameterNameDiscoverer}:
|
||||
*
|
||||
* <pre>
|
||||
* ParameterAnnotationsNameDiscoverer discoverer = new ParameterAnnotationsNameDiscoverer(
|
||||
* "org.springframework.security.access.method.P");
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* It is common for users to use {@link AnnotationParameterNameDiscoverer} in
|
||||
* conjuction with {@link PrioritizedParameterNameDiscoverer}. In fact, Spring
|
||||
* Security's {@link DefaultSecurityParameterNameDiscoverer} (which is used by
|
||||
* default with method level security) extends
|
||||
* {@link PrioritizedParameterNameDiscoverer} and will automatically support
|
||||
* both {@link P} and Spring Data's Param annotation if it is found on the
|
||||
* classpath.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It is important that all the parameter names have a supported annotation on
|
||||
* them. Otherwise, the result will be null. For example, consider the
|
||||
* following:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* import org.springframework.security.access.method.P;
|
||||
*
|
||||
* @PostAuthorize("#to == returnObject.to")
|
||||
* public Message findMessageByToAndFrom(@P("to") User to, User from);
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The result of finding parameters on the previous sample will be a null
|
||||
* String[] since only a single parameter contains an annotation. This is mostly
|
||||
* due to the fact that the fallbacks for
|
||||
* {@link PrioritizedParameterNameDiscoverer} are an all or nothing operation.
|
||||
* </p>
|
||||
*
|
||||
* @see DefaultSecurityParameterNameDiscoverer
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
public class AnnotationParameterNameDiscoverer implements
|
||||
ParameterNameDiscoverer {
|
||||
|
||||
private final Set<String> annotationClassesToUse;
|
||||
|
||||
public AnnotationParameterNameDiscoverer(String... annotationClassToUse) {
|
||||
this(new HashSet<String>(Arrays.asList(annotationClassToUse)));
|
||||
}
|
||||
|
||||
public AnnotationParameterNameDiscoverer(Set<String> annotationClassesToUse) {
|
||||
Assert.notEmpty(annotationClassesToUse,
|
||||
"annotationClassesToUse cannot be null or empty");
|
||||
this.annotationClassesToUse = annotationClassesToUse;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.core.ParameterNameDiscoverer#getParameterNames(java
|
||||
* .lang.reflect.Method)
|
||||
*/
|
||||
public String[] getParameterNames(Method method) {
|
||||
Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
String[] paramNames = lookupParameterNames(METHOD_METHODPARAM_FACTORY, originalMethod);
|
||||
if(paramNames != null) {
|
||||
return paramNames;
|
||||
}
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
Class<?>[] interfaces = declaringClass.getInterfaces();
|
||||
for(Class<?> intrfc : interfaces) {
|
||||
Method intrfcMethod = ReflectionUtils.findMethod(intrfc, method.getName(), method.getParameterTypes());
|
||||
if(intrfcMethod != null) {
|
||||
return lookupParameterNames(METHOD_METHODPARAM_FACTORY, intrfcMethod);
|
||||
}
|
||||
}
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.core.ParameterNameDiscoverer#getParameterNames(java
|
||||
* .lang.reflect.Constructor)
|
||||
*/
|
||||
public String[] getParameterNames(Constructor<?> constructor) {
|
||||
return lookupParameterNames(CONSTRUCTOR_METHODPARAM_FACTORY,
|
||||
constructor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parameter names or null if not found.
|
||||
*
|
||||
* @param parameterNameFactory the {@link ParameterNameFactory} to use
|
||||
* @param t the {@link AccessibleObject} to find the parameter names on (i.e. Method or Constructor)
|
||||
* @return the parameter names or null
|
||||
*/
|
||||
private <T extends AccessibleObject> String[] lookupParameterNames(
|
||||
ParameterNameFactory<T> parameterNameFactory, T t) {
|
||||
int parameterCount = parameterNameFactory.getParamCount(t);
|
||||
String[] paramNames = new String[parameterCount];
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
Annotation[] annotations = parameterNameFactory.findAnnotationsAt(t, i);
|
||||
String parameterName = findParameterName(annotations);
|
||||
if (parameterName == null) {
|
||||
return null;
|
||||
} else {
|
||||
paramNames[i] = parameterName;
|
||||
}
|
||||
}
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the parameter name from the provided {@link Annotation}s or null if
|
||||
* it could not find it. The search is done by looking at the value property
|
||||
* of the {@link #annotationClassesToUse}.
|
||||
*
|
||||
* @param parameterAnnotations
|
||||
* the {@link Annotation}'s to search.
|
||||
* @return
|
||||
*/
|
||||
private String findParameterName(Annotation[] parameterAnnotations) {
|
||||
for (Annotation paramAnnotation : parameterAnnotations) {
|
||||
if (annotationClassesToUse.contains(paramAnnotation
|
||||
.annotationType().getName())) {
|
||||
return (String) AnnotationUtils.getValue(paramAnnotation,
|
||||
"value");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final ParameterNameFactory<Constructor<?>> CONSTRUCTOR_METHODPARAM_FACTORY = new ParameterNameFactory<Constructor<?>>() {
|
||||
public int getParamCount(Constructor<?> constructor) {
|
||||
return constructor.getParameterTypes().length;
|
||||
}
|
||||
|
||||
public Annotation[] findAnnotationsAt(Constructor<?> constructor, int index) {
|
||||
return constructor.getParameterAnnotations()[index];
|
||||
}
|
||||
};
|
||||
|
||||
private static final ParameterNameFactory<Method> METHOD_METHODPARAM_FACTORY = new ParameterNameFactory<Method>() {
|
||||
public int getParamCount(Method method) {
|
||||
return method.getParameterTypes().length;
|
||||
}
|
||||
|
||||
public Annotation[] findAnnotationsAt(Method method, int index) {
|
||||
return method.getParameterAnnotations()[index];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Strategy interface for looking up the parameter names.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*
|
||||
* @param <T> the type to inspect (i.e. {@link Method} or {@link Constructor})
|
||||
*/
|
||||
private interface ParameterNameFactory<T extends AccessibleObject> {
|
||||
/**
|
||||
* Gets the parameter count
|
||||
* @param t
|
||||
* @return
|
||||
*/
|
||||
int getParamCount(T t);
|
||||
|
||||
/**
|
||||
* Gets the {@link Annotation}s at a specified index
|
||||
* @param t
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
Annotation[] findAnnotationsAt(T t, int index);
|
||||
}
|
||||
}
|
|
@ -16,13 +16,16 @@
|
|||
package org.springframework.security.core.parameters;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.PrioritizedParameterNameDiscoverer;
|
||||
import org.springframework.security.access.method.P;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
|
@ -33,6 +36,9 @@ import org.springframework.util.ClassUtils;
|
|||
* classpath.
|
||||
*
|
||||
* <ul>
|
||||
* <li>Will use an instance of {@link AnnotationParameterNameDiscoverer} with
|
||||
* {@link P} as a valid annotation. If, Spring Data is on the classpath will
|
||||
* also add Param annotation.</li>
|
||||
* <li>If Spring 4 is on the classpath, then DefaultParameterNameDiscoverer is
|
||||
* added. This attempts to use JDK 8 information first and falls back to
|
||||
* {@link LocalVariableTableParameterNameDiscoverer}.</li>
|
||||
|
@ -40,6 +46,8 @@ import org.springframework.util.ClassUtils;
|
|||
* {@link LocalVariableTableParameterNameDiscoverer} is added directly.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see AnnotationParameterNameDiscoverer
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
|
@ -53,6 +61,10 @@ public class DefaultSecurityParameterNameDiscoverer extends
|
|||
private static final boolean DEFAULT_PARAM_DISCOVERER_PRESENT =
|
||||
ClassUtils.isPresent(DEFAULT_PARAMETER_NAME_DISCOVERER_CLASSNAME, DefaultSecurityParameterNameDiscoverer.class.getClassLoader());
|
||||
|
||||
private static final String DATA_PARAM_CLASSNAME = "org.springframework.data.repository.query.Param";
|
||||
private static final boolean DATA_PARAM_PRESENT =
|
||||
ClassUtils.isPresent(DATA_PARAM_CLASSNAME, DefaultSecurityParameterNameDiscoverer.class.getClassLoader());
|
||||
|
||||
/**
|
||||
* Creates a new instance with only the default
|
||||
* {@link ParameterNameDiscoverer} instances.
|
||||
|
@ -71,6 +83,15 @@ public class DefaultSecurityParameterNameDiscoverer extends
|
|||
for(ParameterNameDiscoverer discover : parameterNameDiscovers) {
|
||||
addDiscoverer(discover);
|
||||
}
|
||||
|
||||
Set<String> annotationClassesToUse = new HashSet<String>(2);
|
||||
annotationClassesToUse.add(P.class.getName());
|
||||
if(DATA_PARAM_PRESENT) {
|
||||
annotationClassesToUse.add(DATA_PARAM_CLASSNAME);
|
||||
}
|
||||
|
||||
addDiscoverer(new AnnotationParameterNameDiscoverer(annotationClassesToUse));
|
||||
|
||||
if (DEFAULT_PARAM_DISCOVERER_PRESENT) {
|
||||
try {
|
||||
Class<? extends ParameterNameDiscoverer> paramNameDiscoverClass = (Class<? extends ParameterNameDiscoverer>) ClassUtils
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package org.springframework.security.core.parameters;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.access.method.P;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
public class AnnotationParameterNameDiscovererTests {
|
||||
private AnnotationParameterNameDiscoverer discoverer;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
discoverer = new AnnotationParameterNameDiscoverer(P.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesInterfaceSingleParam() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByTo", String.class))).isEqualTo(new String [] { "to"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesInterfaceSingleParamAnnotatedWithMultiParams() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByToAndFrom", String.class, String.class))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesInterfaceNoAnnotation() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByIdNoAnnotation", String.class))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassSingleParam() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByTo", String.class))).isEqualTo(new String [] { "to"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassSingleParamAnnotatedWithMultiParams() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByToAndFrom", String.class, String.class))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassNoAnnotation() {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByIdNoAnnotation", String.class))).isNull();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getParameterNamesConstructor() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(Impl.class.getConstructor(String.class))).isEqualTo(new String[] { "id"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesConstructorNoAnnotation() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(Impl.class.getConstructor(Long.class))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassAnnotationOnInterface() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(DaoImpl.class, "findMessageByTo", String.class))).isEqualTo(new String[] {"to"});
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByTo", String.class))).isEqualTo(new String[] {"to"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassAnnotationOnImpl() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByToAndFrom", String.class, String.class))).isNull();
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(DaoImpl.class, "findMessageByToAndFrom", String.class, String.class))).isEqualTo(new String[] {"to", "from"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParameterNamesClassAnnotationOnBaseClass() throws Exception {
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(Dao.class, "findMessageByIdNoAnnotation", String.class))).isNull();
|
||||
assertThat(discoverer.getParameterNames(ReflectionUtils.findMethod(DaoImpl.class, "findMessageByIdNoAnnotation", String.class))).isEqualTo(new String[] {"id"});
|
||||
}
|
||||
|
||||
interface Dao {
|
||||
String findMessageByTo(@P("to") String to);
|
||||
|
||||
String findMessageByToAndFrom(@P("to") String to, String from);
|
||||
|
||||
String findMessageByIdNoAnnotation(String id);
|
||||
}
|
||||
|
||||
static class BaseDaoImpl {
|
||||
public String findMessageByIdNoAnnotation(@P("id") String id) { return null; }
|
||||
}
|
||||
|
||||
static class DaoImpl extends BaseDaoImpl implements Dao {
|
||||
public String findMessageByTo(String to) { return null; }
|
||||
|
||||
public String findMessageByToAndFrom(@P("to") String to, @P("from") String from) { return null; }
|
||||
}
|
||||
|
||||
static class Impl {
|
||||
public Impl(Long dataSourceId) {}
|
||||
|
||||
public Impl(@P("id") String dataSourceId) {}
|
||||
|
||||
String findMessageByTo(@P("to") String to) { return null; }
|
||||
|
||||
String findMessageByToAndFrom(@P("to") String to, String from) { return null; }
|
||||
|
||||
String findMessageByIdNoAnnotation(String id) { return null; }
|
||||
}
|
||||
}
|
|
@ -19,12 +19,14 @@ import static org.fest.assertions.Assertions.assertThat;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.security.access.method.P;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
|
@ -45,8 +47,16 @@ public class DefaultSecurityParameterNameDiscovererTests {
|
|||
public void constructorDefault() {
|
||||
List<ParameterNameDiscoverer> discoverers = (List<ParameterNameDiscoverer>) ReflectionTestUtils
|
||||
.getField(discoverer, "parameterNameDiscoverers");
|
||||
assertThat(discoverers.size()).isEqualTo(1);
|
||||
assertThat(discoverers.get(0)).isInstanceOf(
|
||||
|
||||
assertThat(discoverers.size()).isEqualTo(2);
|
||||
|
||||
ParameterNameDiscoverer annotationDisc = discoverers.get(0);
|
||||
assertThat(annotationDisc).isInstanceOf(
|
||||
AnnotationParameterNameDiscoverer.class);
|
||||
Set<String> annotationsToUse = (Set<String>)ReflectionTestUtils.getField(annotationDisc, "annotationClassesToUse");
|
||||
assertThat(annotationsToUse).containsOnly(P.class.getName());
|
||||
|
||||
assertThat(discoverers.get(1)).isInstanceOf(
|
||||
DefaultParameterNameDiscoverer.class);
|
||||
}
|
||||
|
||||
|
@ -57,10 +67,17 @@ public class DefaultSecurityParameterNameDiscovererTests {
|
|||
List<ParameterNameDiscoverer> discoverers = (List<ParameterNameDiscoverer>) ReflectionTestUtils
|
||||
.getField(discoverer, "parameterNameDiscoverers");
|
||||
|
||||
assertThat(discoverers.size()).isEqualTo(2);
|
||||
assertThat(discoverers.size()).isEqualTo(3);
|
||||
assertThat(discoverers.get(0)).isInstanceOf(
|
||||
LocalVariableTableParameterNameDiscoverer.class);
|
||||
assertThat(discoverers.get(1)).isInstanceOf(
|
||||
|
||||
ParameterNameDiscoverer annotationDisc = discoverers.get(1);
|
||||
assertThat(annotationDisc).isInstanceOf(
|
||||
AnnotationParameterNameDiscoverer.class);
|
||||
Set<String> annotationsToUse = (Set<String>)ReflectionTestUtils.getField(annotationDisc, "annotationClassesToUse");
|
||||
assertThat(annotationsToUse).containsOnly(P.class.getName());
|
||||
|
||||
assertThat(discoverers.get(2)).isInstanceOf(
|
||||
DefaultParameterNameDiscoverer.class);
|
||||
}
|
||||
}
|
|
@ -139,38 +139,88 @@
|
|||
application)<programlisting language="java">
|
||||
@PreAuthorize("hasRole('ROLE_USER')")
|
||||
public void create(Contact contact);</programlisting>which
|
||||
means that access will only be allowed for users with the role "ROLE_USER".
|
||||
Obviously the same thing could easily be achieved using a traditional
|
||||
configuration and a simple configuration attribute for the required role. But
|
||||
what
|
||||
about:<programlisting language="java">
|
||||
@PreAuthorize("hasPermission(#contact, 'admin')")
|
||||
public void deletePermission(Contact contact, Sid recipient, Permission permission);</programlisting>Here
|
||||
we're actually using a method argument as part of the expression to decide
|
||||
whether the current user has the <quote>admin</quote>permission for the given
|
||||
contact. The built-in <literal>hasPermission()</literal> expression is linked
|
||||
into the Spring Security ACL module through the application context, as we'll
|
||||
<link linkend="el-permission-evaluator">see below</link>. You can access any
|
||||
of the method arguments by name as expression variables, provided your code has
|
||||
debug information compiled in. Any Spring-EL functionality is available within
|
||||
the expression, so you can also access properties on the arguments. For example,
|
||||
if you wanted a particular method to only allow access to a user whose username
|
||||
matched that of the contact, you could write</para>
|
||||
<programlisting language="java">
|
||||
@PreAuthorize("#contact.name == authentication.name")
|
||||
public void doSomething(Contact contact);</programlisting>
|
||||
<para>Here we are accessing another built–in expression, <literal>authentication</literal>,
|
||||
which is the <interfacename>Authentication</interfacename> stored in the
|
||||
security context. You can also access its <quote>principal</quote> property
|
||||
directly, using the expression <literal>principal</literal>. The value will
|
||||
often be a <interfacename>UserDetails</interfacename> instance, so you might use an
|
||||
expression like <literal>principal.username</literal> or
|
||||
<literal>principal.enabled</literal>.</para>
|
||||
<para>Less commonly, you may wish to perform an access-control check after the
|
||||
method has been invoked. This can be achieved using the
|
||||
<literal>@PostAuthorize</literal> annotation. To access the return value from a
|
||||
method, use the built–in name <literal>returnObject</literal> in the
|
||||
expression.</para>
|
||||
means that access will only be allowed for users with the role "ROLE_USER".</para>
|
||||
<section xml:id="el-pre-post-annotations-arguments">
|
||||
<title>Resolving method arguments</title>
|
||||
<para>Obviously the same thing could easily be achieved using a traditional
|
||||
configuration and a simple configuration attribute for the required role. But
|
||||
what
|
||||
about:<programlisting language="java">
|
||||
@PreAuthorize("hasPermission(#contact, 'admin')")
|
||||
public void deletePermission(Contact contact, Sid recipient, Permission permission);</programlisting>Here
|
||||
we're actually using a method argument as part of the expression to decide
|
||||
whether the current user has the <quote>admin</quote>permission for the given
|
||||
contact. The built-in <literal>hasPermission()</literal> expression is linked
|
||||
into the Spring Security ACL module through the application context, as we'll
|
||||
<link linkend="el-permission-evaluator">see below</link>. You can access any
|
||||
of the method arguments by name as expression variables.</para>
|
||||
<para>There are a number of ways in which Spring Security can resolve the method arguments. Spring Security
|
||||
uses <classname>DefaultSecurityParameterNameDiscoverer</classname> to discover the parameter names. By default,
|
||||
the following options are tried for a method as a whole.
|
||||
<orderedlist inheritnum="ignore" continuation="restarts">
|
||||
<listitem>
|
||||
<para>If Spring Security's <literal>@P</literal> annotation is present on a single argument to the method,
|
||||
the value will be used. This is useful for interfaces compiled with a JDK prior to JDK 8 which do not contain
|
||||
any information about the parameter names. For example: <programlisting language="java">
|
||||
import org.springframework.security.access.method.P;
|
||||
|
||||
...
|
||||
|
||||
@PreAuthorize("#c.name == authentication.name")
|
||||
public void doSomething(@P("c") Contact contact);</programlisting></para>
|
||||
<para>Behind the scenes this use implemented using <classname>AnnotationParameterNameDiscoverer</classname> which
|
||||
can be customized to support the value attribute of any specified annotation.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>If Spring Data's <literal>@Param</literal> annotation is present on at least one parameter for the method,
|
||||
the value will be used. This is useful for interfaces compiled with a JDK prior to JDK 8 which do not contain
|
||||
any information about the parameter names. For example: <programlisting language="java">
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
...
|
||||
|
||||
@PreAuthorize("#n == authentication.name")
|
||||
Contact findContactByName(@Param("n") String name);</programlisting></para>
|
||||
<para>Behind the scenes this use implemented using <classname>AnnotationParameterNameDiscoverer</classname> which
|
||||
can be customized to support the value attribute of any specified annotation.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>If JDK 8 was used to compile the source with the -parameters argument and Spring 4+ is being used, then
|
||||
the standard JDK reflection API is used to discover the parameter names. This works on both classes and
|
||||
interfaces.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Last, if the code was compiled with the debug symbols, the parameter names will be discovered using
|
||||
the debug symbols. This will not work for interfaces since they do not have debug information about the
|
||||
parameter names. For interfaces, annotations or the JDK 8 approach must be used.</para>
|
||||
</listitem>
|
||||
</orderedlist></para>
|
||||
</section>
|
||||
<section xml:id="el-pre-post-annotations-spel">
|
||||
<title>Method Expressions and SpEL</title>
|
||||
<para>Any Spring-EL functionality is available within
|
||||
the expression, so you can also access properties on the arguments. For example,
|
||||
if you wanted a particular method to only allow access to a user whose username
|
||||
matched that of the contact, you could write</para>
|
||||
<programlisting language="java">
|
||||
@PreAuthorize("#contact.name == authentication.name")
|
||||
public void doSomething(Contact contact);</programlisting>
|
||||
<para>Here we are accessing another built–in expression, <literal>authentication</literal>,
|
||||
which is the <interfacename>Authentication</interfacename> stored in the
|
||||
security context. You can also access its <quote>principal</quote> property
|
||||
directly, using the expression <literal>principal</literal>. The value will
|
||||
often be a <interfacename>UserDetails</interfacename> instance, so you might use an
|
||||
expression like <literal>principal.username</literal> or
|
||||
<literal>principal.enabled</literal>.</para>
|
||||
</section>
|
||||
<section xml:id="el-pre-post-annotations-post">
|
||||
<title>Accessing the return value</title>
|
||||
<para>Less commonly, you may wish to perform an access-control check after the
|
||||
method has been invoked. This can be achieved using the
|
||||
<literal>@PostAuthorize</literal> annotation. To access the return value from a
|
||||
method, use the built–in name <literal>returnObject</literal> in the
|
||||
expression.</para>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Filtering using <literal>@PreFilter</literal> and
|
||||
|
|
Loading…
Reference in New Issue