mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-13 05:43:29 +00:00
Add AuthorizeReturnObject Spring Data Hints
Issue gh-15709
This commit is contained in:
parent
fd5d03d384
commit
e29058c7e4
@ -21,6 +21,7 @@ dependencies {
|
|||||||
api 'org.springframework:spring-context'
|
api 'org.springframework:spring-context'
|
||||||
api 'org.springframework:spring-core'
|
api 'org.springframework:spring-core'
|
||||||
|
|
||||||
|
optional project(':spring-security-data')
|
||||||
optional project(':spring-security-ldap')
|
optional project(':spring-security-ldap')
|
||||||
optional project(':spring-security-messaging')
|
optional project(':spring-security-messaging')
|
||||||
optional project(path: ':spring-security-saml2-service-provider')
|
optional project(path: ':spring-security-saml2-service-provider')
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 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.config.annotation.method.configuration;
|
||||||
|
|
||||||
|
import org.springframework.aop.framework.AopInfrastructureBean;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Role;
|
||||||
|
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
|
||||||
|
import org.springframework.security.authorization.AuthorizationProxyFactory;
|
||||||
|
import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar;
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
final class AuthorizationProxyDataConfiguration implements AopInfrastructureBean {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
static SecurityHintsRegistrar authorizeReturnObjectDataHintsRegistrar(AuthorizationProxyFactory proxyFactory) {
|
||||||
|
return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,6 +26,7 @@ import org.springframework.context.annotation.AutoProxyRegistrar;
|
|||||||
import org.springframework.context.annotation.ImportSelector;
|
import org.springframework.context.annotation.ImportSelector;
|
||||||
import org.springframework.core.type.AnnotationMetadata;
|
import org.springframework.core.type.AnnotationMetadata;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically determines which imports to include using the {@link EnableMethodSecurity}
|
* Dynamically determines which imports to include using the {@link EnableMethodSecurity}
|
||||||
@ -37,6 +38,9 @@ import org.springframework.lang.NonNull;
|
|||||||
*/
|
*/
|
||||||
final class MethodSecuritySelector implements ImportSelector {
|
final class MethodSecuritySelector implements ImportSelector {
|
||||||
|
|
||||||
|
private static final boolean isDataPresent = ClassUtils
|
||||||
|
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
|
||||||
|
|
||||||
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
|
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -57,6 +61,9 @@ final class MethodSecuritySelector implements ImportSelector {
|
|||||||
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
|
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
|
||||||
}
|
}
|
||||||
imports.add(AuthorizationProxyConfiguration.class.getName());
|
imports.add(AuthorizationProxyConfiguration.class.getName());
|
||||||
|
if (isDataPresent) {
|
||||||
|
imports.add(AuthorizationProxyDataConfiguration.class.getName());
|
||||||
|
}
|
||||||
return imports.toArray(new String[0]);
|
return imports.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 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.data.aot.hint;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
|
||||||
|
import org.springframework.security.aot.hint.AuthorizeReturnObjectCoreHintsRegistrar;
|
||||||
|
import org.springframework.security.aot.hint.AuthorizeReturnObjectHintsRegistrar;
|
||||||
|
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
|
||||||
|
import org.springframework.security.authorization.AuthorizationProxyFactory;
|
||||||
|
import org.springframework.security.authorization.method.AuthorizeReturnObject;
|
||||||
|
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
|
||||||
|
import org.springframework.security.core.annotation.SecurityAnnotationScanners;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link SecurityHintsRegistrar} that scans all beans for implementations of
|
||||||
|
* {@link RepositoryFactoryBeanSupport}, registering the corresponding entity class as a
|
||||||
|
* {@link org.springframework.aot.hint.TypeHint} should any if that repository's method
|
||||||
|
* use {@link AuthorizeReturnObject}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* It also traverses those found types for other return values.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* An instance of this class is published as an infrastructural bean by the
|
||||||
|
* {@code spring-security-config} module. However, in the event you need to publish it
|
||||||
|
* yourself, remember to publish it as an infrastructural bean like so:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* @Bean
|
||||||
|
* @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
* static SecurityHintsRegistrar proxyThese(AuthorizationProxyFactory proxyFactory) {
|
||||||
|
* return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author Josh Cummings
|
||||||
|
* @since 6.4
|
||||||
|
* @see AuthorizeReturnObjectCoreHintsRegistrar
|
||||||
|
* @see AuthorizeReturnObjectHintsRegistrar
|
||||||
|
*/
|
||||||
|
public final class AuthorizeReturnObjectDataHintsRegistrar implements SecurityHintsRegistrar {
|
||||||
|
|
||||||
|
private final AuthorizationProxyFactory proxyFactory;
|
||||||
|
|
||||||
|
private final SecurityAnnotationScanner<AuthorizeReturnObject> scanner = SecurityAnnotationScanners
|
||||||
|
.requireUnique(AuthorizeReturnObject.class);
|
||||||
|
|
||||||
|
private final Set<Class<?>> visitedClasses = new HashSet<>();
|
||||||
|
|
||||||
|
public AuthorizeReturnObjectDataHintsRegistrar(AuthorizationProxyFactory proxyFactory) {
|
||||||
|
this.proxyFactory = proxyFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerHints(RuntimeHints hints, ConfigurableListableBeanFactory beanFactory) {
|
||||||
|
List<Class<?>> toProxy = new ArrayList<>();
|
||||||
|
for (String name : beanFactory.getBeanDefinitionNames()) {
|
||||||
|
ResolvableType type = beanFactory.getBeanDefinition(name).getResolvableType();
|
||||||
|
if (!RepositoryFactoryBeanSupport.class.isAssignableFrom(type.toClass())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Class<?>[] generics = type.resolveGenerics();
|
||||||
|
Class<?> entity = generics[1];
|
||||||
|
AuthorizeReturnObject authorize = beanFactory.findAnnotationOnBean(name, AuthorizeReturnObject.class);
|
||||||
|
if (authorize != null) {
|
||||||
|
toProxy.add(entity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Class<?> repository = generics[0];
|
||||||
|
for (Method method : repository.getDeclaredMethods()) {
|
||||||
|
AuthorizeReturnObject returnObject = this.scanner.scan(method, repository);
|
||||||
|
if (returnObject == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// optimistically assume that the entity needs wrapping if any of the
|
||||||
|
// repository methods use @AuthorizeReturnObject
|
||||||
|
toProxy.add(entity);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new AuthorizeReturnObjectHintsRegistrar(this.proxyFactory, toProxy).registerHints(hints, beanFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2024 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.data.aot.hint;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
|
||||||
|
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||||
|
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
|
||||||
|
import org.springframework.security.authorization.AuthorizationProxyFactory;
|
||||||
|
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
|
||||||
|
import org.springframework.security.authorization.method.AuthorizeReturnObject;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link AuthorizeReturnObjectDataHintsRegistrar}
|
||||||
|
*/
|
||||||
|
public class AuthorizeReturnObjectDataHintsRegistrarTests {
|
||||||
|
|
||||||
|
private final AuthorizationProxyFactory proxyFactory = spy(AuthorizationAdvisorProxyFactory.withDefaults());
|
||||||
|
|
||||||
|
private final SecurityHintsRegistrar registrar = new AuthorizeReturnObjectDataHintsRegistrar(this.proxyFactory);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void registerHintsWhenUsingAuthorizeReturnObjectThenRegisters() {
|
||||||
|
GenericApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
|
||||||
|
RuntimeHints hints = new RuntimeHints();
|
||||||
|
this.registrar.registerHints(hints, context.getBeanFactory());
|
||||||
|
assertThat(hints.reflection().typeHints().map((hint) -> hint.getType().getName()))
|
||||||
|
.containsOnly(cglibClassName(MyObject.class), cglibClassName(MySubObject.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String cglibClassName(Class<?> clazz) {
|
||||||
|
return clazz.getName() + "$$SpringCGLIB$$0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@AuthorizeReturnObject
|
||||||
|
public interface MyInterface extends CrudRepository<MyObject, Long> {
|
||||||
|
|
||||||
|
List<MyObject> findAll();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MyObject {
|
||||||
|
|
||||||
|
@AuthorizeReturnObject
|
||||||
|
public MySubObject get() {
|
||||||
|
return new MySubObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MySubObject {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class AppConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RepositoryFactoryBeanSupport<MyInterface, MyObject, Long> bean() {
|
||||||
|
return new RepositoryFactoryBeanSupport<>(MyInterface.class) {
|
||||||
|
@Override
|
||||||
|
public MyInterface getObject() {
|
||||||
|
return mock(MyInterface.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends MyInterface> getObjectType() {
|
||||||
|
return MyInterface.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RepositoryFactorySupport createRepositoryFactory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user