在 Spring 安全测试中模拟自定义用户
Mock Custom User in Spring Security Test
我们使用的是 Spring 4.3.9.RELEASE 和 Spring Security 4.2.3.RELEASE,所以这些是我们看到的一些最新版本。我们有一个 RESTful (spring-mvc) 后端,我们使用 Spring Web Security 来 roles-based 访问 API。
我们有一个看起来像这样的控制器:
@RequestMapping(value = "/create", method = RequestMethod.POST, produces = "application/json", headers = "content-type=application/json")
public @ResponseBody MyObjectEntity createMyObject(@RequestBody MyObjectEntity myObj) throws MyObjectException
{
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
String email = user.getEmail();
MyObjectEntity myObj = MyObjectService.createMyObject(myObj, email);
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
return myObj;
}
我们知道用户已使用用户名和密码从 web-site 登录。我们知道 UI 有一个令牌,他们在 header 中传递它。我们的安全性使用 SiteMinder 示例,这意味着我们有一个转到 third-party 的 UserDetailsService,传递令牌,我们现在有了用户名、密码和用户的角色。这通常运作良好。
我们确实创建了一个 CustomUserDetailsService,如下所示:
public class CustomUserDetailsService implements UserDetailsService
{
@Override
public UserDetails loadUserByUsername(String accessToken) throws
UsernameNotFoundException,
PreAuthenticatedCredentialsNotFoundException
{
// goto to third-party service to verify token
// get the Custom User and the user roles
// also get some extra data, so a custom user
}
}
因此,一旦我们确定令牌有效,并且我们从该 third-party 获得了额外的用户信息,并且我们拥有了为此 API 授权的有效角色 ... 然后我们可以执行控制器本身。我们看到这段代码是传统的,用于让现有用户脱离 Spring 安全上下文。
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
实际上,根据我们的阅读,当您拥有自定义用户和 CustomUserDetails 时,这是执行此操作的方法。使用此代码,我们希望获取该用户的电子邮件。当我们使用 Advanced REST Client 实际测试 API 时,这一切都有效。我们的 QA 必须针对 web-site 进行身份验证,他们将令牌传递回 UI,他们将获得这些访问令牌,并将它们放入高级 REST 客户端的 headers(或邮递员),这一切都有效。
我们甚至有代码在 API 结束时使安全上下文无效。
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
反对,真正的API,有了真正的进步,这个效果很好。
现在,当谈到测试时,一些测试对我们的安全控制器有效,而另一些则无效。所以,这里我们有一个控制器来测试:
@RequestMapping(value = "/{productId}", method = RequestMethod.GET, headers = "Accept=application/json")
public @ResponseBody ProductEntity getProductById(@PathVariable("productId") long productId)
{
logger.debug("ProductController: getProductById: productId=" + productId);
CustomUser user = authenticate();
ProductEntity productEntity = service.getById(productId);
logger.debug("ProductController: getProductById: productEntity=" + productEntity);
invalidateUser();
return productEntity;
}
这是测试:
@Test
public void testMockGetProductByProductId() throws Exception
{
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user("testuser").roles("REGULAR_USER"));
this.mockMvc.perform(requestBuilder).andDo(print()).andExpect(status().isOk());
}
这是有效的,因为即使我们到达控制器,我们也不需要 CustomerUser 集,所以它有效。如果角色是正确的角色 ("REGULAR_USER"),那么它会起作用,如果角色不正确,我们会得到一个 403 错误,这正是我们所期待的。
但是如果您查看我首先在顶部发布的控制器,我们需要设置 CustomUser,如果未设置,那么当我们尝试获取该电子邮件时,我们会失败。因此,我们一直在研究在身份验证中设置模拟用户的多种方法,因此当我们到达控制器时,我们可以在安全上下文中获得该 CustomUser。
我以前确实这样做过,但那是我们使用标准 spring 安全用户而不是自定义用户的时候。
我们当然可以在安全上下文中建立一个CustomUser,但是当它到达控制器时,这个代码是运行 ....
// THIS WORKS
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
// This IF fails because;
// userDetails is of instance User (Spring Security User)
// and not CustomUser.
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
让我为我们的 CustomUser 添加代码:
public class CustomUser implements UserDetails
{
private static final long serialVersionUID = -6650061185298405641L;
private String userName;
private ArrayList<GrantedAuthority> authorities;
private String firstName;
private String middleName;
private String lastName;
private String email;
private String phone;
private String externalUserId;
// getters/setters
// toString
}
我希望我在这里提供了足够的信息,以便有人可以回答我的问题。我花了一两天时间在互联网上搜索可以回答这个问题的人,但无济于事。一些答案来自 Spring 3 和更早的 Spring 安全性 3.x。如果需要更多信息,请告诉我。谢谢!
我想知道...我是否需要一个实现 UserDetails 的 CustomUserDetails?
再次感谢!
这可能比您想象的要容易得多。
CustomUser userDetails = new CustomUser();
/* TODO: set username, authorities etc */
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user(userDetails));
只要您的 CustomUser
实现了 UserDetails
接口,就允许这样做。
我们使用的是 Spring 4.3.9.RELEASE 和 Spring Security 4.2.3.RELEASE,所以这些是我们看到的一些最新版本。我们有一个 RESTful (spring-mvc) 后端,我们使用 Spring Web Security 来 roles-based 访问 API。
我们有一个看起来像这样的控制器:
@RequestMapping(value = "/create", method = RequestMethod.POST, produces = "application/json", headers = "content-type=application/json")
public @ResponseBody MyObjectEntity createMyObject(@RequestBody MyObjectEntity myObj) throws MyObjectException
{
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
String email = user.getEmail();
MyObjectEntity myObj = MyObjectService.createMyObject(myObj, email);
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
return myObj;
}
我们知道用户已使用用户名和密码从 web-site 登录。我们知道 UI 有一个令牌,他们在 header 中传递它。我们的安全性使用 SiteMinder 示例,这意味着我们有一个转到 third-party 的 UserDetailsService,传递令牌,我们现在有了用户名、密码和用户的角色。这通常运作良好。 我们确实创建了一个 CustomUserDetailsService,如下所示:
public class CustomUserDetailsService implements UserDetailsService
{
@Override
public UserDetails loadUserByUsername(String accessToken) throws
UsernameNotFoundException,
PreAuthenticatedCredentialsNotFoundException
{
// goto to third-party service to verify token
// get the Custom User and the user roles
// also get some extra data, so a custom user
}
}
因此,一旦我们确定令牌有效,并且我们从该 third-party 获得了额外的用户信息,并且我们拥有了为此 API 授权的有效角色 ... 然后我们可以执行控制器本身。我们看到这段代码是传统的,用于让现有用户脱离 Spring 安全上下文。
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
实际上,根据我们的阅读,当您拥有自定义用户和 CustomUserDetails 时,这是执行此操作的方法。使用此代码,我们希望获取该用户的电子邮件。当我们使用 Advanced REST Client 实际测试 API 时,这一切都有效。我们的 QA 必须针对 web-site 进行身份验证,他们将令牌传递回 UI,他们将获得这些访问令牌,并将它们放入高级 REST 客户端的 headers(或邮递员),这一切都有效。
我们甚至有代码在 API 结束时使安全上下文无效。
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
反对,真正的API,有了真正的进步,这个效果很好。 现在,当谈到测试时,一些测试对我们的安全控制器有效,而另一些则无效。所以,这里我们有一个控制器来测试:
@RequestMapping(value = "/{productId}", method = RequestMethod.GET, headers = "Accept=application/json")
public @ResponseBody ProductEntity getProductById(@PathVariable("productId") long productId)
{
logger.debug("ProductController: getProductById: productId=" + productId);
CustomUser user = authenticate();
ProductEntity productEntity = service.getById(productId);
logger.debug("ProductController: getProductById: productEntity=" + productEntity);
invalidateUser();
return productEntity;
}
这是测试:
@Test
public void testMockGetProductByProductId() throws Exception
{
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user("testuser").roles("REGULAR_USER"));
this.mockMvc.perform(requestBuilder).andDo(print()).andExpect(status().isOk());
}
这是有效的,因为即使我们到达控制器,我们也不需要 CustomerUser 集,所以它有效。如果角色是正确的角色 ("REGULAR_USER"),那么它会起作用,如果角色不正确,我们会得到一个 403 错误,这正是我们所期待的。
但是如果您查看我首先在顶部发布的控制器,我们需要设置 CustomUser,如果未设置,那么当我们尝试获取该电子邮件时,我们会失败。因此,我们一直在研究在身份验证中设置模拟用户的多种方法,因此当我们到达控制器时,我们可以在安全上下文中获得该 CustomUser。
我以前确实这样做过,但那是我们使用标准 spring 安全用户而不是自定义用户的时候。
我们当然可以在安全上下文中建立一个CustomUser,但是当它到达控制器时,这个代码是运行 ....
// THIS WORKS
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
// This IF fails because;
// userDetails is of instance User (Spring Security User)
// and not CustomUser.
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
让我为我们的 CustomUser 添加代码:
public class CustomUser implements UserDetails
{
private static final long serialVersionUID = -6650061185298405641L;
private String userName;
private ArrayList<GrantedAuthority> authorities;
private String firstName;
private String middleName;
private String lastName;
private String email;
private String phone;
private String externalUserId;
// getters/setters
// toString
}
我希望我在这里提供了足够的信息,以便有人可以回答我的问题。我花了一两天时间在互联网上搜索可以回答这个问题的人,但无济于事。一些答案来自 Spring 3 和更早的 Spring 安全性 3.x。如果需要更多信息,请告诉我。谢谢!
我想知道...我是否需要一个实现 UserDetails 的 CustomUserDetails?
再次感谢!
这可能比您想象的要容易得多。
CustomUser userDetails = new CustomUser();
/* TODO: set username, authorities etc */
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user(userDetails));
只要您的 CustomUser
实现了 UserDetails
接口,就允许这样做。