Учитывая то, что мы вообще не хотим тестировать безопасность, лучшее решение для такого рода случаев заключается в следующем:
- использовать стандартные средства управления безопасностью Spring *
@WithMockUser
вместе с MockMvc
- Адаптируйте
ResourceServerConfigurerAdapter
для тестов: - Создайте базовый класс, содержащий все конфигурации, кроме токенов
- Создайте наследующий класс для профилей, не относящихся к тестам (
@ActiveProfiles("!test")
) в которой размещается конфигурация, специфичная для токена - создает наследующий класс для тестового профиля, который отключает удаленную проверку токена (
security.stateless(false);
) - , чтобы тестовые классы использовали
test
профиль - Внедрить правильную информацию, извлеченную из токенов, в нужное время в тестах
Вот как это было реализовано на практике:
База ResourceServerConfigurerAdapter
, чтобы конфигурация имелаОсновная общая часть между контекстами тестов и не-тестов:
public class BaseResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("ms/legacy");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll().and().cors().disable().csrf().disable().httpBasic().disable()
.exceptionHandling()
.authenticationEntryPoint(
(request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.accessDeniedHandler(
(request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED));
}
}
Его реализация вне для не-тестов:
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Profile("!test")
public class ResourceServerConfiguration extends BaseResourceServerConfiguration {
@Value("${auth-server.url}")
private String authEndpoint;
@Value("${security.oauth2.client.client-id}")
private String clientId;
@Value("${security.oauth2.client.client-secret}")
private String clientSecret;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("ms/legacy");
}
@Bean
public ResourceServerTokenServices tokenService() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId(clientId);
tokenServices.setClientSecret(clientSecret);
tokenServices.setCheckTokenEndpointUrl(authEndpoint + "/uaa/oauth/check_token");
return tokenServices;
}
}
И для тестов:
@Configuration
@EnableResourceServer
@ActiveProfiles("test")
public class TestResourceServerConfigurerAdapter extends BaseResourceServerConfiguration {
@Override
public void configure(ResourceServerSecurityConfigurer security) throws Exception {
super.configure(security);
// Using OAuth with distant authorization service, stateless implies that the request tokens
// are verified each time against this service. In test, we don't want that because we need
// properly isolated tests. Setting this implies that the security is checked only locally
// and allows us to mock it with @WithMockUser, @AutoConfigureMockMvc and autowired MockMVC
security.stateless(false);
}
}
Введите информацию о токене с помощью фильтра запросовr тесты:
@Component
@ActiveProfiles("test")
public class TestRequestFilter extends OncePerRequestFilter {
private Optional<InfoConf> nextInfoConf = Optional.empty();
// Request info is our request-scoped bean that holds JWT info
@Autowired
private RequestInfo info;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
if (nextInfoConf.isPresent()) {
info.setInfoConf(nextInfoConf.get());
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
public void setNextInfoConf(InfoConf nextInfoConf) {
this.nextInfoConf = Optional.of(nextInfoConf);
}
public void clearNextInfoConf() {
nextInfoConf = Optional.empty();
}
}
И, конечно, заставить анализ JWT ничего не делать, когда нет JWT.
Мы также написали небольшой служебный компонент для создания соответствующей информации для внедрения.
Типичный интеграционный тест будет выглядеть следующим образом:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class TestClass {
@Autowired
protected MockMvc mockMvc;
@Before
public void before() {
// Create an user in DB
// Inject the related information in our filter
}
@After
public void after() {
// Cleanup both in DB and filter
}
@Test
@WithMockUser
public void testThing() throws Exception {
// Use MockMVC
}
}
Другое решение - это действительно смоделировать ResourceServerTokenServices
, но на самом деле гораздо сложнее создать правильные токены и использовать стандартный макет безопасности Spring.кажется гораздо более уместным.