我有一个Spring Boot + Spring Security应用程序,它有几个antMatchers
路径;一些fullyAuthenticated()
,一些permitAll()
。
如何编写一个验证SecurityConfiguration
的测试,我的端点在/api/**
(以及最终其他)下正确保护?
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
//...
.antMatchers("/api/**").fullyAuthenticated()
}
}
使用spring-boot-1.5.2.RELEASE
,spring-security-core-4.2.2-release
。
澄清1:我想尽可能直接测试SecurityConfiguration
,而不是通过其中一个/api/**
端点进行传递测试,这些端点可能有自己的@PreAuthorize
安全性。
澄清2:我想要类似于这个WebSecurityConfigurerAdapterTests。
澄清3:我想@Autowire
在Spring Security层,理想情况下HttpSecurity
,进行测试。
所以你想确保如果有人将.antMatchers("/api/**")
更改为.antMatchers("/WRONG_PATH/**")
,那么你有一个可以计算出来的测试吗?
使用HttpSecurity
定义的规则最终将使用一个或多个FilterChainProxy
配置SecurityFilterChain
,每个UsernamePasswordAuthenticationFilter
都有一个过滤器列表。每个过滤器,例如RequestMatcher
(用于基于表单的登录),将在超类AbstractAuthenticationProcessingFilter
中定义RequestMatcher
。问题是AndRequestMatcher
是一个接口,目前有12种不同的实现,这包括OrRequestMatcher
和RequestMatcher
,所以匹配逻辑并不总是很简单。最重要的是boolean matches(HttpServletRequest request)
只有一个方法RequestMatcher
,并且实现通常不会公开配置,因此您将不得不使用反射来访问每个FilterChainProxy
实现的私有配置(将来可能会更改)。
如果沿着这条路走下去,并将WebSecurityConfigurerAdapter
自动装配到测试中并使用反射来反向设计配置,则必须考虑所有的实现依赖关系。例如,RequestMatchers
有一个默认的过滤器列表,它可能会在不同版本之间发生变化,除非禁用它,否则当它被禁用时你必须明确定义每个过滤器。此外,随着时间的推移可能会添加新的过滤器和HttpSecurity
,或者@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
public class WebConfigIT {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Before
public void setup() throws Exception {
mockMvc = webAppContextSetup(webApplicationContext)
.addFilter(springSecurityFilterChain)
.build();
}
@Test
public void testAuthenticationAtAPIURI() throws Exception {
mockMvc.perform(get("/api/xyz"))
.andExpect(status.is3xxRedirection());
}
在一个版本的Spring Security中生成的过滤器链在下一个版本中可能略有不同(可能不太可能,但仍然可能)。
为您的spring安全配置编写通用测试在技术上是可行的,但这并不是一件容易的事情,Spring Security过滤器肯定不是为了支持这一点而设计的。自2010年以来,我一直在与Spring Security进行过广泛的合作,而且我从未需要过这样的测试,而且我个人认为尝试实现它是浪费时间。我认为花在编写测试框架上的时间会更好,这使得编写集成测试变得容易,这将隐式地测试安全层以及业务逻辑。
我看下面的测试用例可以帮助你实现你想要的。它是一个测试Web安全配置的集成测试,我们对所有TDD驱动的代码进行了类似的测试。
@SpringBootTest
这虽然看起来像是对端点进行显式测试(无论如何都是测试人员必须做的TDD),但这也使Spring安全过滤器链在上下文中使您能够测试APP的安全上下文。
MockMVC应该足以验证您的安全配置,因为它唯一的模拟是Http层。但是,如果你真的想测试你的Spring Boot应用程序,Tomcat服务器和所有,你需要使用@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class NoGoServiceTest {
@LocalServerPort
private int port;
private <T> T makeDepthRequest(NoGoRequest request, NoGoResponse response, String path, Class<T> responseClass) {
testService.addRequestResponseMapping(request, response);
RestTemplate template = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Bearer " + tokenProvider.getToken());
RequestEntity<NoGoRequest> requestEntity = new RequestEntity<>(request, headers, HttpMethod.POST, getURI(path));
ResponseEntity<T> responseEntity = template.exchange(requestEntity, responseClass);
return responseEntity.getBody();
}
@SneakyThrows(URISyntaxException.class)
private URI getURI(String path) {
return new URI("http://localhost:" +port + "/nogo" + path);
}
// Test that makes request using `makeDepthRequest`
}
,就像这样
https://github.com/maritime-web/NoGoService
此代码是从开源项目(RestTemplate
)获取的测试的一部分。基本思想是在随机端口上开始测试,然后Spring会在测试中注入一个字段。这允许您使用与控制器相同的DTO类来构造URL并使用Springs GET /login
向服务器发出http请求。如果身份验证机制是Basic或Token,则只需添加正确的Authorization标头,如本例所示。如果您使用表单身份验证,那么它会变得有点困难,因为您首先必须使用POST
,然后提取CSRF令牌和JSessionId cookie,并使用/login
的凭据将RequestHandlerProvider
用于它们,并且在登录后您必须提取新的JSessionId cookie,因为出于安全原因登录后更改了sessionId。
希望这是你需要的。
如果要以编程方式知道存在哪些端点,可以将@Autowired
List<RequestHandlerProvider> handlerProviders;
@Test
public void doTest() {
for (RequestHandlerProvider handlerProvider : handlerProviders) {
for (RequestHandler requestHandler : handlerProvider.requestHandlers()) {
for (String pattern : requestHandler.getPatternsCondition().getPatterns()) {
// call the endpoint without security and check that you get 401
}
}
}
}
列表自动装配到测试中,并根据它们所暴露的路径对其进行过滤。
RequestHandlerProvider
使用public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final String[] FULLY_AUTH_PUBLIC_URLS = {"/api/**", "/swagger-resources/**", "/health", "/info" };
protected void configure(HttpSecurity http) throws Exception {
http
//...
.antMatchers(FULLY_AUTH_PUBLIC_URLS).fullyAuthenticated()
}
}
是SpringFox在构建API的swagger定义时如何确定哪个端点可用及其签名。
除非您花费很长时间为每个端点构建正确的输入,否则在包含有效的安全令牌时,您将无法从端点返回200 OK,因此您可能必须接受400作为正确的响应。
如果您已经担心某些开发人员在引入新端点时会出现与安全性相关的错误,我会同样担心端点的逻辑,这就是为什么我认为您应该对每个端点进行集成测试,并且会测试你的安全。
稍微在盒子外思考,并以不同的方式回答问题,简单地定义静态String [],例如,
qazxswpoi
...
然后,如果测试的目的是确保不对公共URL进行任何更改,只需测试已知列表?
这里的假设是Spring Security工作并且已经过测试,因此我们唯一测试的是公共URL列表没有被更改。如果他们已经改变了测试,那么应该向开发人员突出显示有龙改变这些值吗?我理解这不包括澄清,但假设提供的静态公共URL是准确的,那么如果需要,这种方法将提供单元可测试的后退。