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

参考:

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

public interface UserService {
    @PreAuthorize("hasAuthority('USER_QUERY')")
    Page<User> findAll(Pageable pageable);
    @PreAuthorize("hasAuthority('USER_SAVE')")
    User save(User user);
    @PreAuthorize("hasAuthority('USER_QUERY')")
    User findOne(Long id);
    @PreAuthorize("hasAuthority('USER_DELETE')")
    void delete(Long id);
}
要测试这些方法,单元测试可像如下写:
package org.exam.service;
import org.exam.config.AppConfig;
import org.exam.config.SecurityConfig;
import org.exam.domain.Authority;
import org.exam.domain.Role;
import org.exam.domain.User;
import org.exam.repository.AuthorityRepository;
import org.exam.repository.RoleRepository;
import org.exam.repository.UserRepository;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.ServletTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
 * Created by xin on 15.9.28.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class, SecurityConfig.class})
@Transactional(transactionManager = "transactionManager")
@TestExecutionListeners(listeners = {
        ServletTestExecutionListener.class,
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        WithSecurityContextTestExecutionListener.class})
public class UserServiceTest {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private AuthorityRepository authorityRepository;
    public static final String USERNAME = "admin";

    @Autowired
    private UserService userService;
    @Before
    public void before(){
        /*没做级联保存,那先保存这几个权限*/
        Authority authority1 = new Authority();
        authority1.setName("查看用户");
        authority1.setAuthority("USER_QUERY");
        Authority authority2 = new Authority();
        authority2.setName("保存用户");
        authority2.setAuthority("USER_SAVE");
        Authority authority3 = new Authority();
        authority3.setName("删除用户");
        authority3.setAuthority("USER_DELETE");
        List<Authority> authorities = Arrays.asList(authority1, authority2, authority3);
        authorityRepository.save(authorities);

		/*角色也一样,没做级联保存,那先保存角色*/
        Role role1 = new Role();
        role1.setName("管理员");
        role1.setAuthorities(new HashSet<Authority>(Arrays.asList(authority2, authority3)));
        roleRepository.save(role1);

		/*最后保存用户*/
        User user1 = new User();
        user1.setUsername(USERNAME);
        user1.setPassword("$2a$04$fCqcakHV2O.4AJgp3CIAGO9l5ZBq61Gt6YNzjcyC8M.js0ucpyun.");//admin
        user1.setCredentialsNonExpired(true);
        user1.setAccountNonLocked(true);
        user1.setEnabled(true);
        user1.setAccountNonExpired(true);
        user1.setAuthorities(new HashSet<Authority>(Arrays.asList(authority1, authority2)));
        user1.setRoles(new HashSet<Role>(Arrays.asList(role1)));
        user1 = userRepository.save(user1);
        assertNotNull(user1);
    }

    /*虚拟一个用户*/
    @WithMockUser(username = "admin",password = "admin",authorities = {"USER_QUERY","USER_SAVE","USER_DELETE"})
    /*通过UserDetailsService.loadUserByUsername根据用户名加载一个用户,与WithMockUser一样会先实例化用户,
    比执行@Before方法还要早,即如果下面的testFindAll()先保存一个用户,再通过@WithUserDetails是不能找到这个用户的*/
    //@WithUserDetails(UserRepositoryTest.USERNAME)
    @Test
    public void testFindAll() {
        Pageable pageable=new PageRequest(0,10);
        Page<User> page=userService.findAll(pageable);
        assertTrue("org.exam.service.UserService.findAll:failed",page.getContent().size()>0);
    }
    @After
    public void after(){
        userRepository.deleteAllInBatch();
        roleRepository.deleteAllInBatch();
        authorityRepository.deleteAllInBatch();
    }
}
参考中有说明@RunWith和@ContextConfiguration注解与其它的spring单元测试没有什么不同.我加入@Transactional是为了事务默认回滚.@TestExecutionListeners指明spring测试模块添加默认的一些监听器使用WithSecurityContextTestExcecutionListener来确保我们的测试使用正确的用户来运行.通过在运行我们的测试之前,放入SecurityContextHolder,测试完成后,清除SecurityContextHolder来实现这样的功能,这就是原理所在,很关键的理解.

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

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

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


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

package org.exam.web;

import org.exam.config.AppConfig;
import org.exam.config.SecurityConfig;
import org.exam.config.WebMvcConfig;
import org.exam.domain.Authority;
import org.exam.domain.Role;
import org.exam.domain.User;
import org.exam.repository.AuthorityRepository;
import org.exam.repository.RoleRepository;
import org.exam.repository.UserRepository;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.Filter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static org.junit.Assert.assertNotNull;
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.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
/**
 * Created by xin on 15.9.28.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class, SecurityConfig.class, WebMvcConfig.class})
@Transactional(transactionManager = "transactionManager")
@WebAppConfiguration
public class UserControllerTest {
    private static final String USERNAME = "admin";
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private AuthorityRepository authorityRepository;
    @Autowired
    private Filter springSecurityFilterChain;
    @Autowired
    private WebApplicationContext webApplicationContext;
    private MockMvc mockMvc;

    private void initData() {
        /*没做级联保存,那先保存这几个权限*/
        Authority authority1 = new Authority();
        authority1.setName("查看用户");
        authority1.setAuthority("USER_QUERY");
        Authority authority2 = new Authority();
        authority2.setName("保存用户");
        authority2.setAuthority("USER_SAVE");
        Authority authority3 = new Authority();
        authority3.setName("删除用户");
        authority3.setAuthority("USER_DELETE");
        List<Authority> authorities = Arrays.asList(authority1, authority2, authority3);
        authorityRepository.save(authorities);

        /*角色也一样,没做级联保存,那先保存角色*/
        Role role1 = new Role();
        role1.setName("管理员");
        role1.setAuthorities(new HashSet<Authority>(Arrays.asList(authority2, authority3)));
        roleRepository.save(role1);

        /*最后保存用户*/
        User user1 = new User();
        user1.setUsername(USERNAME);
        user1.setPassword("$2a$04$fCqcakHV2O.4AJgp3CIAGO9l5ZBq61Gt6YNzjcyC8M.js0ucpyun.");//admin
        user1.setCredentialsNonExpired(true);
        user1.setAccountNonLocked(true);
        user1.setEnabled(true);
        user1.setAccountNonExpired(true);
        user1.setAuthorities(new HashSet<Authority>(Arrays.asList(authority1, authority2)));
        user1.setRoles(new HashSet<Role>(Arrays.asList(role1)));
        user1 = userRepository.save(user1);
        assertNotNull(user1);
    }

    @Before
    public void before() throws Exception {
        initData();
        //如果启用了csrf,别忘了带上with(csrf())
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                .defaultRequest(get("/").with(csrf()).with(user(USERNAME).password(USERNAME)
                        .authorities(new SimpleGrantedAuthority("USER_QUERY"),
                                new SimpleGrantedAuthority("USER_SAVE"),
                                new SimpleGrantedAuthority("USER_DELETE"))))
                .addFilters(springSecurityFilterChain)
                .build();
    }
    @Test
    public void testLoginAndLogout() throws Exception {
        mockMvc.perform(formLogin("/login").user(USERNAME).password(USERNAME)).andExpect(authenticated().withUsername(USERNAME));
        mockMvc.perform(logout("/logout"));
    }

    @Test
    public void testList() throws Exception {
        mockMvc.perform(get("/user/list").param("page", "0").param("size", "10")).andExpect(status().isOk());
    }
    @Test
    public void testSave() throws Exception {
        User user=new User("xiejx618");
        user.setPassword("123456");
        mockMvc.perform(post("/user/save").param("passNonUpdate", "true").param("username", user.getUsername()).param("password", user.getPassword())
        ).andExpect(status().is3xxRedirection());
    }

    @Test
    public void testDelete() throws Exception {
        User user=userRepository.findByUsername(USERNAME);
        mockMvc.perform(get("/user/delete").param("id",user.getId().toString()))
                .andExpect(view().name("redirect:list"));
    }

    //@After
    public void after() {
        userRepository.deleteAllInBatch();
        roleRepository.deleteAllInBatch();
        authorityRepository.deleteAllInBatch();
        //注销用户
    }
}

这里主要是构造一个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


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值