shiro 系列
Shiro-03-5 分钟入门 shiro 安全框架实战笔记
Shiro-07-Session Management 会话管理
Shiro-20-shiro 整合 spring 实战及源码详解
Shiro-21-shiro 整合 springmvc 实战及源码详解
Shiro-22-shiro 整合 springboot 实战
Shiro-31-从零手写 shiro 权限校验框架 (1) 基础功能
使用Apache Shiro进行测试
这部分文档解释了如何在单元测试中启用Shiro。
测试须知
正如我们在Subject引用中已经介绍过的,我们知道Subject是“当前执行”用户的安全特定视图,并且Subject实例总是绑定到线程,以确保我们知道在线程执行期间的任何时候谁在执行逻辑。
这意味着,为了能够访问当前正在执行的主题,必须总是发生三件基本的事情:
-
必须创建一个Subject实例
-
Subject实例必须绑定到当前执行的线程。
-
在线程完成执行之后(或者如果线程的执行导致一个Throwable),必须解除Subject的绑定,以确保线程在任何线程池环境中保持“干净”。
Shiro有一些架构组件,可以为正在运行的应用程序自动执行这种绑定/解绑定逻辑。
例如,在web应用程序中,根Shiro过滤器在过滤请求时执行此逻辑。
但是由于测试环境和框架的不同,我们需要自己为我们所选择的测试框架执行这个绑定/解绑定逻辑。
测试设置
因此,我们知道在创建Subject实例后,它必须绑定到thread。在线程(或者在本例中是一个测试)完成执行后,我们必须解绑定Subject以保持线程“干净”。
幸运的是,像JUnit和TestNG这样的现代测试框架本身就支持这种“设置”和“拆除”的概念。
我们可以利用这种支持来模拟Shiro在一个“完整”的应用程序中会做什么。
我们已经创建了一个基抽象类,您可以在下面的测试中使用它——您可以根据需要随意复制和/或修改它。
它既可以用于单元测试,也可以用于集成测试(本例中我们使用了JUnit,但TestNG也可以):
- AbstractShiroTest
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.LifecycleUtils;
import org.apache.shiro.util.ThreadState;
import org.junit.AfterClass;
/**
* Abstract test case enabling Shiro in test environments.
*/
public abstract class AbstractShiroTest {
private static ThreadState subjectThreadState;
public AbstractShiroTest() {
}
/**
* Allows subclasses to set the currently executing {@link Subject} instance.
*
* @param subject the Subject instance
*/
protected void setSubject(Subject subject) {
clearSubject();
subjectThreadState = createThreadState(subject);
subjectThreadState.bind();
}
protected Subject getSubject() {
return SecurityUtils.getSubject();
}
protected ThreadState createThreadState(Subject subject) {
return new SubjectThreadState(subject);
}
/**
* Clears Shiro's thread state, ensuring the thread remains clean for future test execution.
*/
protected void clearSubject() {
doClearSubject();
}
private static void doClearSubject() {
if (subjectThreadState != null) {
subjectThreadState.clear();
subjectThreadState = null;
}
}
protected static void setSecurityManager(SecurityManager securityManager) {
SecurityUtils.setSecurityManager(securityManager);
}
protected static SecurityManager getSecurityManager() {
return SecurityUtils.getSecurityManager();
}
@AfterClass
public static void tearDownShiro() {
doClearSubject();
try {
SecurityManager securityManager = getSecurityManager();
LifecycleUtils.destroy(securityManager);
} catch (UnavailableSecurityManagerException e) {
//we don't care about this when cleaning up the test environment
//(for example, maybe the subclass is a unit test and it didn't
// need a SecurityManager instance because it was using only
// mock Subject instances)
}
setSecurityManager(null);
}
}
测试与框架
AbstractShiroTest类中的代码使用Shiro的ThreadState概念和一个静态安全管理器。
这些技术在测试和框架代码中很有用,但很少在应用程序代码中使用。
大多数使用Shiro的终端用户需要确保线程状态的一致性,他们几乎总是使用Shiro的自动管理机制,即“主题”。
与‘and the’主语联系起来。执行的方法。这些方法在主题线程关联参考中有介绍)。
单元测试
单元测试主要是测试你的代码,并且只在有限的范围内测试你的代码。
当你考虑到Shiro时,你真正想要关注的是你的代码能够正确地与Shiro的API一起工作——你不需要测试Shiro的实现是否能够正确地工作(这是Shiro开发团队必须在Shiro的代码库中确保的事情)。
测试Shiro的实现是否与您的实现一起工作,这实际上是集成测试(下面将讨论)。
ExampleShiroUnitTest
因为单元测试更适合测试您自己的逻辑(而不是您的逻辑可能调用的任何实现),所以mock逻辑所依赖的任何api是一个好主意。
这在Shiro上工作得很好——你可以模仿Subject接口,让它反映你想让测试代码响应的任何条件。
我们可以利用像EasyMock和Mockito这样的现代模拟框架来为我们完成这项工作。
但如上所述,Shiro测试中的关键是记住,在测试执行期间,任何Subject实例(模拟的或真实的)都必须绑定到线程。
因此,我们所需要做的就是绑定mock主题,以确保一切按预期工作。
(这个例子使用了EasyMock,但是Mockito也一样):
import org.apache.shiro.subject.Subject;
import org.junit.After;
import org.junit.Test;
import static org.easymock.EasyMock.*;
/**
* Simple example test class showing how one may perform unit tests for
* code that requires Shiro APIs.
*/
public class ExampleShiroUnitTest extends AbstractShiroTest {
@Test
public void testSimple() {
//1. Create a mock authenticated Subject instance for the test to run:
Subject subjectUnderTest = createNiceMock(Subject.class);
expect(subjectUnderTest.isAuthenticated()).andReturn(true);
//2. Bind the subject to the current thread:
setSubject(subjectUnderTest);
//perform test logic here. Any call to
//SecurityUtils.getSubject() directly (or nested in the
//call stack) will work properly.
}
@After
public void tearDownSubject() {
//3. Unbind the subject from the current thread:
clearSubject();
}
}
如您所见,我们没有设置Shiro SecurityManager实例或配置一个域或类似的任何东西。
我们只是创建一个mock Subject实例,并通过setSubject方法调用将其绑定到线程。
这将确保在我们的测试代码中或正在测试的代码中对 SecurityyUtils.getsubject()
的任何调用都能正确工作。
请注意,setSubject方法实现将mock Subject绑定到线程,直到使用不同的Subject实例调用setSubject或通过clearSubject()调用显式地将其从线程中清除为止。
将主题绑定到线程多长时间(或在不同的测试中将其交换为一个新实例)取决于您和您的测试需求。
tearDownSubject()
本例中的tearDownSubject()方法使用了Junit 4注释,以确保在每个测试方法执行之后,主题都从线程中清除,不管是什么。
这需要您设置一个新的Subject实例,并(通过setSubject)为每个执行的测试设置它。
然而,这并不是绝对必要的。例如,您可以在每个测试的开始(比如在@ before注释的方法中)绑定一个新的Subject实例(通过setSujbect)。但如果要这样做,不妨使用@After tearDownSubject()方法来保持对称和“干净”。
您可以在每个方法中手动混合和匹配这种设置/删除逻辑,或者根据需要使用@Before和@After注释。
然而,AbstractShiroTest超类将在所有测试之后解除主题与线程的绑定,因为它的tearDownShiro()方法中有@AfterClass注释。
小结
希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。
我是老马,期待与你的下次相遇。
参考资料
10 Minute Tutorial on Apache Shiro
https://shiro.apache.org/reference.html
https://shiro.apache.org/session-management.html