Использование теста безопасности Spring для тестирования защищенного контроллера Spring MVC
Следуя документации по использованию Spring Security Test для написания тестов для приложения Spring MVC, которое подключено к Spring Security.
Это ванильное приложение с пружинной загрузкой, использующее типичную пружинную защитную проводку. Вот основной Application.java
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
Вот проводка для пружинной безопасности:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/","/sign_up").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf()
.and()
.exceptionHandling()
.accessDeniedPage("/access_denied")
.and()
.logout()
.permitAll();
}
}
Как вы можете видеть, все запросы должны быть аутентифицированы, за исключением запросов "/" и "/sign_up". Развернув приложение, я убедился, что схемы аутентификации работают нормально.
Теперь начинается интересная часть: написание весенних тестов MVC. Ссылка, которую я предоставил, дает несколько хороших способов написания таких тестов, в которых среда Spring -security-test позволяет вставлять фиктивные пользователи / контексты безопасности. Я принял подход
- определение ложного интерфейса UserDetails и
- используя SecurityContextFactory для создания SecurityContext, который
- должен быть вставлен в контекст приложения во время запуска теста.
Код для 1. выглядит следующим образом:
@WithSecurityContext(factory=WithMockUserDetailsSecurityContextFactory.class)
public @interface WithMockUserDetails {
String firstName() default "apil";
String lastName() default "tamang";
String password() default "test";
long id() default 999;
String email() default "apil@test.com";
}
Код для 2. выглядит следующим образом:
final class WithMockUserDetailsSecurityContextFactory
implements WithSecurityContextFactory<WithMockUserDetails>{
@Override
public SecurityContext createSecurityContext(WithMockUserDetails mockUserDetails) {
/*
* Use an anonymous implementation for 'UserDetails' to return a
* mock authentication object, which is then set to the SecurityContext
* for the test runs.
*/
UserDetails principal=new UserDetails() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//another anonmyous interface implementation.
GrantedAuthority auth=new GrantedAuthority() {
@Override
public String getAuthority() {
return "ROLE_USER";
}
};
List<GrantedAuthority> authorities=new ArrayList<>();
authorities.add(auth);
return authorities;
}
@Override
public String getPassword() {
return mockUserDetails.password();
}
@Override
public String getUsername() {
return mockUserDetails.email();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
};
Authentication authentication=new
UsernamePasswordAuthenticationToken(principal,principal.getPassword(),principal.getAuthorities());
SecurityContext context= SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
Наконец, вот тестовый класс:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebAppConfiguration
public class UserControllerTest {
@Autowired
private WebApplicationContext context;
@Autowired
private Filter springSecurityFilterChain;
private MockMvc mvc;
@Before
public void setup(){
mvc= MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
@Test
public void testRootIsOk() throws Exception {
mvc.perform(get("/"))
.andExpect(status().isOk());
}
@Test
public void expectRedirectToLogin() throws Exception {
mvc.perform(get("/testConnect"))
.andExpect(redirectedUrl("http://localhost/login"));
}
@Test
@WithMockUserDetails
public void testAuthenticationOkay() throws Exception {
mvc.perform(get("/testConnect"))
.andExpect(content().string("good request."));
}
}
Результат тестового прогона:
- Тест 1 проходит.
- Тест 2 проходит.
- Тест 3 не проходит. Ожидаемый результат, но получил <>.
Весьма вероятно, что тест 3 не удался, потому что "SecurityContext" не был заполнен соответствующим образом. Согласно документации, это должно было сработать. Не уверен, что я пропустил. Буду признателен за любую оказанную помощь.
1 ответ
Вам нужно будет добавить
@Retention(RetentionPolicy.RUNTIME)
к вашей пользовательской аннотации, чтобы сохранить информацию аннотации во время выполнения. Иначе WithSecurityContextTestExecutionListener
не могу обнаружить вашу аннотацию.