Browse Source

SEC-2151: Support binding method arguments with Annotations

This allow utilizing method arguments for method access control on
interfaces prior to JDK 8.
Rob Winch 12 years ago
parent
commit
a09756745f

+ 33 - 0
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()
+        }
+    }
 }

+ 4 - 0
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);
 }

+ 5 - 0
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;
+    }
 }

+ 47 - 0
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();
+}

+ 228 - 0
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:
+ *
+ * <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(
+ * 		&quot;org.springframework.security.access.method.P&quot;);
+ * </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);
+    }
+}

+ 21 - 0
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.
  *
  * <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

+ 106 - 0
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; }
+    }
+}

+ 21 - 4
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<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);
     }
 }

+ 82 - 32
docs/manual/src/docbook/el-access.xml

@@ -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