Skip to content

Commit 9c94c35

Browse files
Add hasScope and hasAnyScope
Signed-off-by: Tran Ngoc Nhan <[email protected]>
1 parent 4157ade commit 9c94c35

File tree

3 files changed

+232
-0
lines changed

3 files changed

+232
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.core.authorization;
17+
18+
import org.springframework.security.authorization.AuthorizationManager;
19+
import org.springframework.security.authorization.AuthorizationManagerFactory;
20+
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
21+
import org.springframework.util.Assert;
22+
23+
/**
24+
* A factory for creating different kinds of {@link AuthorizationManager} instances.
25+
*
26+
* @param <T> the type of object that the authorization check is being done on
27+
* @author Ngoc Nhan
28+
* @since 7.0
29+
*/
30+
public final class DefaultOAuth2AuthorizationManagerFactory<T> implements OAuth2AuthorizationManagerFactory<T> {
31+
32+
private String scopePrefix = "SCOPE_";
33+
34+
private final AuthorizationManagerFactory<T> authorizationManagerFactory;
35+
36+
public DefaultOAuth2AuthorizationManagerFactory() {
37+
this(new DefaultAuthorizationManagerFactory<>());
38+
}
39+
40+
public DefaultOAuth2AuthorizationManagerFactory(AuthorizationManagerFactory<T> authorizationManagerFactory) {
41+
Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory can not be null");
42+
this.authorizationManagerFactory = authorizationManagerFactory;
43+
}
44+
45+
/**
46+
* Sets the prefix used to create an authority name from a scope name. Can be an empty
47+
* string.
48+
* @param scopePrefix the scope prefix to use
49+
*/
50+
public void setScopePrefix(String scopePrefix) {
51+
Assert.notNull(scopePrefix, "scopePrefix can not be null");
52+
this.scopePrefix = scopePrefix;
53+
}
54+
55+
@Override
56+
public AuthorizationManager<T> hasScope(String scope) {
57+
Assert.notNull(scope, "scope can not be null");
58+
return hasAnyScope(scope);
59+
}
60+
61+
@Override
62+
public AuthorizationManager<T> hasAnyScope(String... scopes) {
63+
Assert.notNull(scopes, "scopes can not be null");
64+
String[] mappedScopes = new String[scopes.length];
65+
for (int i = 0; i < scopes.length; i++) {
66+
assertScope(scopes[i]);
67+
mappedScopes[i] = this.scopePrefix + scopes[i];
68+
}
69+
return this.authorizationManagerFactory.hasAnyAuthority(mappedScopes);
70+
}
71+
72+
private void assertScope(String scope) {
73+
Assert.isTrue(!scope.startsWith(this.scopePrefix), () -> scope + " should not start with '" + this.scopePrefix
74+
+ "' since '" + this.scopePrefix
75+
+ "' is automatically prepended when using hasScope and hasAnyScope. Consider using AuthorityAuthorizationManager#hasAuthority or #hasAnyAuthority instead.");
76+
}
77+
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.core.authorization;
17+
18+
import org.springframework.security.authorization.AuthorizationManager;
19+
import org.springframework.security.core.Authentication;
20+
21+
/**
22+
* A factory for creating different kinds of {@link AuthorizationManager} instances.
23+
*
24+
* @param <T> the type of object that the authorization check is being done on
25+
* @author Ngoc Nhan
26+
* @since 7.0
27+
*/
28+
public interface OAuth2AuthorizationManagerFactory<T> {
29+
30+
/**
31+
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
32+
* have a {@code SCOPE_scope} authority.
33+
*
34+
* <p>
35+
* For example, if you call {@code hasScope("read")}, then this will require that each
36+
* authentication have a {@link org.springframework.security.core.GrantedAuthority}
37+
* whose value is {@code SCOPE_read}.
38+
*
39+
* <p>
40+
* This would equivalent to calling
41+
* {@code AuthorityAuthorizationManager#hasAuthority("SCOPE_read")}.
42+
* @param scope the scope value to require
43+
* @return an {@link AuthorizationManager} that requires a {@code "SCOPE_scope"}
44+
* authority
45+
*/
46+
default AuthorizationManager<T> hasScope(String scope) {
47+
return OAuth2AuthorizationManagers.hasScope(scope);
48+
}
49+
50+
/**
51+
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
52+
* have at least one authority among {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
53+
* {@code SCOPE_scopeN}.
54+
*
55+
* <p>
56+
* For example, if you call {@code hasAnyScope("read", "write")}, then this will
57+
* require that each authentication have at least a
58+
* {@link org.springframework.security.core.GrantedAuthority} whose value is either
59+
* {@code SCOPE_read} or {@code SCOPE_write}.
60+
*
61+
* <p>
62+
* This would equivalent to calling
63+
* {@code AuthorityAuthorizationManager#hasAnyAuthority("SCOPE_read", "SCOPE_write")}.
64+
* @param scopes the scope values to allow
65+
* @return an {@link AuthorizationManager} that requires at least one authority among
66+
* {@code "SCOPE_scope1"}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
67+
*/
68+
default AuthorizationManager<T> hasAnyScope(String... scopes) {
69+
return OAuth2AuthorizationManagers.hasAnyScope(scopes);
70+
}
71+
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.core.authorization;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import org.junit.jupiter.api.Test;
21+
import org.springframework.security.authentication.TestingAuthenticationToken;
22+
import org.springframework.security.authorization.AuthorityAuthorizationManager;
23+
import org.springframework.security.authorization.AuthorizationManager;
24+
import org.springframework.security.authorization.AuthorizationManagerFactories;
25+
import org.springframework.security.authorization.AuthorizationResult;
26+
27+
/**
28+
* Tests for {@link OAuth2AuthorizationManagerFactory}.
29+
*
30+
* @author Ngoc Nhan
31+
*/
32+
public class OAuth2AuthorizationManagerFactoryTests {
33+
34+
@Test
35+
public void hasScopeReturnsAuthorityAuthorizationManagerByDefault() {
36+
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
37+
AuthorizationManager<String> authorizationManager = factory.hasScope("message:read");
38+
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
39+
}
40+
41+
@Test
42+
public void hasAnyScopeReturnsAuthorityAuthorizationManagerByDefault() {
43+
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
44+
AuthorizationManager<String> authorizationManager = factory.hasAnyScope("message:read", "message:write");
45+
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
46+
}
47+
48+
@Test
49+
public void hasScopeWhenSetAuthorizationManagerFactories() {
50+
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
51+
AuthorizationManagerFactories.<String>multiFactor().requireFactors("SCOPE_message:read").build());
52+
assertUserGranted(factory.hasScope("message:read"));
53+
assertUserDenied(factory.hasScope("message:write"));
54+
}
55+
56+
@Test
57+
public void hasAnyScopeWhenSetAuthorizationManagerFactories() {
58+
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
59+
AuthorizationManagerFactories.<String>multiFactor().requireFactors("SCOPE_message:read").build());
60+
assertUserGranted(factory.hasAnyScope("message:read"));
61+
assertUserDenied(factory.hasAnyScope("message:write"));
62+
}
63+
64+
private void assertUserGranted(AuthorizationManager<String> manager) {
65+
AuthorizationResult authorizationResult = createAuthorizationResult(manager);
66+
assertThat(authorizationResult).isNotNull();
67+
assertThat(authorizationResult.isGranted()).isTrue();
68+
}
69+
70+
private void assertUserDenied(AuthorizationManager<String> manager) {
71+
AuthorizationResult authorizationResult = createAuthorizationResult(manager);
72+
assertThat(authorizationResult).isNotNull();
73+
assertThat(authorizationResult.isGranted()).isFalse();
74+
}
75+
76+
private AuthorizationResult createAuthorizationResult(AuthorizationManager<String> manager) {
77+
TestingAuthenticationToken authenticatedUser = new TestingAuthenticationToken("user", "pass",
78+
"SCOPE_message:read");
79+
return manager.authorize(() -> authenticatedUser, "");
80+
}
81+
82+
}

0 commit comments

Comments
 (0)