【问题标题】:Spring-Boot WebMvcTest: How to test controller method with Authentication object parameter?Spring-Boot WebMvcTest:如何使用 Authentication 对象参数测试控制器方法?
【发布时间】:2020-12-04 17:54:35
【问题描述】:

这是这个问题的延续 Spring WebMvcTest how to mock Authentication?

我正在尝试在 Spring-boot 中测试一个控制器方法,该方法接收一个 Authentication 对象作为参数。控制器是带有@CrossOrigin 注释的RestController。该方法如下所示:

@GetMapping("/authentication")
public String testAuthentication(Authentication authentication) {
    UserDetailsStub userDetailsStub = (UserDetailsStub) authentication.getPrincipal();
    return userDetailsStub.getUsername();
}

如您所见,我从参数中获取了身份验证的主体。

问题是,在我的WebMvcTest 测试用例中,我得到一个NullPointerException,因为在测试用例中,authentication 似乎为空。我的问题是为什么?

我尝试添加一个given 调用,它将在测试用例中的@PostConstruct 注释方法中返回一个自定义UserDetails 对象,但我仍然得到NullPointerException

我的测试用例如下所示:

@Import(SecurityConfiguration.class)
@RunWith(SpringRunner.class)
@WebMvcTest(PDPController.class)
@AutoConfigureMockMvc(addFilters = false)
public class PDPControllerTests {

    @Autowired
    private MockMvc mvc;

    @MockBean(name = "userDetailsService")
    private MyUserDetailsService userDetailsService;
    
    //..

    @PostConstruct
    public void setup() {
        given(userDetailsService.loadUserByUsername(anyString()))
                .willReturn(new UserDetailsStub());
    }
    
    //..

    @Test
    @WithUserDetails(value = "username", userDetailsServiceBeanName = "userDetailsService")
    public void testAuthentication() throws Exception {
        mvc.perform(get("/pdps/authentication").secure(true)
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }
    
}

为什么authentication 在测试用例中为空,即使我在@PostConstruct 方法中提供它?

可以在此处找到包含最少代码的 GitHub 项目来重现错误。 https://github.com/Kars1090/SpringSecurityTest

谢谢!

【问题讨论】:

    标签: java spring spring-boot spring-security


    【解决方案1】:

    一个(不好的)解决方法是从控制器参数中删除authentication,而是通过SecurityContextHolder.getContext().getAuthentication() 获得身份验证。

    这将使测试工作而无需更改任何其他内容。

    【讨论】:

      【解决方案2】:

      克隆您的项目后,我已经在您的控制器方法中接收到有效的Authentication 对象。基本上你的测试中有两个主要问题:

      1. 不必要的额外配置
      2. 过滤器的错误模拟配置:JwtRequestFilter

      总的来说,变化如下:

      public class UserDetailsStub implements UserDetails {
      
        private String username;
        private String password;
        private Collection<? extends GrantedAuthority> authorities;
      
        public UserDetailsStub() {}
              
        public static UserDetailsStub of (User user) {
          UserDetailsStub userDetails = new UserDetailsStub();
          if (null != user) {
              userDetails.username = user.getUsername();
              userDetails.password = user.getPassword();
              userDetails.authorities = user.getAuthorities();
          }
          return userDetails;
        }
      
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
          return authorities;
        }
      
        @Override
        public String getPassword() {
          return password;
        }
      
        @Override
        public String getUsername() {
          return username;
        }
        // Rest of the code is equal to your version
      

      你的控制器方法:

      @GetMapping("/authentication")
      public String testAuthentication(Authentication authentication) {
        UserDetailsStub userDetailsStub = UserDetailsStub.of((User) 
          authentication.getPrincipal());
        return userDetailsStub.getUsername();
      }
      

      还有测试:

      @WebMvcTest(value = PDPController.class)
      public class PDPControllerTests {
      
        @Autowired
        private MockMvc mvc;
      
        /** You have not to mock the filter because in that case Spring
         * won't know how to deal with it, when the list of them
         * should be managed.
         *
         * That is the reason why you had to include
         * @AutoConfigureMockMvc(addFilters = false), but that
         * is preciselly what was avoiding the creation of your
         * Authentication object, because your JwtRequestFilter
         * was not being executed.
         *
         * With the current code, your filter will be executed and
         * the Authentication object created.
         */
         //@MockBean
         //private JwtRequestFilter jwtRequestFilter;
      
         // What you have to mock are the classes the filter uses internally
         @MockBean
         private MyUserDetailsService userDetailsService;
      
         @MockBean
         private JwtService jwtService;
      
         @Test
         @WithMockUser
         public void test() throws Exception {
           mvc.perform(
                  get("/pdps/authentication").secure(true)
                          .contentType(MediaType.APPLICATION_JSON))
                  .andExpect(status().isOk());
         }
       }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-27
        • 1970-01-01
        • 2021-02-08
        • 1970-01-01
        • 1970-01-01
        • 2018-01-02
        相关资源
        最近更新 更多