mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-02-24 22:25:18 +00:00
Support hasScope in Method Security
Closes gh-18013 Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
This commit is contained in:
parent
8652950fb2
commit
f2b7cb2de5
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2025-present 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
|
||||
*
|
||||
* https://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.oauth2.core.authorization;
|
||||
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactory;
|
||||
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A factory for creating different kinds of {@link AuthorizationManager} instances.
|
||||
*
|
||||
* @param <T> the type of object that the authorization check is being done on
|
||||
* @author Ngoc Nhan
|
||||
* @since 7.1
|
||||
*/
|
||||
public final class DefaultOAuth2AuthorizationManagerFactory<T> implements OAuth2AuthorizationManagerFactory<T> {
|
||||
|
||||
private String scopePrefix = "SCOPE_";
|
||||
|
||||
private final AuthorizationManagerFactory<T> authorizationManagerFactory;
|
||||
|
||||
public DefaultOAuth2AuthorizationManagerFactory() {
|
||||
this(new DefaultAuthorizationManagerFactory<>());
|
||||
}
|
||||
|
||||
public DefaultOAuth2AuthorizationManagerFactory(AuthorizationManagerFactory<T> authorizationManagerFactory) {
|
||||
Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory can not be null");
|
||||
this.authorizationManagerFactory = authorizationManagerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix used to create an authority name from a scope name. Can be an empty
|
||||
* string.
|
||||
* @param scopePrefix the scope prefix to use
|
||||
*/
|
||||
public void setScopePrefix(String scopePrefix) {
|
||||
Assert.notNull(scopePrefix, "scopePrefix can not be null");
|
||||
this.scopePrefix = scopePrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationManager<T> hasScope(String scope) {
|
||||
Assert.notNull(scope, "scope can not be null");
|
||||
assertScope(scope);
|
||||
return this.authorizationManagerFactory.hasAuthority(this.scopePrefix + scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationManager<T> hasAnyScope(String... scopes) {
|
||||
return this.authorizationManagerFactory.hasAnyAuthority(this.mappedScopes(scopes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationManager<T> hasAllScopes(String... scopes) {
|
||||
return this.authorizationManagerFactory.hasAllAuthorities(this.mappedScopes(scopes));
|
||||
}
|
||||
|
||||
private String[] mappedScopes(String... scopes) {
|
||||
Assert.notNull(scopes, "scopes can not be null");
|
||||
String[] mappedScopes = new String[scopes.length];
|
||||
for (int i = 0; i < scopes.length; i++) {
|
||||
assertScope(scopes[i]);
|
||||
mappedScopes[i] = this.scopePrefix + scopes[i];
|
||||
}
|
||||
return mappedScopes;
|
||||
}
|
||||
|
||||
private void assertScope(String scope) {
|
||||
Assert.isTrue(!scope.startsWith(this.scopePrefix), () -> scope + " should not start with '" + this.scopePrefix
|
||||
+ "' since '" + this.scopePrefix
|
||||
+ "' is automatically prepended when using hasScope and hasAnyScope. Consider using AuthorizationManagerFactory#hasAuthority or #hasAnyAuthority instead.");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2025-present 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
|
||||
*
|
||||
* https://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.oauth2.core.authorization;
|
||||
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* A factory for creating different kinds of {@link AuthorizationManager} instances.
|
||||
*
|
||||
* @param <T> the type of object that the authorization check is being done on
|
||||
* @author Ngoc Nhan
|
||||
* @since 7.1
|
||||
*/
|
||||
public interface OAuth2AuthorizationManagerFactory<T> {
|
||||
|
||||
/**
|
||||
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
|
||||
* have a {@code SCOPE_scope} authority.
|
||||
*
|
||||
* <p>
|
||||
* For example, if you call {@code hasScope("read")}, then this will require that each
|
||||
* authentication have a {@link org.springframework.security.core.GrantedAuthority}
|
||||
* whose value is {@code SCOPE_read}.
|
||||
*
|
||||
* <p>
|
||||
* This would equivalent to calling
|
||||
* {@code AuthorityAuthorizationManager#hasAuthority("SCOPE_read")}.
|
||||
* @param scope the scope value to require
|
||||
* @return an {@link AuthorizationManager} that requires a {@code "SCOPE_scope"}
|
||||
* authority
|
||||
*/
|
||||
default AuthorizationManager<T> hasScope(String scope) {
|
||||
return OAuth2AuthorizationManagers.hasScope(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
|
||||
* have at least one authority among {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
|
||||
* {@code SCOPE_scopeN}.
|
||||
*
|
||||
* <p>
|
||||
* For example, if you call {@code hasAnyScope("read", "write")}, then this will
|
||||
* require that each authentication have at least a
|
||||
* {@link org.springframework.security.core.GrantedAuthority} whose value is either
|
||||
* {@code SCOPE_read} or {@code SCOPE_write}.
|
||||
*
|
||||
* <p>
|
||||
* This would equivalent to calling
|
||||
* {@code AuthorityAuthorizationManager#hasAnyAuthority("SCOPE_read", "SCOPE_write")}.
|
||||
* @param scopes the scope values to allow
|
||||
* @return an {@link AuthorizationManager} that requires at least one authority among
|
||||
* {@code "SCOPE_scope1"}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
|
||||
*/
|
||||
default AuthorizationManager<T> hasAnyScope(String... scopes) {
|
||||
return OAuth2AuthorizationManagers.hasAnyScope(scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
|
||||
* have all authorities {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
|
||||
* {@code SCOPE_scopeN}.
|
||||
*
|
||||
* <p>
|
||||
* For example, if you call {@code hasAllScopes("read", "write")}, then each
|
||||
* {@link org.springframework.security.core.Authentication} must have all
|
||||
* {@link org.springframework.security.core.GrantedAuthority} values of
|
||||
* {@code SCOPE_read} and {@code SCOPE_write}.
|
||||
*
|
||||
* <p>
|
||||
* This would be equivalent to calling
|
||||
* {@code AllAuthoritiesAuthorizationManager#hasAllAuthorities("SCOPE_read", "SCOPE_write")}.
|
||||
* @param scopes the scope values to require
|
||||
* @return an {@link AuthorizationManager} that requires all authorities
|
||||
* {@code SCOPE_scope1}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
|
||||
*/
|
||||
default AuthorizationManager<T> hasAllScopes(String... scopes) {
|
||||
return OAuth2AuthorizationManagers.hasAllScopes(scopes);
|
||||
}
|
||||
|
||||
}
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.oauth2.core.authorization;
|
||||
|
||||
import org.springframework.security.authorization.AllAuthoritiesAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -28,6 +29,7 @@ import org.springframework.util.Assert;
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see AuthorityAuthorizationManager
|
||||
* @see AllAuthoritiesAuthorizationManager
|
||||
*/
|
||||
public final class OAuth2AuthorizationManagers {
|
||||
|
||||
@ -85,6 +87,34 @@ public final class OAuth2AuthorizationManagers {
|
||||
return AuthorityAuthorizationManager.hasAnyAuthority(mappedScopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
|
||||
* have all authorities {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
|
||||
* {@code SCOPE_scopeN}.
|
||||
*
|
||||
* <p>
|
||||
* For example, if you call {@code hasAllScopes("read", "write")}, then each
|
||||
* {@link org.springframework.security.core.Authentication} must have all
|
||||
* {@link org.springframework.security.core.GrantedAuthority} values of
|
||||
* {@code SCOPE_read} and {@code SCOPE_write}.
|
||||
*
|
||||
* <p>
|
||||
* This would be equivalent to calling
|
||||
* {@code AllAuthoritiesAuthorizationManager#hasAllAuthorities("SCOPE_read", "SCOPE_write")}.
|
||||
* @param scopes the scope values to require
|
||||
* @return an {@link AuthorizationManager} that requires all authorities
|
||||
* {@code SCOPE_scope1}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
|
||||
* @since 7.1
|
||||
*/
|
||||
public static <T> AuthorizationManager<T> hasAllScopes(String... scopes) {
|
||||
String[] mappedScopes = new String[scopes.length];
|
||||
for (int i = 0; i < scopes.length; i++) {
|
||||
assertScope(scopes[i]);
|
||||
mappedScopes[i] = "SCOPE_" + scopes[i];
|
||||
}
|
||||
return AllAuthoritiesAuthorizationManager.hasAllAuthorities(mappedScopes);
|
||||
}
|
||||
|
||||
private static void assertScope(String scope) {
|
||||
Assert.isTrue(!scope.startsWith("SCOPE_"),
|
||||
() -> scope + " should not start with SCOPE_ since SCOPE_"
|
||||
|
||||
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2025-present 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
|
||||
*
|
||||
* https://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.oauth2.core.authorization;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactories;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AuthorizationManagerFactory}.
|
||||
*
|
||||
* @author Ngoc Nhan
|
||||
*/
|
||||
public class OAuth2AuthorizationManagerFactoryTests {
|
||||
|
||||
private static final String MSG_READ = "message:read";
|
||||
|
||||
private static final String MSG_WRITE = "message:write";
|
||||
|
||||
private static final String SCOPE_MSG_READ = "SCOPE_message:read";
|
||||
|
||||
private static final String SCOPE_MSG_WRITE = "SCOPE_message:write";
|
||||
|
||||
@Test
|
||||
public void hasScopeReturnsAuthorityAuthorizationManagerByDefault() {
|
||||
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
|
||||
AuthorizationManager<String> authorizationManager = factory.hasScope(MSG_READ);
|
||||
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAnyScopeReturnsAuthorityAuthorizationManagerByDefault() {
|
||||
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
|
||||
AuthorizationManager<String> authorizationManager = factory.hasAnyScope(MSG_READ, MSG_WRITE);
|
||||
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAllScopesReturnsAuthorityAuthorizationManagerByDefault() {
|
||||
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
|
||||
AuthorizationManager<String> authorizationManager = factory.hasAnyScope(MSG_READ, MSG_WRITE);
|
||||
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasScopeWhenSetAuthorizationManagerFactories() {
|
||||
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
|
||||
AuthorizationManagerFactories.<String>multiFactor().requireFactors(SCOPE_MSG_READ).build());
|
||||
assertUserGranted(factory.hasScope(MSG_READ), SCOPE_MSG_READ);
|
||||
assertUserDenied(factory.hasScope(MSG_WRITE), SCOPE_MSG_READ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAnyScopeWhenSetAuthorizationManagerFactories() {
|
||||
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
|
||||
AuthorizationManagerFactories.<String>multiFactor().requireFactors(SCOPE_MSG_READ).build());
|
||||
assertUserGranted(factory.hasAnyScope(MSG_READ), SCOPE_MSG_READ);
|
||||
assertUserDenied(factory.hasAnyScope(MSG_WRITE), SCOPE_MSG_READ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAllScopesWhenSetAuthorizationManagerFactories() {
|
||||
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
|
||||
AuthorizationManagerFactories.<String>multiFactor()
|
||||
.requireFactors(SCOPE_MSG_READ, SCOPE_MSG_WRITE)
|
||||
.build());
|
||||
assertUserGranted(factory.hasAllScopes(MSG_READ, MSG_WRITE), SCOPE_MSG_READ, SCOPE_MSG_WRITE);
|
||||
assertUserDenied(factory.hasAllScopes(MSG_READ, MSG_WRITE), SCOPE_MSG_READ);
|
||||
}
|
||||
|
||||
private void assertUserGranted(AuthorizationManager<String> manager, String... authorities) {
|
||||
AuthorizationResult authorizationResult = createAuthorizationResult(manager, authorities);
|
||||
assertThat(authorizationResult).isNotNull();
|
||||
assertThat(authorizationResult.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
private void assertUserDenied(AuthorizationManager<String> manager, String... authorities) {
|
||||
AuthorizationResult authorizationResult = createAuthorizationResult(manager, authorities);
|
||||
assertThat(authorizationResult).isNotNull();
|
||||
assertThat(authorizationResult.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
private AuthorizationResult createAuthorizationResult(AuthorizationManager<String> manager, String... authorities) {
|
||||
TestingAuthenticationToken authenticatedUser = new TestingAuthenticationToken("user", "pass", authorities);
|
||||
return manager.authorize(() -> authenticatedUser, "");
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user