使用BasicAuth和自定义WebSecurityConfigurerAdapter的Spring测试控制器

时间:2017-01-11 12:16:26

标签: spring security testing mocking role

我正在休息api。最近,我在项目中添加了Basic Auth,并指定了如下配置:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    public final static String ROLE_ADMIN = "ADMIN";
    public final static String ROLE_USER = "USER";

    /**
     * Determines the resource access for different account types
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                    .antMatchers("/user/create").permitAll()
                    .antMatchers("/admin/**").hasRole(ROLE_ADMIN)
                    .anyRequest().authenticated()
                .and()
                    .csrf().disable()
                    .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(inMemoryUserDetailsManager());
    }

    /**
     * Initially fills Spring Security with default accounts
     */
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager() {

        final Properties users = new Properties();
        users.put("user","pass,ROLE_USER,enabled"); //login = user, password = pass
        users.put("admin","pass,ROLE_ADMIN,enabled"); //login = admin, password = pass
        return new InMemoryUserDetailsManager(users);
    }
}

我还为他们制作了一些控制器和一些测试:

控制器:

@RestController
public class MovieController {

    @Autowired @Qualifier("MovieService")//not relevant
    private MovieService ms;

    @Autowired @Qualifier("CastService")//not relevant
    private CastService cs;

    @RequestMapping(value = "admin/movies", method = GET)
    public List<Movie> selectAllMovies(){

        return ms.selectAll();
    }

//the rest of the code..
}

测试:

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MovieControllerTest {

    @Mock
    private MovieService movieService;

    @Mock
    private CastService castService;

    @Mock
    private ActorService actorService;

    @Mock
    private UserService userService;

    @InjectMocks
    private MovieController movieController;

    private MockMvc mvc;

    @Before
    public void setUp(){

        MockitoAnnotations.initMocks(this);

        mvc = MockMvcBuilders.standaloneSetup(movieController).build();
    }

    @Test
    @WithMockUser(roles = "ADMIN", username = "admin", password = "pass")
    @WithUserDetails("admin")
    public void testGetAllMovies() throws Exception {

        List<Movie> movieList = Arrays.asList(
                new Movie("title1", "desc1", MovieType.newest, 10f, true),
                new Movie("title2", "desc2", MovieType.newest, 10f, true),
                new Movie("title3", "desc3", MovieType.newest, 10f, true));

        when(movieService.selectAll()).thenReturn(movieList);

        String uri = "admin/movies";

        MvcResult result = mvc.perform(MockMvcRequestBuilders.get(uri)
                .accept(MediaType.APPLICATION_JSON)).andReturn();

        String content = result.getResponse().getContentAsString();
        int status = result.getResponse().getStatus();

        verify(movieService, times(1)).selectAll();

        Assert.assertEquals("failure - expected HTTP status 200", 200, status);
        Assert.assertTrue("failure - expected HTTP response body to have a value", content.trim().length() > 0);
    }

//the rest of the code..
}

但是当我运行测试时,我从Mockito那里得到了错误:

Wanted but not invoked:
movieService.selectAll();
-> at com.myproject.Controller.MovieControllerTest.testGetAllMovies(MovieControllerTest.java:87)
Actually, there were zero interactions with this mock.

看来,Spring安全性不允许测试调用需要身份验证的网址(“admin / movies”)。正如在配置文件中指定的那样,此URL需要ADMIN角色。有趣的是,当我在控制器和测试中删除url的“admin”部分时,测试工作正常!但是,根据配置,它仍然需要使用USER角色进行身份验证(只有“/ user / create”不需要它,如配置中所示)。

我已尝试使用@WithMockUser(username="admin", password="pass", roles="ADMIN")但它没有帮助,错误保持不变。

2 个答案:

答案 0 :(得分:0)

为什么你使用Mockito进行MVC测试,这似乎是一件奇怪的事情?通常情况下,你会将真正的控制器/服务/存储库@Autowire直接进入测试。

以下是我们的MVC测试基类的外观。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigWebContextLoader.class, classes = IntegrationTestConfiguration.class)
@WebAppConfiguration
public abstract class IntegrationTest {
    // from some Spring context
    @Autowired
    protected HttpClientConnectionManager connectionManager;

    @Autowired
    protected ClientDetailsService clientDetailsService;

    @Autowired
    protected WebApplicationContext context;

    @SuppressWarnings("SpringJavaAutowiringInspection") // this is created by Spring Security
    @Autowired
    protected FilterChainProxy filterChain;

    private MockMvc mvc;

    protected TestSession.LoginDetails integrationTestUserDetails;

    @Before
    public void configureMock() throws InvalidDecryptionKeyException {
        // setup spring MVC
        MockHttpServletRequestBuilder requestBuilder = get("/");
        requestBuilder.secure(true);
        requestBuilder.header("origin", "http://localhost");
        mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(SecurityMockMvcConfigurers.springSecurity())
                .addFilter(filterChain)
                .defaultRequest(requestBuilder)
                .build();
    }

}

此外,当您使用MovkMvc时,您应该记住,您正在Http上方的级别进行通信,此时servlet容器已将字节流处理为HttpServletRequest。这意味着如果你发布JSON,你通常会创建一个Java对象,将其序列化为像这样的字节。

mvc.perform(post("/internal/signOut")
    .session(mySession)
    .content(objectMapper.writeValueAsString(signOutRequest))));

答案 1 :(得分:0)

好的,所以看起来Spring Security与我的问题无关。 mockito无法检测到与模拟的交互(控制器从未与之交互过)的真正原因是我投入的网址MockMvcRequestBuilder不正确。只是偶然地发现url必须在url字符串的beginnig处包含斜杠,所以我将"admin/movies"更改为"/admin/movies"并且它可以正常工作,即使事实如此,正如您所看到的那样我上面的代码,在控制器中,控制器的映射没有这些斜杠(“admin / movies”)。 疯了,但确实如此:)