2017-08-27 91 views
3

我有一个POST方法,用于发送Item。每个项目包含作者的用户的ID。而对于GET作者ID我用:(User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 但在测试中,这不起作用由于导致:如何.getPrincipal()在POST方法的spring-boot测试?

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to ru.pravvich.domain.User

POST方法:

@PostMapping("/get_all_items/add_item_page/add_item") 
public String addItem(@RequestParam(value = "description") final String description) { 

    final User user = (User) SecurityContextHolder 
      .getContext() 
      .getAuthentication() 
      .getPrincipal(); 

    final Item item = new Item(); 
    item.setDescription(description); 
    item.setAuthorId(user.getId()); 
    service.add(item); 
    return "redirect:/get_all_items"; 
} 

和测试:

@Test 
@WithMockUser(username = "user", roles = "USER") 
public void whenPostThenAdd() throws Exception { 

    Item item = new Item(); 
    item.setDescription("test"); 

    mvc.perform(
      post("/get_all_items/add_item_page/add_item") 
        .param("description", "test") 
    ).andExpect(
      status().is3xxRedirection() 
    ); 

    verify(itemService, times(1)).add(item); 
} 

为什么ClassCastException不会抛出然后我从浏览器窗体发送数据?如何解决这个问题?

谢谢。

UPDATE:

@Service 
public class UserService implements UserDetailsService { 

    @Autowired 
    private UserRepository userRepo; 

     @Override 
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
      User user = userRepo.findByUsername(username); 
      if (user == null) user = new User(); 
      return user; 
     } 
} 
+0

的[为什么春天测试失败,不能正常工作@MockBean]可能的复制(https://stackoverflow.com/questions/45905591/why-spring-test-is-fail-does-not -work-mockbean) –

+0

你有可能使用'@ WithUserDetails'? https://docs.spring.io/spring-security/site/docs/current/reference/html/test-method.html#test-method-withuserdetails –

回答

3

@WithMockUser可以帮助您注入org.springframework.security.core.userdetails.User对象并执行测试,但在这种情况下,您需要自定义用户对象ru.pravvich.domain.User

它为什么适用于浏览器窗体?这是因为主要来自你的认证过程,例如,如果您为了回报您的自定义ru.pravvich.domain.User有自己UserDetailsService,这个过程发生在正常的情况下,当你通过网络形式进行authetnication,但在测试的情况下至少你可以配置测试用例并通知你的自定义实现。让我们来看一个例子:从用户扩展

自定义的用户类(这里更新)

//MySpring user is just a class that extends from 
    //org.springframework.security.core.userdetails.User 
    //org...security....User implements UserDetailsService that is the 
    //the reason why I can return this class from loadUserByUsername method, you will see it later 
    //it helps me to wrap my own User definition 
    public class MySpringUser extends User { 

    //this is my own user definition, where all my custom fields are defined 
    private MyUser user; 

    //the constructor expect for myUser and then I call the super 
    //constructor to fill the basic fields that are mandatory for spring 
    //security in order to perform the authentication process 
    public MySpringUser(MyUser myUser, Collection<? extends GrantedAuthority> authorities) { 
     super(myUser.getUsername(), myUser.getPassword(), myUser.isEnabled() 
       , true, true, true, authorities); 

     this.setUser(myUser); 
    } 

    public MyUser getUser() { 
     return user; 
    } 

    public void setUser(MyUser user) { 
     this.user = user; 
    } 
    //this is an example of how to get custom fields a custom id for example 
    public Long getId(){ 
     return this.getUser().getId(); 
    } 
} 

一个自定义的UserDetails服务(这里更新)

@Service 
public class MyUserService implements UserDetailsService { 

    @Autowired 
    MyUserRepository myUserRepository; 

    //Principal 
    @Override 
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { 

     //Here I get the User that I defined for my own domain structure 
     MyUser myUser = myUserRepository.findFirstByUsername(s); 

     //Here I return a new User object, in this case MySpringUser is 
     //just a class that extends from User class, its constructor 
     //receive myUser object as parameter in order to get my own custom fields later 
     return new MySpringUser(myUser,AuthorityUtils.createAuthorityList("ADMIN")); 

    } 
} 

最后测试使用@WithUserDetails的情况通知测试应该调用loadUserByUsername方法,该方法由定制UserDetailsService实施,在在这种情况下,您可以自由地将主体转换为您的自定义用户对象。

@Test 
    @WithUserDetails("myUserNameOnDatabase") 
    public void contextLoads() { 
     try { 
      mockMvc.perform(get("/stores")).andExpect(status().isOk()).andDo(print()); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 

    } 

这是投是如何在控制器执行,就像你

@RequestMapping("/stores") 
    public List<Store> getStores(Principal principal){ 

     MySpringUser activeUser = (MySpringUser) ((Authentication) principal).getPrincipal(); 
     System.out.println(activeUser.getUsername()); 

     return storeRepository.findByMyUserId(); 

    } 

更新这里)我还上传了一个完整的例子我混帐回购协议,你可以下载它,并尝试它为了给你一个更清晰的视图,当然这只是一个选项,还有其他的一些选择,比如扩展spring User类,你可以创建自己的类来实现接口UserDetails,替代方案将取决于你的需求。

希望这个替代方案能帮助你。

My git Repo example of how use @WithUserDetails

+0

谢谢。我有一个理解'loadUserByUsername(String s)'的问题'可能你可能会重写没有lambda?而且我还有一个问题:是否有通向没有org.springframework.security.core.userdetails.User的解决方案?我的验证系统崩溃,然后我使用'安全...用户'我更新问题和我的UserServiece版本。可能会提供更多有关情况的信息。再次感谢你。 – Pavel

+1

肯定只是给我一点时间来更新答案。 –

+0

你可以看到更新,希望它可以帮助你。 –

0

Spring Security的@WithMockUser填充有org.springframework.security.core.userdetails.User对象,这就是为什么你遇到这个异常的安全上下文。该documentation是这个相当清楚的:

的认证主要是Spring Security的用户对象

一个可能的解决您的ClassCastException是简单地重复使用Spring的User类,并使用getUsername()返回作者ID,或者如果您必须为客户提供User对象,请查找另一种方法来填充测试的安全上下文。