基于java config的springSecurity--单元测试

本文详细介绍了如何使用 Spring Security 进行单元测试和服务层、Web 层的安全测试,包括使用 @WithMockUser 和 @WithUserDetails 注解创建虚拟用户及通过 MockMvc 模拟请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:

1.http://spring.io/blog/2014/05/07/preview-spring-security-test-method-security

2.http://spring.io/blog/2014/05/23/preview-spring-security-test-web-security

3.http://spring.io/blog/2014/05/23/preview-spring-security-test-htmlunit


应用一旦嵌入用户权限功能,测试就会变得稍为复杂,不止是spring security,其它框架应该亦如此.spring security提供了比较完善的测试案例.ROB WINCH写了三篇博客,从标题可大概看出主要讲什么,我对第1,2篇感兴趣,第3篇也不难,但不想花时间去实践.本文主要举例对第1,第2篇的部分关键点讲述与理解.

一.先来看看测试spring security的方法,个人可理解为:嵌入了权限方法(即使用了@PreAuthorize等注解的方法)的测试.在http://blog.csdn.NET/xiejx618/article/details/42739707基础上进行修改,将权限声明都放在service方法的接口上.

org.exam.service.UserService

[java]  view plain  copy
  1. public interface UserService {  
  2.     @PreAuthorize("hasAuthority('USER_QUERY')")  
  3.     Page<User> findAll(Pageable pageable);  
  4.     @PreAuthorize("hasAuthority('USER_SAVE')")  
  5.     User save(User user);  
  6.     @PreAuthorize("hasAuthority('USER_QUERY')")  
  7.     User findOne(Long id);  
  8.     @PreAuthorize("hasAuthority('USER_DELETE')")  
  9.     void delete(Long id);  
  10. }  
要测试这些方法,单元测试可像如下写:
[java]  view plain  copy
  1. package org.exam.service;  
  2. import org.exam.config.AppConfig;  
  3. import org.exam.config.SecurityConfig;  
  4. import org.exam.domain.Authority;  
  5. import org.exam.domain.Role;  
  6. import org.exam.domain.User;  
  7. import org.exam.repository.AuthorityRepository;  
  8. import org.exam.repository.RoleRepository;  
  9. import org.exam.repository.UserRepository;  
  10. import org.junit.After;  
  11. import org.junit.Before;  
  12. import org.junit.Test;  
  13. import org.junit.runner.RunWith;  
  14. import org.springframework.beans.factory.annotation.Autowired;  
  15. import org.springframework.data.domain.Page;  
  16. import org.springframework.data.domain.PageRequest;  
  17. import org.springframework.data.domain.Pageable;  
  18. import org.springframework.security.test.context.support.WithMockUser;  
  19. import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;  
  20. import org.springframework.test.context.ContextConfiguration;  
  21. import org.springframework.test.context.TestExecutionListeners;  
  22. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;  
  23. import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;  
  24. import org.springframework.test.context.support.DirtiesContextTestExecutionListener;  
  25. import org.springframework.test.context.transaction.TransactionalTestExecutionListener;  
  26. import org.springframework.test.context.web.ServletTestExecutionListener;  
  27. import org.springframework.transaction.annotation.Transactional;  
  28. import java.util.Arrays;  
  29. import java.util.HashSet;  
  30. import java.util.List;  
  31. import static org.junit.Assert.assertNotNull;  
  32. import static org.junit.Assert.assertTrue;  
  33. /** 
  34.  * Created by xin on 15.9.28. 
  35.  */  
  36. @RunWith(SpringJUnit4ClassRunner.class)  
  37. @ContextConfiguration(classes = {AppConfig.class, SecurityConfig.class})  
  38. @Transactional(transactionManager = "transactionManager")  
  39. @TestExecutionListeners(listeners = {  
  40.         ServletTestExecutionListener.class,  
  41.         DependencyInjectionTestExecutionListener.class,  
  42.         DirtiesContextTestExecutionListener.class,  
  43.         TransactionalTestExecutionListener.class,  
  44.         WithSecurityContextTestExecutionListener.class})  
  45. public class UserServiceTest {  
  46.     @Autowired  
  47.     private UserRepository userRepository;  
  48.     @Autowired  
  49.     private RoleRepository roleRepository;  
  50.     @Autowired  
  51.     private AuthorityRepository authorityRepository;  
  52.     public static final String USERNAME = "admin";  
  53.   
  54.     @Autowired  
  55.     private UserService userService;  
  56.     @Before  
  57.     public void before(){  
  58.         /*没做级联保存,那先保存这几个权限*/  
  59.         Authority authority1 = new Authority();  
  60.         authority1.setName("查看用户");  
  61.         authority1.setAuthority("USER_QUERY");  
  62.         Authority authority2 = new Authority();  
  63.         authority2.setName("保存用户");  
  64.         authority2.setAuthority("USER_SAVE");  
  65.         Authority authority3 = new Authority();  
  66.         authority3.setName("删除用户");  
  67.         authority3.setAuthority("USER_DELETE");  
  68.         List<Authority> authorities = Arrays.asList(authority1, authority2, authority3);  
  69.         authorityRepository.save(authorities);  
  70.   
  71.         /*角色也一样,没做级联保存,那先保存角色*/  
  72.         Role role1 = new Role();  
  73.         role1.setName("管理员");  
  74.         role1.setAuthorities(new HashSet<Authority>(Arrays.asList(authority2, authority3)));  
  75.         roleRepository.save(role1);  
  76.   
  77.         /*最后保存用户*/  
  78.         User user1 = new User();  
  79.         user1.setUsername(USERNAME);  
  80.         user1.setPassword("$2a$04$fCqcakHV2O.4AJgp3CIAGO9l5ZBq61Gt6YNzjcyC8M.js0ucpyun.");//admin  
  81.         user1.setCredentialsNonExpired(true);  
  82.         user1.setAccountNonLocked(true);  
  83.         user1.setEnabled(true);  
  84.         user1.setAccountNonExpired(true);  
  85.         user1.setAuthorities(new HashSet<Authority>(Arrays.asList(authority1, authority2)));  
  86.         user1.setRoles(new HashSet<Role>(Arrays.asList(role1)));  
  87.         user1 = userRepository.save(user1);  
  88.         assertNotNull(user1);  
  89.     }  
  90.   
  91.     /*虚拟一个用户*/  
  92.     @WithMockUser(username = "admin",password = "admin",authorities = {"USER_QUERY","USER_SAVE","USER_DELETE"})  
  93.     /*通过UserDetailsService.loadUserByUsername根据用户名加载一个用户,与WithMockUser一样会先实例化用户, 
  94.     比执行@Before方法还要早,即如果下面的testFindAll()先保存一个用户,再通过@WithUserDetails是不能找到这个用户的*/  
  95.     //@WithUserDetails(UserRepositoryTest.USERNAME)  
  96.     @Test  
  97.     public void testFindAll() {  
  98.         Pageable pageable=new PageRequest(0,10);  
  99.         Page<User> page=userService.findAll(pageable);  
  100.         assertTrue("org.exam.service.UserService.findAll:failed",page.getContent().size()>0);  
  101.     }  
  102.     @After  
  103.     public void after(){  
  104.         userRepository.deleteAllInBatch();  
  105.         roleRepository.deleteAllInBatch();  
  106.         authorityRepository.deleteAllInBatch();  
  107.     }  
  108. }  
参考中有说明@RunWith和@ContextConfiguration注解与其它的spring单元测试没有什么不同.我加入@Transactional是为了事务默认回滚.@TestExecutionListeners指明spring测试模块添加默认的一些监听器使用WithSecurityContextTestExcecutionListener来确保我们的测试使用正确的用户来运行.通过在运行我们的测试之前,放入SecurityContextHolder,测试完成后,清除SecurityContextHolder来实现这样的功能,这就是原理所在,很关键的理解.

1.使用@WithMockUser来虚拟一个用户,实际上,这个用户可以不存在的.上面的testFindAll就是一个例子.

2.使用@WithUserDetails,通过UserDetailsService.loadUserByUsername根据用户名加载一个用户.从上面的原理可知,这个用户必须在运行单元测试之前就存在,这对有些测试带来不便,我也想不出什么方法来改善.

3.通过@WithSecurityContext实现自定义的@WithMockUser和@WithUserDetails,一般都用不上吧.


二.再看看测试web层,也就是和Controller打交道.下面是单元测试方法的举例.

[java]  view plain  copy
  1. package org.exam.web;  
  2.   
  3. import org.exam.config.AppConfig;  
  4. import org.exam.config.SecurityConfig;  
  5. import org.exam.config.WebMvcConfig;  
  6. import org.exam.domain.Authority;  
  7. import org.exam.domain.Role;  
  8. import org.exam.domain.User;  
  9. import org.exam.repository.AuthorityRepository;  
  10. import org.exam.repository.RoleRepository;  
  11. import org.exam.repository.UserRepository;  
  12. import org.junit.Before;  
  13. import org.junit.Test;  
  14. import org.junit.runner.RunWith;  
  15. import org.springframework.beans.factory.annotation.Autowired;  
  16. import org.springframework.security.core.authority.SimpleGrantedAuthority;  
  17. import org.springframework.test.context.ContextConfiguration;  
  18. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;  
  19. import org.springframework.test.context.web.WebAppConfiguration;  
  20. import org.springframework.test.web.servlet.MockMvc;  
  21. import org.springframework.test.web.servlet.setup.MockMvcBuilders;  
  22. import org.springframework.transaction.annotation.Transactional;  
  23. import org.springframework.web.context.WebApplicationContext;  
  24. import javax.servlet.Filter;  
  25. import java.util.Arrays;  
  26. import java.util.HashSet;  
  27. import java.util.List;  
  28. import static org.junit.Assert.assertNotNull;  
  29. import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;  
  30. import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout;  
  31. import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;  
  32. import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;  
  33. import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;  
  34. import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;  
  35. import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;  
  36. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;  
  37. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;  
  38. /** 
  39.  * Created by xin on 15.9.28. 
  40.  */  
  41. @RunWith(SpringJUnit4ClassRunner.class)  
  42. @ContextConfiguration(classes = {AppConfig.class, SecurityConfig.class, WebMvcConfig.class})  
  43. @Transactional(transactionManager = "transactionManager")  
  44. @WebAppConfiguration  
  45. public class UserControllerTest {  
  46.     private static final String USERNAME = "admin";  
  47.     @Autowired  
  48.     private UserRepository userRepository;  
  49.     @Autowired  
  50.     private RoleRepository roleRepository;  
  51.     @Autowired  
  52.     private AuthorityRepository authorityRepository;  
  53.     @Autowired  
  54.     private Filter springSecurityFilterChain;  
  55.     @Autowired  
  56.     private WebApplicationContext webApplicationContext;  
  57.     private MockMvc mockMvc;  
  58.   
  59.     private void initData() {  
  60.         /*没做级联保存,那先保存这几个权限*/  
  61.         Authority authority1 = new Authority();  
  62.         authority1.setName("查看用户");  
  63.         authority1.setAuthority("USER_QUERY");  
  64.         Authority authority2 = new Authority();  
  65.         authority2.setName("保存用户");  
  66.         authority2.setAuthority("USER_SAVE");  
  67.         Authority authority3 = new Authority();  
  68.         authority3.setName("删除用户");  
  69.         authority3.setAuthority("USER_DELETE");  
  70.         List<Authority> authorities = Arrays.asList(authority1, authority2, authority3);  
  71.         authorityRepository.save(authorities);  
  72.   
  73.         /*角色也一样,没做级联保存,那先保存角色*/  
  74.         Role role1 = new Role();  
  75.         role1.setName("管理员");  
  76.         role1.setAuthorities(new HashSet<Authority>(Arrays.asList(authority2, authority3)));  
  77.         roleRepository.save(role1);  
  78.   
  79.         /*最后保存用户*/  
  80.         User user1 = new User();  
  81.         user1.setUsername(USERNAME);  
  82.         user1.setPassword("$2a$04$fCqcakHV2O.4AJgp3CIAGO9l5ZBq61Gt6YNzjcyC8M.js0ucpyun.");//admin  
  83.         user1.setCredentialsNonExpired(true);  
  84.         user1.setAccountNonLocked(true);  
  85.         user1.setEnabled(true);  
  86.         user1.setAccountNonExpired(true);  
  87.         user1.setAuthorities(new HashSet<Authority>(Arrays.asList(authority1, authority2)));  
  88.         user1.setRoles(new HashSet<Role>(Arrays.asList(role1)));  
  89.         user1 = userRepository.save(user1);  
  90.         assertNotNull(user1);  
  91.     }  
  92.   
  93.     @Before  
  94.     public void before() throws Exception {  
  95.         initData();  
  96.         //如果启用了csrf,别忘了带上with(csrf())  
  97.         mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)  
  98.                 .defaultRequest(get("/").with(csrf()).with(user(USERNAME).password(USERNAME)  
  99.                         .authorities(new SimpleGrantedAuthority("USER_QUERY"),  
  100.                                 new SimpleGrantedAuthority("USER_SAVE"),  
  101.                                 new SimpleGrantedAuthority("USER_DELETE"))))  
  102.                 .addFilters(springSecurityFilterChain)  
  103.                 .build();  
  104.     }  
  105.     @Test  
  106.     public void testLoginAndLogout() throws Exception {  
  107.         mockMvc.perform(formLogin("/login").user(USERNAME).password(USERNAME)).andExpect(authenticated().withUsername(USERNAME));  
  108.         mockMvc.perform(logout("/logout"));  
  109.     }  
  110.   
  111.     @Test  
  112.     public void testList() throws Exception {  
  113.         mockMvc.perform(get("/user/list").param("page""0").param("size""10")).andExpect(status().isOk());  
  114.     }  
  115.     @Test  
  116.     public void testSave() throws Exception {  
  117.         User user=new User("xiejx618");  
  118.         user.setPassword("123456");  
  119.         mockMvc.perform(post("/user/save").param("passNonUpdate""true").param("username", user.getUsername()).param("password", user.getPassword())  
  120.         ).andExpect(status().is3xxRedirection());  
  121.     }  
  122.   
  123.     @Test  
  124.     public void testDelete() throws Exception {  
  125.         User user=userRepository.findByUsername(USERNAME);  
  126.         mockMvc.perform(get("/user/delete").param("id",user.getId().toString()))  
  127.                 .andExpect(view().name("redirect:list"));  
  128.     }  
  129.   
  130.     //@After  
  131.     public void after() {  
  132.         userRepository.deleteAllInBatch();  
  133.         roleRepository.deleteAllInBatch();  
  134.         authorityRepository.deleteAllInBatch();  
  135.         //注销用户  
  136.     }  
  137. }  

这里主要是构造一个MockMvc,用户信息可在构造MockMvc时加上,也可以在发模拟请求时加上.上面是前者的这种情况,如果你的spring security应用启用了scrf,别忘了带上with(csrf()).我昨天就在这问题上卡了二个多小时,后来调试时,发觉被csrf的filter拦截直接返回304,而没有跳入controller的方法,我刚开始怀疑是源码的bug,是我多虑了!参考还举例了登入和登出.


三.建议结合源码例子https://github.com/rwinch/spring-security-test-blog/blob/master/src/test/Java/sample/htmlunit/MockMvcHtmlUnitCreateMessageTest.java  ,我理解要先启动web应用,通过htmlunit来模拟用户的一些操作行为来测试,哈哈不感兴趣!


温馨提示:如果使用的IDE是idea,对于一个类自动生成相应单元测试类的方法是:右键编辑区-->Go To-->Test.(windows的快捷键是ctrl+shift+t)


源码:http://download.csdn.net/detail/xiejx618/9145825

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值