編輯:我不推薦這個了 - 我結束了我自己的REST控制器,因爲它變得太hacky和不可預知。否則see here for a possible alternative。
可以在本文的標題中實現目標,但它有點複雜,因爲沒有得到Spring的官方支持。
作爲一個粗略概述,您必須創建兩個存儲庫,一個供內部使用,另一個(安全)供外部使用。然後你必須修改彈簧,以便它只輸出外部使用的彈簧。
大部分代碼來自下面的鏈接;一個巨大的感謝威爾忠實於未來與修復:
的Bug票:https://jira.spring.io/browse/DATAREST-923
修復庫:https://github.com/wfaithfull/spring-data-rest-multiple-repositories-workaround
步驟1
創建爲無抵押,免出口倉庫僅供內部使用:
@RepositoryRestResource(exported = false)
@Component("UserRepository")
public interface UserRepository extends CrudRepository<User, Long> { }
請注意有n o安全註釋(例如@PreAuthorized)和@RepositoryRestResource被設置爲exported = false。
步驟2
創建使用了唯一的HTTP REST的擔保,出口倉庫:這裏
@Component("UserRepositoryRest")
@Primary
@RepositoryRestResource(collectionResourceRel = "users", path = "users", exported = true)
public interface UserRepositoryRest extends UserRepository {
@PostAuthorize(" principal.getUsername() == returnObject.getUsername() || hasRole('ROLE_ADMIN') ")
@Override
User findOne(Long id);
}
注意我們正在使用的安全註解,我們也明確地把資源庫與出口=真正。
步驟3
這是它變得有點複雜。如果你停在這裏,Spring有時會加載並試圖導出你的UserRepository類,有時會加載並試圖導出你的UserRepositoryRest類。這可能會導致單元測試失敗(大約50%的時間),以及其他奇怪的副作用,這使得難以追蹤。
我們將通過調整Spring選擇導出存儲庫的方式來解決此問題。使用以下內容創建一個文件:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import java.io.Serializable;
import java.util.*;
/**
* @author Will Faithfull
*
* Warning: Ugly hack territory.
*
* Firstly, I can't just swap out this implementation, because Repositories is referenced everywhere directly without an
* interface.
*
* Unfortunately, the offending code is in a private method, {@link #cacheRepositoryFactory(String)}, and modifies private
* fields in the Repositories class. This means we can either use reflection, or replicate the functionality of the class.
*
* In this instance, I've chosen to do the latter because it's simpler, and most of this code is a simple copy/paste from
* Repositories. The superclass is given an empty bean factory to satisfy it's constructor demands, and ensure that
* it will keep as little redundant state as possible.
*/
public class ExportAwareRepositories extends Repositories {
static final Repositories NONE = new ExportAwareRepositories();
private static final RepositoryFactoryInformation<Object, Serializable> EMPTY_REPOSITORY_FACTORY_INFO = EmptyRepositoryFactoryInformation.INSTANCE;
private static final String DOMAIN_TYPE_MUST_NOT_BE_NULL = "Domain type must not be null!";
private final BeanFactory beanFactory;
private final Map<Class<?>, String> repositoryBeanNames;
private final Map<Class<?>, RepositoryFactoryInformation<Object, Serializable>> repositoryFactoryInfos;
/**
* Constructor to create the {@link #NONE} instance.
*/
private ExportAwareRepositories() {
/* Mug off the superclass with an empty beanfactory to placate the Assert.notNull */
super(new DefaultListableBeanFactory());
this.beanFactory = null;
this.repositoryBeanNames = Collections.<Class<?>, String> emptyMap();
this.repositoryFactoryInfos = Collections.<Class<?>, RepositoryFactoryInformation<Object, Serializable>> emptyMap();
}
/**
* Creates a new {@link Repositories} instance by looking up the repository instances and meta information from the
* given {@link ListableBeanFactory}.
*
* @param factory must not be {@literal null}.
*/
public ExportAwareRepositories(ListableBeanFactory factory) {
/* Mug off the superclass with an empty beanfactory to placate the Assert.notNull */
super(new DefaultListableBeanFactory());
Assert.notNull(factory, "Factory must not be null!");
this.beanFactory = factory;
this.repositoryFactoryInfos = new HashMap<Class<?>, RepositoryFactoryInformation<Object, Serializable>>();
this.repositoryBeanNames = new HashMap<Class<?>, String>();
populateRepositoryFactoryInformation(factory);
}
private void populateRepositoryFactoryInformation(ListableBeanFactory factory) {
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, RepositoryFactoryInformation.class,
false, false)) {
cacheRepositoryFactory(name);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private synchronized void cacheRepositoryFactory(String name) {
RepositoryFactoryInformation repositoryFactoryInformation = beanFactory.getBean(name,
RepositoryFactoryInformation.class);
Class<?> domainType = ClassUtils
.getUserClass(repositoryFactoryInformation.getRepositoryInformation().getDomainType());
RepositoryInformation information = repositoryFactoryInformation.getRepositoryInformation();
Set<Class<?>> alternativeDomainTypes = information.getAlternativeDomainTypes();
String beanName = BeanFactoryUtils.transformedBeanName(name);
Set<Class<?>> typesToRegister = new HashSet<Class<?>>(alternativeDomainTypes.size() + 1);
typesToRegister.add(domainType);
typesToRegister.addAll(alternativeDomainTypes);
for (Class<?> type : typesToRegister) {
// I still want to add repositories if they don't have an exported counterpart, so we eagerly add repositories
// but then check whether to supercede them. If you have more than one repository with exported=true, clearly
// the last one that arrives here will be the registered one. I don't know why anyone would do this though.
if(this.repositoryFactoryInfos.containsKey(type)) {
Class<?> repoInterface = information.getRepositoryInterface();
if(repoInterface.isAnnotationPresent(RepositoryRestResource.class)) {
boolean exported = repoInterface.getAnnotation(RepositoryRestResource.class).exported();
if(exported) { // Then this has priority.
this.repositoryFactoryInfos.put(type, repositoryFactoryInformation);
this.repositoryBeanNames.put(type, beanName);
}
}
} else {
this.repositoryFactoryInfos.put(type, repositoryFactoryInformation);
this.repositoryBeanNames.put(type, beanName);
}
}
}
/**
* Returns whether we have a repository instance registered to manage instances of the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return
*/
@Override
public boolean hasRepositoryFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
return repositoryFactoryInfos.containsKey(domainClass);
}
/**
* Returns the repository managing the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return
*/
@Override
public Object getRepositoryFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
String repositoryBeanName = repositoryBeanNames.get(domainClass);
return repositoryBeanName == null || beanFactory == null ? null : beanFactory.getBean(repositoryBeanName);
}
/**
* Returns the {@link RepositoryFactoryInformation} for the given domain class. The given <code>code</code> is
* converted to the actual user class if necessary, @see ClassUtils#getUserClass.
*
* @param domainClass must not be {@literal null}.
* @return the {@link RepositoryFactoryInformation} for the given domain class or {@literal null} if no repository
* registered for this domain class.
*/
private RepositoryFactoryInformation<Object, Serializable> getRepositoryFactoryInfoFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
Class<?> userType = ClassUtils.getUserClass(domainClass);
RepositoryFactoryInformation<Object, Serializable> repositoryInfo = repositoryFactoryInfos.get(userType);
if (repositoryInfo != null) {
return repositoryInfo;
}
if (!userType.equals(Object.class)) {
return getRepositoryFactoryInfoFor(userType.getSuperclass());
}
return EMPTY_REPOSITORY_FACTORY_INFO;
}
/**
* Returns the {@link EntityInformation} for the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
@Override
public <T, S extends Serializable> EntityInformation<T, S> getEntityInformationFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
return (EntityInformation<T, S>) getRepositoryFactoryInfoFor(domainClass).getEntityInformation();
}
/**
* Returns the {@link RepositoryInformation} for the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return the {@link RepositoryInformation} for the given domain class or {@literal null} if no repository registered
* for this domain class.
*/
@Override
public RepositoryInformation getRepositoryInformationFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
RepositoryFactoryInformation<Object, Serializable> information = getRepositoryFactoryInfoFor(domainClass);
return information == EMPTY_REPOSITORY_FACTORY_INFO ? null : information.getRepositoryInformation();
}
/**
* Returns the {@link RepositoryInformation} for the given repository interface.
*
* @param repositoryInterface must not be {@literal null}.
* @return the {@link RepositoryInformation} for the given repository interface or {@literal null} there's no
* repository instance registered for the given interface.
* @since 1.12
*/
@Override
public RepositoryInformation getRepositoryInformation(Class<?> repositoryInterface) {
for (RepositoryFactoryInformation<Object, Serializable> factoryInformation : repositoryFactoryInfos.values()) {
RepositoryInformation information = factoryInformation.getRepositoryInformation();
if (information.getRepositoryInterface().equals(repositoryInterface)) {
return information;
}
}
return null;
}
/**
* Returns the {@link PersistentEntity} for the given domain class. Might return {@literal null} in case the module
* storing the given domain class does not support the mapping subsystem.
*
* @param domainClass must not be {@literal null}.
* @return the {@link PersistentEntity} for the given domain class or {@literal null} if no repository is registered
* for the domain class or the repository is not backed by a {@link MappingContext} implementation.
*/
@Override
public PersistentEntity<?, ?> getPersistentEntity(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
return getRepositoryFactoryInfoFor(domainClass).getPersistentEntity();
}
/**
* Returns the {@link QueryMethod}s contained in the repository managing the given domain class.
*
* @param domainClass must not be {@literal null}.
* @return
*/
@Override
public List<QueryMethod> getQueryMethodsFor(Class<?> domainClass) {
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
return getRepositoryFactoryInfoFor(domainClass).getQueryMethods();
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<Class<?>> iterator() {
return repositoryFactoryInfos.keySet().iterator();
}
/**
* Null-object to avoid nasty {@literal null} checks in cache lookups.
*
* @author Thomas Darimont
*/
private static enum EmptyRepositoryFactoryInformation implements RepositoryFactoryInformation<Object, Serializable> {
INSTANCE;
@Override
public EntityInformation<Object, Serializable> getEntityInformation() {
return null;
}
@Override
public RepositoryInformation getRepositoryInformation() {
return null;
}
@Override
public PersistentEntity<?, ?> getPersistentEntity() {
return null;
}
@Override
public List<QueryMethod> getQueryMethods() {
return Collections.<QueryMethod> emptyList();
}
}
}
步驟4
使用以下內容創建另一個文件:
import me.faithfull.hack.ExportAwareRepositories;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
/**
* @author Will Faithfull
*/
@Configuration
public class RepositoryRestConfiguration extends RepositoryRestMvcConfiguration {
@Autowired
ApplicationContext context;
/**
* We replace the stock repostiories with our modified subclass.
*/
@Override
public Repositories repositories() {
return new ExportAwareRepositories(context);
}
}
利潤
應該這樣做 - 現在Spring應該只導出你的UserRepositoryRest類,而忽略讓你的UserRepository類可以在沒有安全限制的情況下在內部使用。
請注意...它可能會導致很多副作用,例如https://github.com/spring-projects/spring-boot/issues/2392或https://stackoverflow.com/questions/43494321/extending-repositoryrestmvcconfiguration -breaks-jackson-localdatetime-serialisat – Piotr
謝謝@Piotr - 最後我終於把我自己的休息控制器拖到了最後,因爲事情變得太難以預料了。我編輯我的帖子這樣說。 –
是的,我自己試過這個解決方案,但最終刪除了它。現在嘗試從這裏開始https://github.com/spring-projects/spring-data-examples/blob/master/rest/security/src/main/java/example/springdata/rest/security/Application.java#L57 。他們稱之爲SecurityUtils.runAs(「system」,「system」,「ROLE_ADMIN」);在任何回購相關的查詢之前。不錯的嘗試:) – Piotr