简体   繁体   中英

Spring test: How to test method secured with @PreAuthorize(“@SecurityPermission.hasPermission('somepermission')”)

In our Spring Boot project we secured each method with @PreAuthorize annotation. It checks if user has permission for the requested resource.

Here is one of our controllers:

@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;
    }

}    

The question is: How to test properly permissions for above mentioned method?

I have tried the following two options:

@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())));
    }

}

optionOne passes even though mock user doesn't have required permission("role.list") while optionTwo failes with the status 403.

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

UPDATE : I am adding WebSecurityConfig class

@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);
    }

}

I had the same problem a few months ago but in a slightly different way. I think your context is not setup correctly, since you have to apply SpringSecurity explicitiy to it for testing purposes:

private MockMvc mockMvc;

@Autowired
private WebApplicationContext context;

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

You can also refer to: How to unit test a secured controller which uses thymeleaf (without getting TemplateProcessingException)? It is slightly different to your problem, but since SecurityHandling is kind of an individual setup, it ist hard to help without knowing your project better.

If you are trying to test the behaviour for a non-authorized User, you can also do something like this:

@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));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM