【问题标题】:Spring Boot 2: test secured endpointsSpring Boot 2:测试安全端点
【发布时间】:2019-07-10 09:47:55
【问题描述】:

我有一个使用 spring boot 2 构建的项目(数据、休息、安全......),所以在项目中我有这样的休息控制器:

@RestController
class ProductController {

private final ProductService productService;
private final ProductConverter productConverter;
private final UserService userService;

@Autowired
ProductController(ProductService productService,
                  ProductConverter productConverter,
                  UserService userService) {
    this.productService = productService;
    this.productConverter = productConverter;
    this.userService = userService;
}

@GetMapping(EndpointPath.PRODUCTS)
PageableProductsDTO getProducts(Principal principal, Pageable pageable) {
    return productService.getUsersProducts(principal.getName(), pageable);
}

@PostMapping(EndpointPath.PRODUCT)
ResponseEntity<?> createNewProduct(@Valid @RequestBody ProductDetailsDto productDetailsDto, Principal principal) {
   productService.saveProduct(productConverter.createFrom(productDetailsDto), principal.getName());
   return ResponseEntity.status(HttpStatus.CREATED).build();
}
}

还有安全配置:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

private final RESTLogoutSuccessHandler restLogoutSuccessHandler;

@Autowired
public ResourceServerConfig(RESTLogoutSuccessHandler restLogoutSuccessHandler) {
    this.restLogoutSuccessHandler = restLogoutSuccessHandler;
}

@Override
public void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .anonymous()
                .disable()
            .logout()
                .logoutUrl(EndpointPath.SIGN_OUT)
                .logoutSuccessHandler(restLogoutSuccessHandler)
            .and()
            .authorizeRequests()
                .antMatchers(GET, EndpointPath.PRODUCTS).authenticated()
                .antMatchers(POST, EndpointPath.PRODUCT).authenticated()
            .and()
                .exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}

我还用这样的单元测试介绍了 ProductController

@ExtendWith({SpringExtension.class, MockitoExtension.class})
@ContextConfiguration(classes = {MessageSourceConfiguration.class})
@MockitoSettings(strictness = Strictness.LENIENT)
class ProductControllerTest {

private MockMvc mockMvc;

@Autowired
private MessageSource messageSource;

@Mock
private ProductService productService;

@Mock
private ProductConverter productConverter;

@Mock
private UserService userService;

@BeforeEach
void setUp() {
    mockMvc = MockMvcBuilders.standaloneSetup(new ProductController(productService, productConverter, userService))
                                              .setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
                                              .setControllerAdvice(new ControllerAdvice(messageSource)).build();
}

@Test
void shouldReturnSetOfProducts() throws Exception {
    // Given
    String authenticatedUserEmail = "email@g.com";
    when(productService.getUsersProducts(eq(authenticatedUserEmail), ArgumentMatchers.any(Pageable.class))).thenReturn(new PageableProductsDTO(new PageImpl<>(
                                                                        List.of(new ProductDetailsDto("1234", "name", 1), new ProductDetailsDto("12345", "name 2", 2)))));

    // When - Then
    mockMvc.perform(get(EndpointPath.PRODUCTS).principal(() -> authenticatedUserEmail).accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(jsonPath("$.productDtos.content", hasSize(2)))
            .andExpect(jsonPath("$.productDtos.totalPages", is(1)))
            .andExpect(jsonPath("$.productDtos.totalElements", is(2)))
            .andExpect(jsonPath("$.productDtos.content.[*].barcode", containsInAnyOrder("1234", "12345")))
            .andExpect(jsonPath("$.productDtos.content.[*].name", containsInAnyOrder("name", "name 2")))
            .andExpect(jsonPath("$.productDtos.content.[*].price", containsInAnyOrder(1, 2)));
}
}

除了单元测试之外,我还想确保端点 EndpointPath.PRODUCTS 是安全的。有人知道如何为此编写单元测试吗?例如:用户未通过身份验证/授权时的预期状态 401/403。

【问题讨论】:

  • 编写一个没有身份验证的案例,触发 API 并期望状态为 -401/403 并测试您的 401/403 响应

标签: java spring-boot junit spring-security junit5


【解决方案1】:

最简单的测试,完整的 Spring Boot 集成测试,如下所示

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("sample boot integration tests")
public class SpringBootIntegrationTests {

    @Autowired
    private MockMvc mvc;

    @DisplayName("pages are secured")
    void pageIsSecure() throws Exception {
        mvc.perform(
            MockMvcRequestBuilders.get("/products")
        )
            .andExpect(status().is3xxRedirection())
            .andExpect(redirectedUrl("http://localhost/login"))
            .andExpect(unauthenticated())
        ;
    }
}

如果没有身份验证,通常您不会看到 401/403。因为 Spring Security 会将你重定向到授权服务器。

redirectUrl 将与重定向到授权服务器相匹配。

所以可能是这样的

    redirectedUrl(containsString("http://authorization.server/oauth/authorize"))

如果您有一个需要令牌的端点,您可以在测试中执行类似的操作

    .andExpect(status().isUnauthorized())
    .andExpect(unauthenticated())

【讨论】:

    猜你喜欢
    • 2021-01-08
    • 2020-07-14
    • 2019-05-11
    • 2018-11-19
    • 2016-01-02
    • 2019-05-15
    • 2016-08-26
    • 1970-01-01
    • 2022-10-24
    相关资源
    最近更新 更多