From a09756745fd95bcfa8c05eb977227ef06d929ad4 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Fri, 27 Sep 2013 11:18:37 -0500 Subject: [PATCH] SEC-2151: Support binding method arguments with Annotations This allow utilizing method arguments for method access control on interfaces prior to JDK 8. --- ...balMethodSecurityConfigurationTests.groovy | 33 +++ .../MethodSecurityService.groovy | 4 + .../MethodSecurityServiceImpl.groovy | 5 + .../security/access/method/P.java | 47 ++++ .../AnnotationParameterNameDiscoverer.java | 228 ++++++++++++++++++ ...efaultSecurityParameterNameDiscoverer.java | 21 ++ ...nnotationParameterNameDiscovererTests.java | 106 ++++++++ ...tSecurityParameterNameDiscovererTests.java | 25 +- docs/manual/src/docbook/el-access.xml | 114 ++++++--- 9 files changed, 547 insertions(+), 36 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/access/method/P.java create mode 100644 core/src/main/java/org/springframework/security/core/parameters/AnnotationParameterNameDiscoverer.java create mode 100644 core/src/test/java/org/springframework/security/core/parameters/AnnotationParameterNameDiscovererTests.java 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: + * + *
+ * import org.springframework.security.access.method.P;
+ *
+ * @PostAuthorize("#to == returnObject.to")
+ * public Message findMessageByTo(@P("to") String to);
+ * 
+ * + * We can make this possible using the following + * {@link AnnotationParameterNameDiscoverer}: + * + *
+ * ParameterAnnotationsNameDiscoverer discoverer = new ParameterAnnotationsNameDiscoverer(
+ * 		"org.springframework.security.access.method.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. + *

+ * + *

+ * 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}.
  • @@ -40,6 +46,8 @@ import org.springframework.util.ClassUtils; * {@link LocalVariableTableParameterNameDiscoverer} is added directly. *
* + * @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 paramNameDiscoverClass = (Class) 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 <literal>@PreFilter</literal> and