Spring Security ACL:我无法更新ACL(SQLIntegrityConstraintViolationException)

时间:2018-07-13 12:56:28

标签: java spring spring-boot spring-security spring-security-acl

我正在尝试学习一些Spring Security ACL控件并将其应用于我的Spring Boot项目。因此,我试图重用Spring Security reference中的代码片段,我对项目需求做了一些调整。

我具有以下组件,以便在启动时用一些初始值填充数据库。

<AppBarLayout ... />
<android.support.v4.widget.NestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        android:fitsSystemWindows="true"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">



                    <LinearLayout
                        android:orientation="vertical"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent">

                        <android.support.v7.widget.RecyclerView
                            android:id="@+id/recycler_asks"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:orientation="vertical"
                            android:scrollbars="none"
                            app:layoutManager="android.support.v7.widget.LinearLayoutManager"
                            app:layout_constraintBottom_toTopOf="@+id/divider"
                            app:layout_constraintTop_toTopOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintEnd_toEndOf="parent"
                            android:layout_marginBottom="@dimen/small_margin"
                            android:layout_above="@+id/divider"
                            android:background="@color/red"/>

                        <View
                            android:id="@+id/divider"
                            android:layout_width="match_parent"
                            android:layout_height="1dp"
                            app:layout_constraintBottom_toBottomOf="parent"
                            app:layout_constraintEnd_toEndOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintTop_toTopOf="parent"/>

                        <android.support.v7.widget.RecyclerView
                            android:id="@+id/recycler_bids"
                            android:layout_width="match_parent"
                            android:layout_height="0dp"
                            android:orientation="vertical"
                            android:scrollbars="none"
                            app:layout_constraintTop_toBottomOf="@+id/divider"
                            app:layout_constraintBottom_toBottomOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintEnd_toEndOf="parent"
                            app:layoutManager="android.support.v7.widget.LinearLayoutManager"
                            android:layout_below="@+id/divider"
                            android:layout_marginTop="@dimen/small_margin"
                            android:background="@color/cyan"/>

                    </LinearLayout>


    </android.support.v4.widget.NestedScrollView>

每次刷新应用程序上下文时都会运行此方法。我可以看到,如果仅运行一种测试方法(或测试类),则该方法将成功运行。这些测试是端到端测试(@Component public class AppBootstrap { private Authority adminAuth; private User admin; private TimeSheet timeSheetAdmin; private final JdbcMutableAclService jdbcMutableAclService; private final PlatformTransactionManager transactionManager; @Autowired public AppBootstrap(JdbcMutableAclService jdbcMutableAclService, PlatformTransactionManager transactionManager) { this.jdbcMutableAclService = jdbcMutableAclService; this.transactionManager = transactionManager; } @Bean public CommandLineRunner initialAuthorities(AuthorityRepository authorityRepository) { return args -> { adminAuth = new Authority(ROLE_ADMIN); authorityRepository.save(adminAuth); }; } @Bean public CommandLineRunner initialUsers(UserRepository userRepository) { return args -> { admin = new User("admin", "{bcrypt}$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi", "admin", "admin", "admin@example.com", true, getDate(2016, JANUARY, 1)); admin.setAuthorities(asList(adminAuth)); userRepository.save(admin); }; } @Bean public CommandLineRunner initialTimeSheets(TimeSheetRepository timeSheetRepository) { return args -> { timeSheetAdmin = new TimeSheet(LocalDate.of(2016, MARCH, 1), admin); timeSheetRepository.save(timeSheetUser); }; } @Bean public CommandLineRunner initialRights() { return args -> grantPermission(admin, timeSheetAdmin, ADMINISTRATION); } MutableAcl grantPermission(User user, TimeSheet timeSheet, Permission p) { TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); SecurityContext securityContext = SecurityContextHolder.getContext(); var authorities = user.getAuthorities().stream().map(auth -> auth.getName().toString()).collect(toList()); Authentication authentication = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPass(), AuthorityUtils.createAuthorityList(authorities.toArray(new String[]{}))); securityContext.setAuthentication(authentication); ObjectIdentity oiTimeSheet = new ObjectIdentityImpl(TimeSheet.class, timeSheet.getId()); Sid sidAdmin = new PrincipalSid(user.getUsername()); MutableAcl acl; try { acl = (MutableAcl) jdbcMutableAclService.readAclById(oiTimeSheet); } catch (NotFoundException nfe) { acl = transactionTemplate.execute(status -> jdbcMutableAclService.createAcl(oiTimeSheet)); } acl.insertAce(acl.getEntries().size(), p, sidAdmin, true); // updating permission MutableAcl finalAcl = acl; return transactionTemplate.execute(status -> jdbcMutableAclService.updateAcl(finalAcl)); } } )。另外,单独运行我的@RunWith(SpringRunner.class) @SpringBootTest也没问题。但是,如果我一次运行所有测试,几乎所有@SpringBootApplication都会失败,并显示以下堆栈跟踪信息

@SpringBootTest

我有几个问题:

  • 为什么我运行多个测试类时会发生此错误?
  • 如何找到解决方法?有没有更好的方法可以实现相同目的?

谢谢! :-)

2 个答案:

答案 0 :(得分:1)

我遇到了同样的问题。
就我而言,ACL缓存脏了。每次测试后,我必须清除缓存,然后所有测试都以绿色运行,即使同时调用也是如此。
我为此创建了一个TestExcutionListener。只需用它注释您的抽象Test类:

@TestExecutionListeners(
    value = ClearAclCacheTestExecutionListener.class,
    mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 
public class AbstractTest{...}
public class ClearAclCacheTestExecutionListener extends AbstractTestExecutionListener {

    @Autowired
    private AclCache aclCache;

    @Override
    public void beforeTestClass(TestContext testContext) {
        testContext.getApplicationContext()
                .getAutowireCapableBeanFactory()
                .autowireBean(this);
    }

    @Override
    public void afterTestMethod(TestContext testContext) throws Exception {
        super.afterTestMethod(testContext);
        aclCache.clearCache();
    }
}

答案 1 :(得分:0)

由于这个问题并没有那么流行,所以我尝试了另一种似乎很有效的方法:-)

首先,我创建了一个新的ACLService类,以隔离与JdbcMutableAclService的所有交互。

package com.roberto.security.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.acls.model.*;
import org.springframework.stereotype.Service;

/**
 * Service class to handle ACL permissions.
 */
@Service
public class ACLService {
    private final MutableAclService mutableAclService;

    @Autowired
    public ACLService(MutableAclService mutableAclService) {
        this.mutableAclService = mutableAclService;
    }

    /**
     * Insert an ACL entry
     * @param oid represents the model object
     * @param recipient represents the principal (user, group of users, etc)
     * @param permission quite explicit name...
     * @return the new ACL database entry
     */
    public MutableAcl addPermission(ObjectIdentity oid, Sid recipient, Permission permission) {
        MutableAcl acl;

        try {
            acl = (MutableAcl) mutableAclService.readAclById(oid);
        } catch (NotFoundException nfe) {
            acl = mutableAclService.createAcl(oid);
        }

        acl.insertAce(acl.getEntries().size(), permission, recipient, true);
        return mutableAclService.updateAcl(acl);
    }   
}

然后,我创建了另一个可以正常运行的集成测试,没有IllegalStateException。现在,我只需要从引导类中调用它即可。

package com.roberto.security.service;

import com.roberto.model.TimeSheet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.*;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static org.junit.Assert.*;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD;

/**
 * This test handles basic interaction between our codebase
 * Spring Security ACL and the underlying database model
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class ACLServiceIntegrationTest {

    private Authentication authentication;
    private ObjectIdentity oid ;
    private Sid sid;
    private Permission administration = BasePermission.ADMINISTRATION;

    @Autowired
    private ACLService aclService;

    @Autowired
    private MutableAclService mutableAclService;

    @Autowired
    private PermissionEvaluator permissionEvaluator;

    @Before
    public void setUp() {
        authentication = TestSecurityContextHolder.getContext().getAuthentication();
        sid = new PrincipalSid(((JwtUser) authentication.getPrincipal()).getUsername());
        oid = new ObjectIdentityImpl(TimeSheet.class, 1);
    }

    @Test
    @WithUserDetails("admin")
    public void testBeans() {
        assertNotNull(aclService);
        assertNotNull(mutableAclService);
        assertNotNull(permissionEvaluator);
    }

    @Test
    @Transactional
    @WithUserDetails("admin")
    @DirtiesContext(methodMode = AFTER_METHOD)
    public void addPermissionIntegrationTest() {
        assertFalse(permissionEvaluator.hasPermission(authentication, oid.getIdentifier(), oid.getType(), administration));

        MutableAcl acl = aclService.addPermission(oi, sid, administration);

        assertTrue(permissionEvaluator.hasPermission(authentication, oid.getIdentifier(), oid.getType(), administration));

        assertEquals(TimeSheet.class.toString().split(" ")[1], acl.getObjectIdentity().getType());
        assertTrue(acl.getEntries().stream().anyMatch(e -> e.getSid().equals(sid) && e.getPermission().equals(administration)));
        assertTrue(acl.isGranted(List.of(administration), List.of(sid), true));
    }
}