如何对使用 thymeleaf 的安全控制器进行单元测试(不会出现 TemplateProcessingException)?

2023-11-25

我正在尝试使用 spring security 和一个简单的家庭(根)控制器在 spring-boot 中运行单元测试,该控制器使用 thymeleaf 进行模板处理。我正在尝试编写一些单元测试来验证我的安全权限是否正常工作以及我的模板(使用 thymeleaf spring 安全集成)隐藏或显示了正确的数据。当我运行应用程序时,它本身可以正常工作。我只是想验证它是否可以与一组集成测试一起使用。 您可以在这里找到所有代码,但我还将在下面包含相关片段:

https://github.com/azeckoski/lti_starter

控制器非常简单,除了渲染模板(在根目录 - 即“/”)之外什么也不做。

@Controller
public class HomeController extends BaseController {
    @RequestMapping(method = RequestMethod.GET)
    public String index(HttpServletRequest req, Principal principal, Model model) {
        log.info("HOME: " + req);
        model.addAttribute("name", "HOME");
        return "home"; // name of the template
    }
}

该模板包含很多内容,但测试的相关部分是:

<p>Hello Spring Boot User <span th:text="${username}"/>! (<span th:text="${name}"/>)</p>
<div sec:authorize="hasRole('ROLE_USER')">
    This content is only shown to users (ROLE_USER).
</div>
<div sec:authorize="isAnonymous()"><!-- only show this when user is NOT logged in -->
    <h2>Form Login endpoint</h2>
    ...
</div>

最后是测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class AppControllersTest extends BaseApplicationTest {

    @Autowired
    WebApplicationContext wac;

    @Autowired
    private FilterChainProxy springSecurityFilter;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        // Process mock annotations
        MockitoAnnotations.initMocks(this);
        // Setup Spring test in webapp-mode (same config as spring-boot)
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
                .addFilter(springSecurityFilter, "/*")
                .build();
    }

    @Test
    public void testLoadRoot() throws Exception {
        // Test basic home controller request
        MvcResult result = this.mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
                .andReturn();
        String content = result.getResponse().getContentAsString();
        assertNotNull(content);
        assertTrue(content.contains("Hello Spring Boot"));
        assertTrue(content.contains("Form Login endpoint"));
    }

    @Test
    public void testLoadRootWithAuth() throws Exception {
        Collection<GrantedAuthority> authorities = new HashSet<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        Authentication authToken = new UsernamePasswordAuthenticationToken("azeckoski", "password", authorities);
        SecurityContextHolder.getContext().setAuthentication(authToken);
        // Test basic home controller request
        MvcResult result = this.mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
                .andReturn();
        String content = result.getResponse().getContentAsString();
        assertNotNull(content);
        assertTrue(content.contains("Hello Spring Boot"));
        assertTrue(content.contains("only shown to users (ROLE_USER)"));
    }
}

我在上述两个测试中得到的错误是:

testLoadRoot(ltistarter.controllers.AppControllersTest) 经过的时间: 0.648 秒

然而,只有在启用两个测试并且包含 springSecurityFilter 时才会发生这种情况。如果我禁用其中一项测试并删除 springSecurityFilter 代码(.addFilter(springSecurityFilter, "/*"))然后我就不再收到该错误了。我怀疑某些东西可能会弄乱 WebApplicationContext 或使安全性处于某种故障状态,但我不确定需要重置或更改什么。

因此,如果我取出第二个测试并删除弹簧安全过滤器,那么我的第一个测试仍然会失败(特别是这个assertTrue(content.contains("Form Login endpoint")))但我不再收到任何错误。当我查看生成的 HTML 时,我没有看到任何使用sec:authorize属性。

所以我四处搜索并发现了一个我需要添加到的建议springSecurityFilter(我在上面的代码示例中已经完成了),但是,一旦我这样做,我就会立即失败(如果没有它,它甚至不会达到失败的地步)。关于导致该异常的原因以及如何修复它有什么建议吗?


我有一个解决方案,似乎可以完全解决 spring-boot:1.1.4、spring-security:3.2.4 和 thymeleaf:2.1.3 的这个问题(尽管它有点像黑客)。

这是修改后的单元测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class AppControllersTest {

    @Autowired
    public WebApplicationContext context;

    @Autowired
    private FilterChainProxy springSecurityFilter;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        assertNotNull(context);
        assertNotNull(springSecurityFilter);
        // Process mock annotations
        MockitoAnnotations.initMocks(this);
        // Setup Spring test in webapp-mode (same config as spring-boot)
        this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .addFilters(springSecurityFilter)
                .build();
        context.getServletContext().setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
    }
...

这里的魔力是迫使WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE作为实际的 Web 应用程序上下文(我注入的)。 这允许实际的 sec: 属性起作用,但我尝试设置权限以便用户登录的第二个测试没有通过(看起来用户仍然是匿名的)。

UPDATE

缺少一些东西(我认为这是 Spring 安全性工作方式的一个差距),但幸运的是相当容易解决(尽管这有点像黑客)。有关该问题的更多详细信息,请参阅:Spring 测试与安全:如何模拟身份验证?

我需要添加一个为测试创建模拟会话的方法。该方法将设置安全性Principal/Authentication并强制SecurityContext进入HttpSession然后可以将其添加到测试请求中(请参阅下面的测试片段和NamedOAuthPrincipal类示例)。

public MockHttpSession makeAuthSession(String username, String... roles) {
    if (StringUtils.isEmpty(username)) {
        username = "azeckoski";
    }
    MockHttpSession session = new MockHttpSession();
    session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
    Collection<GrantedAuthority> authorities = new HashSet<>();
    if (roles != null && roles.length > 0) {
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
    }
    //Authentication authToken = new UsernamePasswordAuthenticationToken("azeckoski", "password", authorities); // causes a NPE when it tries to access the Principal
    Principal principal = new NamedOAuthPrincipal(username, authorities,
            "key", "signature", "HMAC-SHA-1", "signaturebase", "token");
    Authentication authToken = new UsernamePasswordAuthenticationToken(principal, null, authorities);
    SecurityContextHolder.getContext().setAuthentication(authToken);
    return session;
}

类来创建Principal(通过 ConsumerCredentials 提供 OAuth 支持)。如果您不使用 OAuth,那么您可以跳过 ConsumerCredentials 部分,只需实现主体(但您应该返回 GrantedAuthority 的集合)。

public static class NamedOAuthPrincipal extends ConsumerCredentials implements Principal {
    public String name;
    public Collection<GrantedAuthority> authorities;
    public NamedOAuthPrincipal(String name, Collection<GrantedAuthority> authorities, String consumerKey, String signature, String signatureMethod, String signatureBaseString, String token) {
        super(consumerKey, signature, signatureMethod, signatureBaseString, token);
        this.name = name;
        this.authorities = authorities;
    }
    @Override
    public String getName() {
        return name;
    }
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }
}

然后像这样修改测试(创建会话,然后将其设置在模拟请求上):

@Test
public void testLoadRootWithAuth() throws Exception {
    // Test basic home controller request with a session and logged in user
    MockHttpSession session = makeAuthSession("azeckoski", "ROLE_USER");
    MvcResult result = this.mockMvc.perform(get("/").session(session))
            .andExpect(status().isOk())
            .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
            .andReturn();
    String content = result.getResponse().getContentAsString();
    assertNotNull(content);
    assertTrue(content.contains("Hello Spring Boot"));
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何对使用 thymeleaf 的安全控制器进行单元测试(不会出现 TemplateProcessingException)? 的相关文章

随机推荐

  • Pandas,在字符串中打印变量

    我有一个数据框 新 看起来像这样 num name1 name2 11 A AB 14 Y YX 25 L LS 39 Z ZT 我只想提取打印语句中的数值 这样我就有一个如下所示的输出 The value is 11 The value
  • Python:xml ElementTree(或 lxml)中的命名空间

    我想检索旧版 xml 文件 操作并保存它 这是我的代码 from xml etree import cElementTree as ET NS http www somedomain com XI Traffic 10 def fix xm
  • 如何在 gdb 中打印 Objective-C 类方法的结果?

    当使用 gdb 通过调试控制台 在 Xcode 4 中调试 iPad 程序时 我试图打印出运行类方法的结果 gdb po MyClass foo bar gdb 输出以下内容 当前上下文中没有符号 MyClass 有没有办法打印结果 NSS
  • C# 编译/构建是一个增量过程吗?

    我们的解决方案包含大量 C 项目 它们之间存在复杂的依赖关系 例如 项目A B C A依赖于B B依赖于C 如果我更改项目C中的一个文件 然后重建解决方案 项目A B C将一起重建 在C 中 构建包含两个过程 编译和链接 如果我更改项目C中
  • 如何与 NSPersistentCloudKitContainer 设置有序关系?

    当我检查时Used with CloudKit 错误Folder children must not be ordered出现了 关于有序关系有什么想法吗 使用 Xcode 11 测试版 3 这是Folder Entity 适用于 iOS
  • 在 Fortran 中使用“X != 0”时出现语法错误

    我的 Fortran 程序有一个问题 它除了计算素数分解 或应该做 之外什么也不做 这就是错误 C MinGW Fortran gt gfortran aufg3 f90 aufg3 f90 15 15 if prim i 0 and mo
  • 计算函数 sin()

    为了我的学习 我必须编写一个算法来计算sin 有了这个功能 然而 在我的算法中 我必须将 X 的值保持在 0 到 Pi 2 之间 所以 我写了我的算法 但所有结果都是错误的 这是我的代码 double sinX double x doubl
  • 在启动时获取azure应用程序服务插槽名称?

    当 asp net core 进程启动时 如何获取我的应用程序服务的插槽 生产或暂存 的名称 HTTP HOST 环境变量似乎没有在启动时设置 并且我没有要检查的 http 请求 如果我们想获取主机名 可以使用环境变量WEBSITE HOS
  • 在一个项目中编译 Silverlight 和 WPF 的最佳实践是什么?

    我刚刚完成了一个 Silverlight 项目 是时候进行一些清理了 我想将我的核心文件放入一个单独的项目中 我将从我的主 Silverlight 应用程序中引用该项目 其中一些类与 WPF 兼容 我非常希望能够将 Silverlight
  • C 获取文件的最后修改日期

    我想获取 C 语言中文件的最后修改日期 我发现的几乎所有来源都使用以下代码片段中的内容 char get last modified char file struct tm clock struct stat attr stat file
  • os.system()在哪个linux shell下执行命令?

    我在用 bin tcsh作为我的默认外壳 然而 tcsh 风格的命令os system setenv VAR val 对我不起作用 但os system export VAR val works 所以我的问题是我怎样才能知道os syste
  • 安全性:如何验证图像文件上传?

    我需要接受用户上传的图像文件 如何检查并 100 确定我没有收到恶意文件或其他内容 检查 mime 类型 重新画图 防病毒 重新绘制图像 读取它GD s imagecreatefromXXX 并将其保存回来imageXXX 通过这种方式 您
  • 使用 JWT 验证套接字 io 连接

    如何验证 socket io 连接 我的应用程序使用来自另一台服务器 python 的登录端点来获取令牌 每当用户在节点端打开套接字连接时 如何才能使用该令牌 io on connection function socket socket
  • 上传json数据时如何按百分比显示进度条状态?

    我正在上传字符串和照片 它工作正常 现在我想在上传百分比数据时显示进度条 但百分比很快显示到 100 百分比 并且需要更多时间上传 最后到达后执行方法 protected class upload images extends AsyncT
  • 如何使 Django 的“DATETIME_FORMAT”处于活动状态?

    应该在哪里DATETIME FORMAT放置以使其发挥作用 在 Django 管理站点中显示日期时间 Django 的自动管理界面 文档用于DATETIME FORMAT 页上http docs djangoproject com en 1
  • 从 pyaudio-stream 获取浮点数形式的音频样本

    由于我目前正准备构建一个基于 Raspberry Pi 的设备 用于测量声卡记录的噪声 例如方差 中的一些内容 并尝试在 python 中执行此操作 所以我陷入了如何获取音频样本的困境中用于进一步计算的浮点数 我做了什么 使用线路输入适配器
  • 如何将 telnet 控制台日志重定向到文件 Linux

    我想将 telnet 控制台日志重定向到 Linux 中的文件 例如 telnet someIp gt someFile ls exit 我希望控制台日志保存在文件名中someFile 我在用tcl用于自动化此操作 截至目前 我正在做spa
  • 使用带有 Kerberos 身份验证的 impyla 客户端连接到 Impala

    我在 W8 机器上 使用 Python Anaconda分布 连接到Impala in our Hadoop集群使用Impyla包裹 我们的 hadoop 集群是通过以下方式保护的Kerberos 我已遵循API参考如何配置连接 from
  • 如何更改 Eclipse 中的*默认*默认编码?

    每次创建新工作区时 Eclipse 都会默认使用 Cp1250 编码 每当我创建项目的新分支并切换到 Eclipse 中的新工作区时 我需要转到窗口 gt 首选项 gt 常规 gt 工作区 文本文件编码并手动切换到 其他 UTF 8 如果我
  • 如何对使用 thymeleaf 的安全控制器进行单元测试(不会出现 TemplateProcessingException)?

    我正在尝试使用 spring security 和一个简单的家庭 根 控制器在 spring boot 中运行单元测试 该控制器使用 thymeleaf 进行模板处理 我正在尝试编写一些单元测试来验证我的安全权限是否正常工作以及我的模板 使