我们正在使用Spring 4.3.9.RELEASE和Spring Security 4.2.3.RELEASE,所以这些是我们见过的一些最新版本。我们有一个RESTful(spring-mvc)后端,我们使用Spring Web Security进行基于角色的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;
}
我们知道用户已使用用户名和密码从网站登录。我们知道UI有一个令牌,它们在标题中传递它。我们的安全性使用SiteMinder示例,这意味着我们有一个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
}
}
因此,一旦我们建立了令牌有效,并且我们从该第三方获得了额外的用户信息,并且我们拥有该API授权的有效角色......那么我们就可以执行控制器本身。我们看到这个代码是传统的,可以让现有用户脱离Spring Security Context。
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
实际上,根据我们已经阅读过的内容,当您拥有自定义用户和CustomUserDetails时,这是实现此目的的方法。使用此代码,我们希望获得此用户的电子邮件。当我们使用Advanced REST Client实际测试API时,这一切都有效。我们的QA必须针对网站进行身份验证,并且他们将令牌传递回UI,他们获得这些访问令牌,并将它们放在高级REST客户端(或邮递员)的标题中,这一切都有效。
我们甚至有代码在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,如果没有设置,那么当我们尝试收到该电子邮件时,我们就会失败。因此,我们一直在寻找在身份验证中设置模拟用户的多种方法,因此当我们访问Controller时,我们可以在安全上下文中获取该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 Security 3.x开始,一些答案有点老了。如果需要更多信息,请告诉我。谢谢!
我想知道......如果我需要一个包含UserDetails的CustomUserDetails?
再次感谢!
答案 0 :(得分:5)
这可能比你想象的容易得多。
CustomUser userDetails = new CustomUser();
/* TODO: set username, authorities etc */
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user(userDetails));
只要您的CustomUser
实施UserDetails
界面,就可以使用此功能。