使用 Spring Security 进行 Spring Boot 单元测试
Posted
技术标签:
【中文标题】使用 Spring Security 进行 Spring Boot 单元测试【英文标题】:Spring Boot Unit Testing with Spring Security 【发布时间】:2017-10-21 09:24:56 【问题描述】:我有一个简单的应用程序,我使用自定义 mysql 数据库设置了 Spring Security。现在我正在为它编写测试用例,它们似乎在登录页面和登录后任何有效的东西上都失败了。我的问题是如何为它编写测试用例来检查成功登录和后续请求?
我的安全配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private DataSource dataSource;
@Value("$spring.queries.users-query")
private String usersQuery;
@Value("$spring.queries.roles-query")
private String rolesQuery;
@Autowired
private CustomAuthenticationSuccessHandler successHandler;
/** Providing the queries and data source for security*/
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception
auth.
jdbcAuthentication()
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource)
.passwordEncoder(bCryptPasswordEncoder);
/** Defining fine grained access for ADMIN and CUSTOMER user */
@Override
protected void configure(HttpSecurity http) throws Exception
http.
authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/registration").permitAll()
.antMatchers("/user/**").hasAuthority(AppRole.CUSTOMER.toString())
.antMatchers("/health/**").hasAuthority(AppRole.ADMIN.toString())
.antMatchers("/admin/**").hasAuthority(AppRole.ADMIN.toString()).anyRequest()
.authenticated().and().csrf().disable().formLogin()
.loginPage("/login").failureUrl("/login?error=true")
.successHandler(successHandler)
.usernameParameter("username")
.passwordParameter("password")
.and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/").and().exceptionHandling()
.accessDeniedPage("/access-denied");
/** Defining ant matchers that should ignore the paths and provide no access to any one */
@Override
public void configure(WebSecurity web) throws Exception
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
我的自定义成功处理程序:
@Component
@Configuration
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler
/** Getting reference to UserService */
@Autowired
private UserService userService;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Authentication authentication)
throws IOException, ServletException, RuntimeException
HttpSession session = httpServletRequest.getSession();
User authUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
com.crossover.techtrial.java.se.model.User user = userService.findUserByUsername(authUser.getUsername());
session.setAttribute("userId", user.getUserId());
session.setAttribute("username", authUser.getUsername());
session.setAttribute("accountId", user.getAccountId());
//set our response to OK status
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
authorities.forEach(authority ->
if(authority.getAuthority().equals(AppRole.ADMIN.toString()))
session.setAttribute("role", AppRole.ADMIN);
try
//since we have created our custom success handler, its up to us to where
//we will redirect the user after successfully login
httpServletResponse.sendRedirect("/admin/home");
catch (IOException e)
throw new RuntimeException(e);
else if (authority.getAuthority().equals(AppRole.CUSTOMER.toString()))
session.setAttribute("role", AppRole.CUSTOMER);
try
//since we have created our custom success handler, its up to us to where
//we will redirect the user after successfully login
httpServletResponse.sendRedirect("/user/home");
catch (IOException e)
throw new RuntimeException(e);
);
经过一番搜索后,我尝试编写这样的测试用例,但它们似乎不起作用:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TrialApplicationTests
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Autowired
private MockHttpServletRequest request;
private MockMvc mockMvc;
@Test
public void contextLoads()
@Before
public void setup()
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilters(springSecurityFilterChain)
.build();
@Test
public void verifiesLoginPageLoads() throws Exception
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.view().name("login"))
.andExpect(MockMvcResultMatchers.status().isOk());
@Test
public void testUserLogin() throws Exception
HttpSession session = mockMvc.perform(post("/login")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("username", "test")
.param("password", "test123")
)
.andExpect(MockMvcResultMatchers.status().isOk())
//.andExpect(redirectedUrl("/user/home"))
.andReturn()
.getRequest()
.getSession();
request.setSession(session);
SecurityContext securityContext = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
SecurityContextHolder.setContext(securityContext);
@Test
public void testRetrieveUserBookings() throws Exception
testUserLogin();
mockMvc.perform(MockMvcRequestBuilders.get("user/bookings"))
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.model().attributeExists("bookings"))
.andExpect(MockMvcResultMatchers.view().name("user/bookings"))
.andExpect(content().string(containsString("Booking")));
I searched on the net and there are links WithMockUser and UserDetails, but the problem is as you can see I'm setting a my primary key userId in the session in my custom success handler. So I would also need to get the session in my test. Please tell me the simplest way to write tests that will work, possibly with code since I'm new with security and all such.
UPDATE:
I changed the code as suggested but still getting the 404 error on my testRetrieveUserBookings. Any more ideas?
@RunWith(SpringRunner.class)
@ContextConfiguration
@SpringBootTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@TestExecutionListeners(listeners=ServletTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
WithSecurityContextTestExecutionListener.class)
public class TrialApplicationTests
@Autowired
private WebApplicationContext webApplicationContext;
MockMvc mockMvc;
@Autowired
ForestApiClient apiClient;
@Autowired
AccountClient accountClient;
@Autowired
AirlineClient airlineClient;
@Autowired
UserService userService;
private final String INTEGRATION_ACCOUNT = "account1";
private MockHttpSession mockSession;
private Authentication authentication;
@Test
public void contextLoads()
@Before
public void setup()
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
//.addFilters(springSecurityFilterChain)
.build();
mockSession = new MockHttpSession(webApplicationContext.getServletContext(), UUID.randomUUID().toString());
mockSession.setAttribute("userId", 3);
mockSession.setAttribute("accountId", "ZWR26539");
@Test
public void testVerifiesLoginPageLoads() throws Exception
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.view().name("login"))
.andExpect(MockMvcResultMatchers.status().isOk());
@Test
public void testRegistration() throws Exception
mockMvc.perform(post("/registration")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("username", "test2")
.param("password", "test123")
.param("email", "crossovertestuser@gmail.com")
.param("address", "Some Address")
.param("accountCurrency", "USD")
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.model().attributeExists("user"))
.andExpect(MockMvcResultMatchers.view().name("registration"))
.andExpect(content().string(containsString("User has been registered successfully")));
@Test
@WithMockUser(username="test",roles="USER","ADMIN")
public void testRetrieveUserBookings() throws Exception
mockMvc.perform(MockMvcRequestBuilders.get("user/bookings"))
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.model().attributeExists("bookings"))
.andExpect(MockMvcResultMatchers.view().name("user/bookings"))
.andExpect(content().string(containsString("Booking")));
【问题讨论】:
【参考方案1】:如果您的问题只是在测试中获取会话,那么您可以使用 MockHttpSession。
@Before
public void setUp() throws Exception
mock = MockMvcBuilders.webAppContextSetup(wac).addFilters(springSecurityFilterChain).build();
MockHttpSession httpSession = new MockHttpSession(webAppContext.getServletContext(), UUID.randomUUID().toString());
@Test
public void test1()
mock.perform(get("/").session(mockSession)).perfor();
【讨论】:
我按照你告诉我的做了,但它似乎没有从会话中选择值。我收到 404 错误。请查看更新代码的编辑。以上是关于使用 Spring Security 进行 Spring Boot 单元测试的主要内容,如果未能解决你的问题,请参考以下文章
在 Grails 3.0 中配置 Spring Boot Security 以使用 BCrypt 密码编码
Maven:Spring 4 + Spring Security
spring security 没有调用 loadUserByUsername() 方法