2017-08-27 30 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對象,請查找另一種方法來填充測試的安全上下文。