diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfigurationTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfigurationTests.groovy
index 96c063bf68..071177ddd3 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfigurationTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfigurationTests.groovy
@@ -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()
+ }
+ }
}
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.groovy
index 743c466e4e..3db9414d2a 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.groovy
@@ -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);
}
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.groovy
index 013f3ce70e..d925e43c6a 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.groovy
@@ -69,4 +69,9 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
public String postHasPermission(String object) {
return null;
}
+
+ @Override
+ public String postAnnotation(String object) {
+ return null;
+ }
}
diff --git a/core/src/main/java/org/springframework/security/access/method/P.java b/core/src/main/java/org/springframework/security/access/method/P.java
new file mode 100644
index 0000000000..350964a1c7
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/access/method/P.java
@@ -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();
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/springframework/security/core/parameters/AnnotationParameterNameDiscoverer.java b/core/src/main/java/org/springframework/security/core/parameters/AnnotationParameterNameDiscoverer.java
new file mode 100644
index 0000000000..ba081e6d7a
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/core/parameters/AnnotationParameterNameDiscoverer.java
@@ -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:
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ * 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:
+ *
+ *
+ *
+ * import org.springframework.security.access.method.P;
+ *
+ * @PostAuthorize("#to == returnObject.to")
+ * public Message findMessageByToAndFrom(@P("to") User to, User from);
+ *
+ *
+ *
+ * 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.
+ *
+ *
+ * @see DefaultSecurityParameterNameDiscoverer
+ *
+ * @author Rob Winch
+ * @since 3.2
+ */
+public class AnnotationParameterNameDiscoverer implements
+ ParameterNameDiscoverer {
+
+ private final Set annotationClassesToUse;
+
+ public AnnotationParameterNameDiscoverer(String... annotationClassToUse) {
+ this(new HashSet(Arrays.asList(annotationClassToUse)));
+ }
+
+ public AnnotationParameterNameDiscoverer(Set 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 String[] lookupParameterNames(
+ ParameterNameFactory 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_METHODPARAM_FACTORY = new ParameterNameFactory>() {
+ 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_METHODPARAM_FACTORY = new ParameterNameFactory() {
+ 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 the type to inspect (i.e. {@link Method} or {@link Constructor})
+ */
+ private interface ParameterNameFactory {
+ /**
+ * 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);
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/springframework/security/core/parameters/DefaultSecurityParameterNameDiscoverer.java b/core/src/main/java/org/springframework/security/core/parameters/DefaultSecurityParameterNameDiscoverer.java
index db730b9ddf..877aeec7c2 100644
--- a/core/src/main/java/org/springframework/security/core/parameters/DefaultSecurityParameterNameDiscoverer.java
+++ b/core/src/main/java/org/springframework/security/core/parameters/DefaultSecurityParameterNameDiscoverer.java
@@ -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.
*
*
+ *
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.
*
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}.
*
+ * @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 annotationClassesToUse = new HashSet(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
diff --git a/core/src/test/java/org/springframework/security/core/parameters/AnnotationParameterNameDiscovererTests.java b/core/src/test/java/org/springframework/security/core/parameters/AnnotationParameterNameDiscovererTests.java
new file mode 100644
index 0000000000..f618ac2702
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/core/parameters/AnnotationParameterNameDiscovererTests.java
@@ -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; }
+ }
+}
diff --git a/core/src/test/java/org/springframework/security/core/parameters/DefaultSecurityParameterNameDiscovererTests.java b/core/src/test/java/org/springframework/security/core/parameters/DefaultSecurityParameterNameDiscovererTests.java
index d0528dc87f..ae2c326fd3 100644
--- a/core/src/test/java/org/springframework/security/core/parameters/DefaultSecurityParameterNameDiscovererTests.java
+++ b/core/src/test/java/org/springframework/security/core/parameters/DefaultSecurityParameterNameDiscovererTests.java
@@ -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 discoverers = (List) 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 annotationsToUse = (Set)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 discoverers = (List) 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 annotationsToUse = (Set)ReflectionTestUtils.getField(annotationDisc, "annotationClassesToUse");
+ assertThat(annotationsToUse).containsOnly(P.class.getName());
+
+ assertThat(discoverers.get(2)).isInstanceOf(
DefaultParameterNameDiscoverer.class);
}
}
\ No newline at end of file
diff --git a/docs/manual/src/docbook/el-access.xml b/docs/manual/src/docbook/el-access.xml
index ec5eb981e2..390f93d93a 100644
--- a/docs/manual/src/docbook/el-access.xml
+++ b/docs/manual/src/docbook/el-access.xml
@@ -139,38 +139,88 @@
application)
@PreAuthorize("hasRole('ROLE_USER')")
public void create(Contact contact);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:
- @PreAuthorize("hasPermission(#contact, 'admin')")
- public void deletePermission(Contact contact, Sid recipient, Permission permission);Here
- we're actually using a method argument as part of the expression to decide
- whether the current user has the adminpermission for the given
- contact. The built-in hasPermission() expression is linked
- into the Spring Security ACL module through the application context, as we'll
- see below. 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
-
- @PreAuthorize("#contact.name == authentication.name")
- public void doSomething(Contact contact);
- Here we are accessing another built–in expression, authentication,
- which is the Authentication stored in the
- security context. You can also access its principal property
- directly, using the expression principal. The value will
- often be a UserDetails instance, so you might use an
- expression like principal.username or
- principal.enabled.
- Less commonly, you may wish to perform an access-control check after the
- method has been invoked. This can be achieved using the
- @PostAuthorize annotation. To access the return value from a
- method, use the built–in name returnObject in the
- expression.
+ means that access will only be allowed for users with the role "ROLE_USER".
+
+ Resolving method arguments
+ Obviously the same thing could easily be achieved using a traditional
+ configuration and a simple configuration attribute for the required role. But
+ what
+ about:
+ @PreAuthorize("hasPermission(#contact, 'admin')")
+ public void deletePermission(Contact contact, Sid recipient, Permission permission);Here
+ we're actually using a method argument as part of the expression to decide
+ whether the current user has the adminpermission for the given
+ contact. The built-in hasPermission() expression is linked
+ into the Spring Security ACL module through the application context, as we'll
+ see below. You can access any
+ of the method arguments by name as expression variables.
+ There are a number of ways in which Spring Security can resolve the method arguments. Spring Security
+ uses DefaultSecurityParameterNameDiscoverer to discover the parameter names. By default,
+ the following options are tried for a method as a whole.
+
+
+ If Spring Security's @P 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:
+import org.springframework.security.access.method.P;
+
+...
+
+@PreAuthorize("#c.name == authentication.name")
+public void doSomething(@P("c") Contact contact);
+ Behind the scenes this use implemented using AnnotationParameterNameDiscoverer which
+ can be customized to support the value attribute of any specified annotation.
+
+
+ If Spring Data's @Param 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:
+import org.springframework.data.repository.query.Param;
+
+...
+
+@PreAuthorize("#n == authentication.name")
+Contact findContactByName(@Param("n") String name);
+ Behind the scenes this use implemented using AnnotationParameterNameDiscoverer which
+ can be customized to support the value attribute of any specified annotation.
+
+
+ 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.
+
+
+ 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.
+
+
+
+
+ Method Expressions and SpEL
+ 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
+
+ @PreAuthorize("#contact.name == authentication.name")
+ public void doSomething(Contact contact);
+ Here we are accessing another built–in expression, authentication,
+ which is the Authentication stored in the
+ security context. You can also access its principal property
+ directly, using the expression principal. The value will
+ often be a UserDetails instance, so you might use an
+ expression like principal.username or
+ principal.enabled.
+
+
+ Accessing the return value
+ Less commonly, you may wish to perform an access-control check after the
+ method has been invoked. This can be achieved using the
+ @PostAuthorize annotation. To access the return value from a
+ method, use the built–in name returnObject in the
+ expression.
+ Filtering using @PreFilter and