2539-SpringSecurity系列--在有安全验证的情况下做单元测试Test

Posted 江湖月影

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2539-SpringSecurity系列--在有安全验证的情况下做单元测试Test相关的知识,希望对你有一定的参考价值。

在有安全验证的情况下做单元测试Test

版本信息


<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.14.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>1.5.14.RELEASE</version>
    <!--实际里面spring-security-web的版本是4.2.7-->
</dependency>



添加依赖

<!--spring-security单元测试-->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>4.2.3.RELEASE</version>
    <scope>test</scope>
</dependency>
<!--spring-security单元测试-->

需求

  1. 在写单元测试时,需要模拟某个用户的登录状态
  2. 在写单元测试时,需要模拟某个用户具有某个权限,但又不想改变数据库
  3. 编写单元测试时,需求完整调用某个用户的登录

解决需求:

springSecurity提供了相关的组件spring-security-test,可参考官方文档(https://docs.spring.io/spring-security/site/docs/5.0.6.RELEASE/reference/htmlsingle/#test-method-withmockuser),该组件提供了相关的注解来来模拟用户登录信息或者调用用户登录的方法

  • @WithMockUser 模拟用户,手动指定用户名和授权
  • @WithAnonymousUser 模拟匿名用户
  • @WithUserDetails 模拟用户,给定用户名,通过自定义UserDetails来认证
  • @WithSecurityContext 通过SecurityContext构造器模拟用户

例如

@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public void getMessageWithMockUserCustomUser() {
    String message = messageService.getMessage();
    ...
}

模拟了一个名叫admin的用户,拥有角色"USER","ADMIN"

代码范例


import com.alibaba.fastjson.JSONObject;
import org.junit.Before;
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.http.MediaType;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;

import java.util.HashMap;
import java.util.Map;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * 接口测试+ SpringSecurity的用户登录模拟
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
@Rollback(true)// 事务自动回滚,默认是true。可以不写
public class ExampleRestClientTest {


    private MockMvc mockMvc; // 模拟MVC对象,通过MockMvcBuilders.webAppContextSetup(this.wac).build()初始化。

    @Autowired
    private WebApplicationContext wac; // 注入WebApplicationContext

    @Before // 在测试开始前初始化工作
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).apply(springSecurity()).build();
    }

    @Test
    @WithUserDetails(value = "admin", userDetailsServiceBeanName = "customUserDetailsService")
    public void testQ1() throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("param1", "valueaa");

        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/secmenu/getUserMenuList")
                .contentType(MediaType.APPLICATION_JSON_UTF8).content(JSONObject.toJSONString(map)))
                .andExpect(status().is(200))// 模拟向testRest发送get请求
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))// 预期返回值的媒体类型text/plain;charset=UTF-8
                .andReturn();// 返回执行请求的结果

    }

    @Test
    public void testFormLoginSuccess() throws Exception {

        // 测试登录成功
        mockMvc
                .perform(formLogin("/login").user("admin").password("123456"))
                .andExpect(authenticated());
    }

    @Test
    public void testFormLoginFail() throws Exception {
        // 测试登录失败
        mockMvc
                .perform(formLogin("/login").user("admin").password("invalid"))
                .andExpect(unauthenticated());
    }

    @Test
    public void testLogoutFail() throws Exception {
        // 测试退出登录
        mockMvc.perform(logout("/logout")).andExpect(unauthenticated());
    }
}

完整项目工程参考

https://github.com/starmoon1994/springsecurity-collection

以上是关于2539-SpringSecurity系列--在有安全验证的情况下做单元测试Test的主要内容,如果未能解决你的问题,请参考以下文章

《HTTP协议:菜鸟入门系列》

设计模式系列-创建型模式篇-抽象工厂模式

hdu1072 逃离迷宫系列 bfs

cisco vpc

微服务-SpringCloud学习系列:负载均衡Ribbon

相关文章关联文章产品功能开发方案