Skip to content

Commit 4157ade

Browse files
Add hasScope and hasAnyScope for @PreAuthorize
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
1 parent b7fb289 commit 4157ade

File tree

4 files changed

+159
-1
lines changed

4 files changed

+159
-1
lines changed

core/src/main/java/org/springframework/security/access/expression/SecurityExpressionOperations.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,38 @@ public interface SecurityExpressionOperations {
8181
*/
8282
boolean hasAnyRole(String... roles);
8383

84+
/**
85+
* <p>
86+
* Determines if the {@link #getAuthentication()} has a particular authority within
87+
* {@link Authentication#getAuthorities()}.
88+
* </p>
89+
* <p>
90+
* This is similar to {@link #hasAuthority(String)} except that this method implies
91+
* that the String passed in is a scope. For example, if "read" is passed in the
92+
* implementation may convert it to use "SCOPE_read" instead. The way in which the
93+
* scope is converted may depend on the implementation settings.
94+
* </p>
95+
* @param scope the authority to test (i.e. "read")
96+
* @return true if the authority is found, else false
97+
*/
98+
boolean hasScope(String scope);
99+
100+
/**
101+
* <p>
102+
* Determines if the {@link #getAuthentication()} has any of the specified authorities
103+
* within {@link Authentication#getAuthorities()}.
104+
* </p>
105+
* <p>
106+
* This is similar to {@link #hasAnyAuthority(String...)} except that this method
107+
* implies that the String passed in is a scope. For example, if "read" is passed in
108+
* the implementation may convert it to use "SCOPE_read" instead. The way in which the
109+
* scope is converted may depend on the implementation settings.
110+
* </p>
111+
* @param scopes the authorities to test (i.e. "write", "read")
112+
* @return true if any of the authorities is found, else false
113+
*/
114+
boolean hasAnyScope(String... scopes);
115+
84116
/**
85117
* Always grants access.
86118
* @return true

core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public abstract class SecurityExpressionRoot<T extends @Nullable Object> impleme
4646

4747
private String defaultRolePrefix = "ROLE_";
4848

49+
private String defaultScopePrefix = "SCOPE_";
50+
4951
private final T object;
5052

5153
private AuthorizationManagerFactory<T> authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
@@ -165,6 +167,24 @@ public final boolean hasAllRoles(String... roles) {
165167
return isGranted(manager);
166168
}
167169

170+
@Override
171+
public final boolean hasScope(String scope) {
172+
assertScope(scope);
173+
return isGranted(this.authorizationManagerFactory.hasAuthority(this.defaultScopePrefix + scope));
174+
}
175+
176+
@Override
177+
public final boolean hasAnyScope(String... scopes) {
178+
Assert.notNull(scopes, "scopes cannot be null");
179+
if (this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T>) {
180+
for (int i = 0; i < scopes.length; i++) {
181+
assertScope(scopes[i]);
182+
scopes[i] = this.defaultScopePrefix + scopes[i];
183+
}
184+
}
185+
return isGranted(this.authorizationManagerFactory.hasAnyAuthority(scopes));
186+
}
187+
168188
@Override
169189
public final Authentication getAuthentication() {
170190
return this.authentication.get();
@@ -208,7 +228,8 @@ private boolean isGranted(AuthorizationManager<T> authorizationManager) {
208228
/**
209229
* Convenience method to access {@link Authentication#getPrincipal()} from
210230
* {@link #getAuthentication()}
211-
* @return
231+
* @return the {@code Principal} being authenticated or the authenticated principal
232+
* after authentication.
212233
*/
213234
public @Nullable Object getPrincipal() {
214235
return getAuthentication().getPrincipal();
@@ -304,4 +325,11 @@ public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
304325
this.permissionEvaluator = permissionEvaluator;
305326
}
306327

328+
private void assertScope(String scope) {
329+
Assert.notNull(scope, "scope cannot be null");
330+
Assert.isTrue(!scope.startsWith(this.defaultScopePrefix), () -> scope + " should not start with '"
331+
+ this.defaultScopePrefix + "' since '" + this.defaultScopePrefix
332+
+ "' is automatically prepended when using hasScope and hasAnyScope. Consider using AuthorityAuthorizationManager#hasAuthority or #hasAnyAuthority instead.");
333+
}
334+
307335
}

core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManagerTests.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,48 @@ public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws E
8989
assertThat(decision.isGranted()).isFalse();
9090
}
9191

92+
@Test
93+
public void checkSecuredScope() throws Exception {
94+
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
95+
"securedScope");
96+
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
97+
AuthorizationResult decision = manager.authorize(TestAuthentication::authenticatedUser, methodInvocation);
98+
assertThat(decision).isNotNull();
99+
assertThat(decision.isGranted()).isFalse();
100+
101+
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
102+
"SCOPE_read");
103+
decision = manager.authorize(authentication, methodInvocation);
104+
assertThat(decision).isNotNull();
105+
assertThat(decision.isGranted()).isFalse();
106+
107+
authentication = () -> new TestingAuthenticationToken("user", "password", "SCOPE_write");
108+
decision = manager.authorize(authentication, methodInvocation);
109+
assertThat(decision).isNotNull();
110+
assertThat(decision.isGranted()).isTrue();
111+
}
112+
113+
@Test
114+
public void checkSecuredAnyScope() throws Exception {
115+
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
116+
"securedAnyScope");
117+
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
118+
AuthorizationResult decision = manager.authorize(TestAuthentication::authenticatedUser, methodInvocation);
119+
assertThat(decision).isNotNull();
120+
assertThat(decision.isGranted()).isFalse();
121+
122+
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
123+
"SCOPE_read");
124+
decision = manager.authorize(authentication, methodInvocation);
125+
assertThat(decision).isNotNull();
126+
assertThat(decision.isGranted()).isTrue();
127+
128+
authentication = () -> new TestingAuthenticationToken("user", "password", "SCOPE_write");
129+
decision = manager.authorize(authentication, methodInvocation);
130+
assertThat(decision).isNotNull();
131+
assertThat(decision.isGranted()).isTrue();
132+
}
133+
92134
@Test
93135
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
94136
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
@@ -181,6 +223,16 @@ public void inheritedAnnotations() {
181223

182224
}
183225

226+
@PreAuthorize("hasScope('write')")
227+
public void securedScope() {
228+
229+
}
230+
231+
@PreAuthorize("hasAnyScope('write', 'read')")
232+
public void securedAnyScope() {
233+
234+
}
235+
184236
}
185237

186238
@PreAuthorize("hasRole('USER')")

core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManagerTests.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,42 @@ public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws E
9090
assertThat(decision.isGranted()).isFalse();
9191
}
9292

93+
@Test
94+
public void checkSecuredScope() throws Exception {
95+
MockMethodInvocation methodInvocation = new MockMethodInvocation(
96+
new PreAuthorizeAuthorizationManagerTests.TestClass(),
97+
PreAuthorizeAuthorizationManagerTests.TestClass.class, "securedScope");
98+
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
99+
Mono<Authentication> authentication = Mono
100+
.just(new TestingAuthenticationToken("user", "password", "SCOPE_read"));
101+
AuthorizationResult decision = manager.authorize(authentication, methodInvocation).block();
102+
assertThat(decision).isNotNull();
103+
assertThat(decision.isGranted()).isFalse();
104+
105+
authentication = Mono.just(new TestingAuthenticationToken("user", "password", "SCOPE_write"));
106+
decision = manager.authorize(authentication, methodInvocation).block();
107+
assertThat(decision).isNotNull();
108+
assertThat(decision.isGranted()).isTrue();
109+
}
110+
111+
@Test
112+
public void checkSecuredAnyScope() throws Exception {
113+
MockMethodInvocation methodInvocation = new MockMethodInvocation(
114+
new PreAuthorizeAuthorizationManagerTests.TestClass(),
115+
PreAuthorizeAuthorizationManagerTests.TestClass.class, "securedAnyScope");
116+
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager();
117+
Mono<Authentication> authentication = Mono
118+
.just(new TestingAuthenticationToken("user", "password", "SCOPE_read"));
119+
AuthorizationResult decision = manager.authorize(authentication, methodInvocation).block();
120+
assertThat(decision).isNotNull();
121+
assertThat(decision.isGranted()).isTrue();
122+
123+
authentication = Mono.just(new TestingAuthenticationToken("user", "password", "SCOPE_write"));
124+
decision = manager.authorize(authentication, methodInvocation).block();
125+
assertThat(decision).isNotNull();
126+
assertThat(decision.isGranted()).isTrue();
127+
}
128+
93129
@Test
94130
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
95131
Mono<Authentication> authentication = Mono
@@ -149,6 +185,16 @@ public void inheritedAnnotations() {
149185

150186
}
151187

188+
@PreAuthorize("hasScope('write')")
189+
public void securedScope() {
190+
191+
}
192+
193+
@PreAuthorize("hasAnyScope('write', 'read')")
194+
public void securedAnyScope() {
195+
196+
}
197+
152198
}
153199

154200
@PreAuthorize("hasRole('USER')")

0 commit comments

Comments
 (0)