英文:
Spring-Boot WebMvcTest: How to test controller method with Authentication object parameter?
问题
这是问题的续篇。
链接:https://stackoverflow.com/questions/62799664/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();
}
正如你所看到的,我从参数中的 Authentication
对象中获取了主体。
问题是,在我的 WebMvcTest
测试用例中,我得到了一个 NullPointerException
,因为在测试用例中,authentication
似乎为 null。我的问题是为什么会这样?
我尝试过在测试用例中添加一个 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
谢谢!
英文:
This is a continuation of this question
https://stackoverflow.com/questions/62799664/spring-webmvctest-how-to-mock-authentication
I'm trying to test a controller method in Spring-boot that receives an Authentication
object as parameter. The controller is a RestController
with @CrossOrigin
annotation. The method looks like this:
@GetMapping("/authentication")
public String testAuthentication(Authentication authentication) {
UserDetailsStub userDetailsStub = (UserDetailsStub) authentication.getPrincipal();
return userDetailsStub.getUsername();
}
As you can see i get the principal from the Authentication out of the parameters.
The problem is, in my WebMvcTest
test case i get a NullPointerException
because in the test case, authentication
seems to be null. My question is why?
I have tried adding a given
call which will return a custom UserDetails
object in a @PostConstruct
annotated metho in the test case, but still i get the NullPointerException
.
My test case looks like this:
@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());
}
}
Why is authentication
null in the test case, even when i supply it in the @PostConstruct
method?
A GitHub project with minimal code that reproduces the error can be found here.
https://github.com/Kars1090/SpringSecurityTest
Thanks!
答案1
得分: 1
在克隆您的项目后,我成功地在您的控制器方法中获得了一个有效的 Authentication
对象。基本上,您的测试中存在两个主要问题:
- 不必要的额外配置
- 对您的过滤器
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;
}
// 代码的其余部分与您的版本相同
}
您的控制器方法:
@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;
/** 您不必模拟过滤器,因为在这种情况下,Spring 将不知道如何处理它,
* 当应该管理它们的列表时。
*
* 这就是您不得不包含
* @AutoConfigureMockMvc(addFilters = false) 的原因,但这正是导致
* 您的 Authentication 对象未被创建的原因,因为您的 JwtRequestFilter
* 没有被执行。
*
* 使用当前的代码,您的过滤器将被执行,Authentication 对象将被创建。
*/
//@MockBean
//private JwtRequestFilter jwtRequestFilter;
// 您需要模拟过滤器在内部使用的类
@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());
}
}
英文:
After clone your project I have achieved to receive a valid Authentication
object in your controller method. Basically you have 2 main problems in your test:
- Unnecessary extra configuration
- Bad mock configuration of your filter:
JwtRequestFilter
In summary, the changes were the following ones:
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
Your controller method:
@GetMapping("/authentication")
public String testAuthentication(Authentication authentication) {
UserDetailsStub userDetailsStub = UserDetailsStub.of((User)
authentication.getPrincipal());
return userDetailsStub.getUsername();
}
And the test:
@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());
}
}
答案2
得分: 0
一个(不好的)解决方法是从控制器参数中移除authentication
,而是通过SecurityContextHolder.getContext().getAuthentication()
来获取认证信息。
这将使测试正常工作,而无需改变其他任何内容。
英文:
A (bad) workaround would be to remove authentication
from the controller parameters and instead get the authentication via SecurityContextHolder.getContext().getAuthentication()
.
This will make the test work without changing anything else.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论