diff --git a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionOperations.java b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionOperations.java
index f63f131585f..8776b0c7709 100644
--- a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionOperations.java
+++ b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionOperations.java
@@ -81,6 +81,38 @@ public interface SecurityExpressionOperations {
*/
boolean hasAnyRole(String... roles);
+ /**
+ *
+ * Determines if the {@link #getAuthentication()} has a particular authority within
+ * {@link Authentication#getAuthorities()}.
+ *
+ *
+ * This is similar to {@link #hasAuthority(String)} except that this method implies
+ * that the String passed in is a scope. For example, if "read" is passed in the
+ * implementation may convert it to use "SCOPE_read" instead. The way in which the
+ * scope is converted may depend on the implementation settings.
+ *
+ * @param scope the authority to test (i.e. "read")
+ * @return true if the authority is found, else false
+ */
+ boolean hasScope(String scope);
+
+ /**
+ *
+ * Determines if the {@link #getAuthentication()} has any of the specified authorities
+ * within {@link Authentication#getAuthorities()}.
+ *
+ *
+ * This is similar to {@link #hasAnyAuthority(String...)} except that this method
+ * implies that the String passed in is a scope. For example, if "read" is passed in
+ * the implementation may convert it to use "SCOPE_read" instead. The way in which the
+ * scope is converted may depend on the implementation settings.
+ *
+ * @param scopes the authorities to test (i.e. "write", "read")
+ * @return true if any of the authorities is found, else false
+ */
+ boolean hasAnyScope(String... scopes);
+
/**
* Always grants access.
* @return true
diff --git a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java
index e0c35f7c2d3..e1fa6a97d79 100644
--- a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java
+++ b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java
@@ -46,6 +46,8 @@ public abstract class SecurityExpressionRoot impleme
private String defaultRolePrefix = "ROLE_";
+ private String defaultScopePrefix = "SCOPE_";
+
private final T object;
private AuthorizationManagerFactory authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
@@ -165,6 +167,24 @@ public final boolean hasAllRoles(String... roles) {
return isGranted(manager);
}
+ @Override
+ public final boolean hasScope(String scope) {
+ assertScope(scope);
+ return isGranted(this.authorizationManagerFactory.hasAuthority(this.defaultScopePrefix + scope));
+ }
+
+ @Override
+ public final boolean hasAnyScope(String... scopes) {
+ Assert.notNull(scopes, "scopes cannot be null");
+ if (this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory) {
+ for (int i = 0; i < scopes.length; i++) {
+ assertScope(scopes[i]);
+ scopes[i] = this.defaultScopePrefix + scopes[i];
+ }
+ }
+ return isGranted(this.authorizationManagerFactory.hasAnyAuthority(scopes));
+ }
+
@Override
public final Authentication getAuthentication() {
return this.authentication.get();
@@ -208,7 +228,8 @@ private boolean isGranted(AuthorizationManager authorizationManager) {
/**
* Convenience method to access {@link Authentication#getPrincipal()} from
* {@link #getAuthentication()}
- * @return
+ * @return the {@code Principal} being authenticated or the authenticated principal
+ * after authentication.
*/
public @Nullable Object getPrincipal() {
return getAuthentication().getPrincipal();
@@ -304,4 +325,11 @@ public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
+ private void assertScope(String scope) {
+ Assert.notNull(scope, "scope cannot be null");
+ Assert.isTrue(!scope.startsWith(this.defaultScopePrefix), () -> scope + " should not start with '"
+ + this.defaultScopePrefix + "' since '" + this.defaultScopePrefix
+ + "' is automatically prepended when using hasScope and hasAnyScope. Consider using AuthorityAuthorizationManager#hasAuthority or #hasAnyAuthority instead.");
+ }
+
}
diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java
index b9439ca3d60..7f7498c3196 100644
--- a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java
+++ b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java
@@ -89,6 +89,48 @@ public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws E
assertThat(decision.isGranted()).isFalse();
}
+ @Test
+ public void checkSecuredScope() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "securedScope");
+ PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
+ AuthorizationResult decision = manager.authorize(TestAuthentication::authenticatedUser, methodInvocation);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isFalse();
+
+ Supplier authentication = () -> new TestingAuthenticationToken("user", "password",
+ "SCOPE_read");
+ decision = manager.authorize(authentication, methodInvocation);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isFalse();
+
+ authentication = () -> new TestingAuthenticationToken("user", "password", "SCOPE_write");
+ decision = manager.authorize(authentication, methodInvocation);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+ }
+
+ @Test
+ public void checkSecuredAnyScope() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
+ "securedAnyScope");
+ PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
+ AuthorizationResult decision = manager.authorize(TestAuthentication::authenticatedUser, methodInvocation);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isFalse();
+
+ Supplier authentication = () -> new TestingAuthenticationToken("user", "password",
+ "SCOPE_read");
+ decision = manager.authorize(authentication, methodInvocation);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+
+ authentication = () -> new TestingAuthenticationToken("user", "password", "SCOPE_write");
+ decision = manager.authorize(authentication, methodInvocation);
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+ }
+
@Test
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
@@ -181,6 +223,16 @@ public void inheritedAnnotations() {
}
+ @PreAuthorize("hasScope('write')")
+ public void securedScope() {
+
+ }
+
+ @PreAuthorize("hasAnyScope('write', 'read')")
+ public void securedAnyScope() {
+
+ }
+
}
@PreAuthorize("hasRole('USER')")
diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManagerTests.java
index bb0d003e3d9..f320bb51c09 100644
--- a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManagerTests.java
+++ b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManagerTests.java
@@ -90,6 +90,42 @@ public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws E
assertThat(decision.isGranted()).isFalse();
}
+ @Test
+ public void checkSecuredScope() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(
+ new PreAuthorizeAuthorizationManagerTests.TestClass(),
+ PreAuthorizeAuthorizationManagerTests.TestClass.class, "securedScope");
+ PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
+ Mono authentication = Mono
+ .just(new TestingAuthenticationToken("user", "password", "SCOPE_read"));
+ AuthorizationResult decision = manager.authorize(authentication, methodInvocation).block();
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isFalse();
+
+ authentication = Mono.just(new TestingAuthenticationToken("user", "password", "SCOPE_write"));
+ decision = manager.authorize(authentication, methodInvocation).block();
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+ }
+
+ @Test
+ public void checkSecuredAnyScope() throws Exception {
+ MockMethodInvocation methodInvocation = new MockMethodInvocation(
+ new PreAuthorizeAuthorizationManagerTests.TestClass(),
+ PreAuthorizeAuthorizationManagerTests.TestClass.class, "securedAnyScope");
+ PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
+ Mono authentication = Mono
+ .just(new TestingAuthenticationToken("user", "password", "SCOPE_read"));
+ AuthorizationResult decision = manager.authorize(authentication, methodInvocation).block();
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+
+ authentication = Mono.just(new TestingAuthenticationToken("user", "password", "SCOPE_write"));
+ decision = manager.authorize(authentication, methodInvocation).block();
+ assertThat(decision).isNotNull();
+ assertThat(decision.isGranted()).isTrue();
+ }
+
@Test
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
Mono authentication = Mono
@@ -149,6 +185,16 @@ public void inheritedAnnotations() {
}
+ @PreAuthorize("hasScope('write')")
+ public void securedScope() {
+
+ }
+
+ @PreAuthorize("hasAnyScope('write', 'read')")
+ public void securedAnyScope() {
+
+ }
+
}
@PreAuthorize("hasRole('USER')")