【问题标题】:Spring testing controllers with BasicAuth on and custom WebSecurityConfigurerAdapter带有 BasicAuth 和自定义 WebSecurityConfigurerAdapter 的 Spring 测试控制器
【发布时间】:2017-05-26 05:41:24
【问题描述】:

我正在制作一个休息 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 安全性不允许测试调用需要身份验证的 url(“admin/movies”)。正如在配置文件中指定的那样,此 url 需要 ADMIN 角色。有趣的是,当我在控制器和测试中删除 url 的“admin”部分时,测试有效!但是,根据配置,它仍然需要使用 USER 角色进行身份验证(只有“/user/create”不需要它,正如您在配置中看到的那样)。

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

【问题讨论】:

    标签: spring security testing mocking role


    【解决方案1】:

    为什么要使用 Mockito 进行 MVC 测试,这似乎是一件很奇怪的事情?通常你会将真实的 Controller/Service/Repository 直接@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))));
    

    【讨论】:

    • 嘿,我使用 Mockito 只是为了熟悉它。无论如何,感谢您的回答,但似乎我已经找到了解决我愚蠢问题的方法:) 请参阅下面的帖子。
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2017-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-12
      相关资源
      最近更新 更多