【问题标题】:Spring test: How to test method secured with @PreAuthorize("@SecurityPermission.hasPermission('somepermission')")Spring 测试:如何测试使用 @PreAuthorize("@SecurityPermission.hasPermission('somepermission')") 保护的方法
【发布时间】:2018-09-05 13:44:30
【问题描述】:

在我们的 Spring Boot 项目中,我们使用 @PreAuthorize 注释来保护每个方法。它检查用户是否对请求的资源有权限。

这是我们的控制器之一:

@PreAuthorize("@SecurityPermission.hasPermission('role')")
@RequestMapping(value = "/role")
public class RoleController {
    @Autowired
    private RoleService roleService;

    @PreAuthorize("@SecurityPermission.hasPermission('role.list')")
    @RequestMapping(value = "/allroles", method = RequestMethod.GET, consumes = "application/json", produces = "application/json")
    public JsonData<Role> getListOfRoles() {
        JsonData<Role> roleJsonData = new JsonData<>();
        roleJsonData.setData(roleService.list());
        return roleJsonData;
    }

}    

问题是:如何正确测试上述方法的权限?

我尝试了以下两种选择:

@RunWith(SpringRunner.class)
@WebMvcTest(RoleController.class)
public class RoleControllerTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private RoleService roleService;


    @Test
    public void optionOne() throws Exception {
        ArrayList<Role> roles = new ArrayList<>();
        roles.add(new Role().setId(1L).setName("administrator"));
        roles.add(new Role().setId(2L).setName("user"));
        given(roleService.list()).willReturn(roles);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Type", "application/json");

        this.mvc.perform(get("/role/allroles").with(user("testadmin"))
                .headers(headers))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data[0].name", is( roles.get(0).getName())))
                .andExpect(jsonPath("$.data[1].name", is( roles.get(1).getName())));
    }


    @Test
    @WithMockUser(authorities = {"role.list"})
    public void optionTwo() throws Exception {
        ArrayList<Role> roles = new ArrayList<>();
        roles.add(new Role().setId(1L).setName("administrator"));
        roles.add(new Role().setId(2L).setName("user"));
        given(roleService.list()).willReturn(roles);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Type", "application/json");

        this.mvc.perform(get("/role/allroles")
                .headers(headers))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data[0].name", is( roles.get(0).getName())))
                .andExpect(jsonPath("$.data[1].name", is( roles.get(1).getName())));
    }

}

即使模拟用户没有所需的权限(“role.list”),optionOne 也会通过,而 optionTwo 失败,状态为 403。

java.lang.AssertionError: Status 
Expected :200
Actual   :403

更新:我正在添加 WebSecurityConfig 类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";
    public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/auth/login";
    public static final String SEARCH_BASED_ENTRY_POINT = "/search/**";
    public static final String TOKEN_REFRESH_ENTRY_POINT = "/auth/token";
    public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/**";

    @Autowired
    private RestAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private AuthenticationSuccessHandler successHandler;
    @Autowired
    private AuthenticationFailureHandler failureHandler;
    @Autowired
    private AjaxAuthenticationProvider ajaxAuthenticationProvider;
    @Autowired
    private JwtAuthenticationProvider jwtAuthenticationProvider;
    @Autowired
    private TokenExtractor tokenExtractor;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    protected AjaxLoginProcessingFilter buildAjaxLoginProcessingFilter() throws Exception {
        AjaxLoginProcessingFilter filter = new AjaxLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
        filter.setAuthenticationManager(this.authenticationManager);
        return filter;
    }

    @Bean
    protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
        List<String> pathsToSkip = Arrays.asList(TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, SEARCH_BASED_ENTRY_POINT);
        SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
        JwtTokenAuthenticationProcessingFilter filter
                = new JwtTokenAuthenticationProcessingFilter(failureHandler, tokenExtractor, matcher);
        filter.setAuthenticationManager(this.authenticationManager);
        return filter;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(ajaxAuthenticationProvider);
        auth.authenticationProvider(jwtAuthenticationProvider);

    }

    @Bean
    protected Md5PasswordEncoder passwordEncoder() {
        return new Md5PasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("utf-8");
        filter.setForceEncoding(true);
        http.addFilterBefore(filter, CsrfFilter.class);

        http.addFilterBefore(new WebSecurityCorsFilter(), ChannelProcessingFilter.class);
        http
                .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint(this.authenticationEntryPoint)
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll()
                .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll()
                .antMatchers(SEARCH_BASED_ENTRY_POINT).permitAll()
                .and()
                .authorizeRequests()
                .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()
                .and()
                .addFilterBefore(buildAjaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
    }

}

【问题讨论】:

  • 如果您对方法 SecurityPermission.hasPermission('role') 进行单元测试就足够了,因为注释已经在相应的库中进行了测试并且应该可以工作。你真的想要一个集成测试吗?
  • 如果有人临时 cets 注释并忘记了它,测试应该失败,让我们知道代码已损坏
  • 很公平,但如果发生这种情况,您应该修复您的 QA 流程。如果下面的答案不能解决问题,您能否向我们展示 bean SecurityPermission 的代码?因为这是检查发生的原因。
  • 从JWT payload中提取的角色,每个角色都有一组权限
  • 方法安全性基于权限

标签: java spring junit spring-security integration-testing


【解决方案1】:

几个月前我遇到了同样的问题,但方式略有不同。我认为您的上下文设置不正确,因为您必须明确地将 SpringSecurity 应用于它以进行测试:

private MockMvc mockMvc;

@Autowired
private WebApplicationContext context;

@Before
public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .apply(springSecurity())
            .build();
}

您也可以参考:How to unit test a secured controller which uses thymeleaf (without getting TemplateProcessingException)? 这与您的问题略有不同,但由于 SecurityHandling 是一种个人设置,因此如果不更好地了解您的项目,很难提供帮助。

如果您尝试测试非授权用户的行为,您还可以执行以下操作:

@Test
public void getLoginSuccessWithAnonymousUserReturnsAccessDeniedException() throws Exception {

    MvcResult mvcResult = mockMvc.perform(get("/your-url").with(anonymous()))
            .andExpect(status().is3xxRedirection()) //change to your code
            .andReturn();

    Class result = mvcResult.getResolvedException().getClass();
    MatcherAssert.assertThat((result.equals(org.springframework.security.access.AccessDeniedException.class)), is(true));
}

【讨论】:

    猜你喜欢
    • 2012-05-22
    • 2020-05-09
    • 1970-01-01
    • 2019-06-22
    • 2012-11-05
    • 1970-01-01
    • 1970-01-01
    • 2011-08-21
    • 2017-09-21
    相关资源
    最近更新 更多