diff --git a/config/src/test/java/org/springframework/security/config/method/Contact.java b/config/src/test/java/org/springframework/security/config/method/Contact.java
new file mode 100644
index 0000000000..12f3e433bd
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/method/Contact.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2015 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.config.method;
+
+/**
+ * @author Rob Winch
+ *
+ */
+public class Contact {
+ private String name;
+
+ /**
+ * @param name
+ */
+ public Contact(String name) {
+ super();
+ this.name = name;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+}
\ No newline at end of file
diff --git a/config/src/test/java/org/springframework/security/config/method/ContactPermission.java b/config/src/test/java/org/springframework/security/config/method/ContactPermission.java
new file mode 100644
index 0000000000..2a260aba8a
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/method/ContactPermission.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2015 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.config.method;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+
+/**
+ * @author Rob Winch
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@PreAuthorize("#contact.name == authentication.name")
+public @interface ContactPermission {}
diff --git a/config/src/test/java/org/springframework/security/config/method/PreAuthorizeAdminRole.java b/config/src/test/java/org/springframework/security/config/method/PreAuthorizeAdminRole.java
new file mode 100644
index 0000000000..c7c013685b
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/method/PreAuthorizeAdminRole.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2015 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.config.method;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+
+/**
+ * @author Rob Winch
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@PreAuthorize("hasRole('ADMIN')")
+public @interface PreAuthorizeAdminRole {
+
+}
diff --git a/config/src/test/java/org/springframework/security/config/method/PreAuthorizeServiceImpl.java b/config/src/test/java/org/springframework/security/config/method/PreAuthorizeServiceImpl.java
new file mode 100644
index 0000000000..91905c6654
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/method/PreAuthorizeServiceImpl.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2002-2015 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.config.method;
+
+/**
+ * @author Rob Winch
+ *
+ */
+public class PreAuthorizeServiceImpl {
+
+ @PreAuthorizeAdminRole
+ public void preAuthorizeAdminRole() {}
+
+ @ContactPermission
+ public void contactPermission(Contact contact) {}
+
+}
\ No newline at end of file
diff --git a/config/src/test/java/org/springframework/security/config/method/PreAuthorizeTests.java b/config/src/test/java/org/springframework/security/config/method/PreAuthorizeTests.java
new file mode 100644
index 0000000000..5f863916ec
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/method/PreAuthorizeTests.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2015 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.config.method;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class PreAuthorizeTests {
+ @Autowired
+ PreAuthorizeServiceImpl service;
+
+ @After
+ public void cleanup() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test(expected = AccessDeniedException.class)
+ public void preAuthorizeAdminRoleDenied() {
+ SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_USER"));
+ service.preAuthorizeAdminRole();
+ }
+
+ @Test
+ public void preAuthorizeAdminRoleGranted() {
+ SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN"));
+ service.preAuthorizeAdminRole();
+ }
+
+ @Test
+ public void preAuthorizeContactPermissionGranted() {
+ SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN"));
+ service.contactPermission(new Contact("user"));
+ }
+
+ @Test(expected = AccessDeniedException.class)
+ public void preAuthorizeContactPermissionDenied() {
+ SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN"));
+ service.contactPermission(new Contact("admin"));
+ }
+}
\ No newline at end of file
diff --git a/config/src/test/java/org/springframework/security/config/method/SecuredAdminRole.java b/config/src/test/java/org/springframework/security/config/method/SecuredAdminRole.java
new file mode 100644
index 0000000000..60df1d01e2
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/method/SecuredAdminRole.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2015 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.config.method;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.springframework.security.access.annotation.Secured;
+
+/**
+ * @author Rob Winch
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Secured("ROLE_ADMIN")
+public @interface SecuredAdminRole { }
\ No newline at end of file
diff --git a/config/src/test/java/org/springframework/security/config/method/SecuredServiceImpl.java b/config/src/test/java/org/springframework/security/config/method/SecuredServiceImpl.java
new file mode 100644
index 0000000000..73bd991649
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/method/SecuredServiceImpl.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2015 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.config.method;
+
+/**
+*
+* @author Rob Winch
+*
+*/
+public class SecuredServiceImpl {
+ @SecuredAdminRole
+ public void securedAdminRole() {}
+}
diff --git a/config/src/test/java/org/springframework/security/config/method/SecuredTests.java b/config/src/test/java/org/springframework/security/config/method/SecuredTests.java
new file mode 100644
index 0000000000..9983039541
--- /dev/null
+++ b/config/src/test/java/org/springframework/security/config/method/SecuredTests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2015 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.config.method;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ *
+ * @author Rob Winch
+ *
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class SecuredTests {
+ @Autowired
+ SecuredServiceImpl service;
+
+ @After
+ public void cleanup() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test(expected = AccessDeniedException.class)
+ public void securedAdminRoleDenied() {
+ SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_USER"));
+ service.securedAdminRole();
+ }
+
+ @Test
+ public void securedAdminRoleGranted() {
+ SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN"));
+ service.securedAdminRole();
+ }
+}
\ No newline at end of file
diff --git a/config/src/test/resources/org/springframework/security/config/method/PreAuthorizeTests-context.xml b/config/src/test/resources/org/springframework/security/config/method/PreAuthorizeTests-context.xml
new file mode 100644
index 0000000000..a28e01a6e5
--- /dev/null
+++ b/config/src/test/resources/org/springframework/security/config/method/PreAuthorizeTests-context.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/src/test/resources/org/springframework/security/config/method/SecuredTests-context.xml b/config/src/test/resources/org/springframework/security/config/method/SecuredTests-context.xml
new file mode 100644
index 0000000000..d4b5c1391a
--- /dev/null
+++ b/config/src/test/resources/org/springframework/security/config/method/SecuredTests-context.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc
index f5f9f6c686..21ea16f36c 100644
--- a/docs/manual/src/docs/asciidoc/index.adoc
+++ b/docs/manual/src/docs/asciidoc/index.adoc
@@ -369,7 +369,9 @@ This will give you access to the entire project history (including all releases
[[new]]
== What's new in Spring Security 4.1
-* <>
+* Meta Annotation Support
+** <>
+** <>
=== What's new in Spring Security 4.0
@@ -4727,6 +4729,29 @@ To use `hasPermission()` expressions, you have to explicitly configure a `Permis
Where `myPermissionEvaluator` is the bean which implements `PermissionEvaluator`. Usually this will be the implementation from the ACL module which is called`AclPermissionEvaluator`. See the "Contacts" sample application configuration for more details.
+===== Method Security Meta Annotations
+
+You can make use of meta annotations for method security to make your code more readable.
+This is especially convenient if you find that you are repeating the same complex expression throughout your code base.
+For example, consider the following:
+
+[source,java]
+----
+@PreAuthorize("#contact.name == authentication.name")
+----
+
+Instead of repeating this everywhere, we can create a meta annotation that can be used instead.
+
+[source,java]
+----
+@Retention(RetentionPolicy.RUNTIME)
+@PreAuthorize("#contact.name == authentication.name")
+public @interface ContactPermission {}
+----
+
+Meta annotations can be used for any of the Spring Security method security annotations.
+In order to remain compliant with the specification JSR-250 annotations do not support meta annotations.
+
[[advanced-topics]]
= Additional Topics
In this part we cover features which require a knowledge of previous chapters as well as some of the more advanced and less-commonly used features of the framework.