4

I have a POST method which for sends Item. Each Item contains the id of User which author. And for get author id I use :(User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); But in test, this does not work due to result :

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 method:

@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";
}

And test:

@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);
}

Why ClassCastException does not throw then I send data from the browser form? How fix this issue?

Thank You.

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;
        }
}
Pavel
  • 2,005
  • 5
  • 36
  • 68
  • Possible duplicate of [Why spring test is fail, does not work @MockBean](https://stackoverflow.com/questions/45905591/why-spring-test-is-fail-does-not-work-mockbean) – Abhijit Sarkar Aug 27 '17 at 21:01
  • Is it possible for you to use `@WithUserDetails`? https://docs.spring.io/spring-security/site/docs/current/reference/html/test-method.html#test-method-withuserdetails – Daniel C. Aug 27 '17 at 21:31

2 Answers2

4

@WithMockUser helps you to inject a org.springframework.security.core.userdetails.User object and perform the test, but in this case you need a custom user object ru.pravvich.domain.User.

Why it works with browser form? it is because the principal comes from your authentication process, for example if you have your own UserDetailsService in order to return your custom ru.pravvich.domain.User, this process happens in a normal case scenario when you perform authetnication by the web form, but in a test case scenario it doesn't happens at least you configure the test case and inform about your custom implementation. Let's see an example:

A Custom user class that extends from User (update here)

   //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();
    }
}

A Custom UserDetails Service (update here)

@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"));

    }
}

Finally the test case using @WithUserDetails to inform that the test should call the loadUserByUsername method that is implemented by the custom UserDetailsService, in that case you are free to cast the principal to your custom user object.

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

    }

This is how cast is performed in the controller, just like yours

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

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

        return storeRepository.findByMyUserId();

    }

(Update here) I also uploaded a full example to my git repo, you can download it and try it in order to give you a more clear view, of course this is just one option, there are others, for example instead of extends spring User class you can create your own class that implements the UserDetails interface, the alternative will depends on your needs.

Hope this alternative help you.

My git Repo example of how use @WithUserDetails

Daniel C.
  • 5,418
  • 3
  • 23
  • 26
  • Thank You. I have an issue with understanding `loadUserByUsername(String s)` may be You might rewrite without lambda? And I have one more question: is there the path to a solution without `org.springframework.security.core.userdetails.User`? My auth system crash then I use `security...User` I update the question to and my UserServiece version. May be it give more information about the situation. Thank You one more time. – Pavel Aug 28 '17 at 19:11
  • Yes! You are great! It's work. Git repo answer on all questions. Thank You! – Pavel Aug 29 '17 at 01:49
0

Spring Security's @WithMockUser populates the security context with a org.springframework.security.core.userdetails.User object, which is why you're encountering this exception. The documentation is pretty clear about this:

The principal on the Authentication is Spring Security’s User object

One possible solution to your ClassCastException is to simply reuse Spring's User class and use getUsername() to return the author ID, or if you must customer the User object, find another way to populate the security context for your tests.

ck1
  • 5,243
  • 1
  • 21
  • 25