java - 如何在 POST 方法的 spring-boot 测试中使用 .getPrincipal()?

标签 java spring spring-mvc spring-security

我有一个用于发送 Item 的 POST 方法。每个项目都包含作者的用户 ID。对于获取作者 ID,我使用:(User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 但在测试中,由于结果,这不起作用:

org.springframework.web.util.NestedServletException:请求处理失败;嵌套异常是 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 在我从浏览器表单发送数据时没有抛出?如何解决这个问题?

谢谢。

更新:

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

最佳答案

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

为什么它适用于浏览器表单?这是因为委托(delegate)人来自您的身份验证过程,例如,如果您有自己的 UserDetailsS​​ervice 以返回您的自定义 ru.pravvich.domain.User,则会发生此过程在正常情况下,当您通过 Web 表单执行身份验证时,但在测试用例场景中,它不会发生,至少您配置测试用例并通知您的自定义实现。让我们看一个例子:

从用户扩展的自定义用户类(在此处更新)

   //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 通知测试应该调用由自定义UserDetailsS​​ervice 实现的loadUserByUsername 方法,在在这种情况下,您可以自由地将委托(delegate)人转换为您的自定义用户对象。

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

    }

这就是在 Controller 中执行转换的方式,就像您的一样

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

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

        return storeRepository.findByMyUserId();

    }

(在这里更新) 我也上传了一个完整的示例到我的git repo,你可以下载并尝试一下,以便让你更清楚地了解,当然这只是一种选择,还有其他的,例如,您可以创建自己的类来实现 UserDetails 接口(interface),而不是扩展 spring User 类,替代方案将取决于您的需要。

希望这个替代方案对您有所帮助。

My git Repo example of how use @WithUserDetails

关于java - 如何在 POST 方法的 spring-boot 测试中使用 .getPrincipal()?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45909210/

相关文章:

java - 我可以在不同的 Spring Controller 中使用不同参数的相同映射值吗?

java - p6spy 使用 spring boot 两次输出 sql 消息

java - 通过spring boot读取JSON映射结构

Spring YAML 配置文件配置

spring-mvc - Thymeleaf: <label> 将动态文本与静态文本 Spring MVC 连接起来

java - 在 Spring 中为 Controller 操作生成 URL

java - Spring Boot 中的阴影 jar

java - 如何将 HTML 字符串读入 JEditorPane/JTextPane?

java - 在特定位置设置工具提示文本

java - 使用 IDE 运行 Spring-boot 的 main