【问题标题】:Spring Security 4 with third party authentication token带有第三方身份验证令牌的 Spring Security 4
【发布时间】:2016-02-20 14:29:01
【问题描述】:

我已经对此进行了大量研究,并且有大量示例,但我对选择感到不知所措,但我已经将一些代码放在一起来做我想做的事情。

我有一个 Apache 负载均衡器,它会将我重定向到 OpenAM 10 的登录页面。当我转到 https://portal.mydomain.com/myapp 时,我会被重定向到: https://sso.mydomain.net:9443/sso/UI/Login?module=AGMAuth&goto=https%3A%2F%2Fvmlb.mydomain.net%3A443%2Fmyapp

这是我的登录页面,我被要求输入用户名和密码,然后我在 cookie 中取回了一个类似于 (AQIC5wM2LY4Sfcyl9raxhA-xBE14_4QBbkjXi-rFn43PMpc.AAJTSQACMDE.) 的令牌。从前端,我可以从 cookie 中获取令牌,我可以将它添加到请求标头中,以便每次调用后端是 SpringMVC RESTful 后端。

仅供参考,我知道如何调用 OpenAM RESTful api,传入该令牌并取回用户名和其他一些信息。 OpenAM 中不保留角色。如果在我的 RESTful Web 服务的后端,我使用令牌调用 OpenAM ...如果我没有取回任何数据,那么我想返回我的自定义身份验证登录页面:https://sso.mydomain.net:9443/sso/UI/Login?module=AGMAuth&goto=https%3A%2F%2Fvmlb.mydomain.net%3A443%2Fmyapp

这是我的一个安全 URL 的控制器:

@RequestMapping(value = "/trialId/{trialId}", method = RequestMethod.GET, headers = "Accept=application/json")
public @ResponseBody TrialEntity getTrialById(@PathVariable("trialId") long trialId)
{
    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    System.out.println("TrialController: getTrialById: principal");

    UserAccountEntity user = null;
    if (principal instanceof UserAccountEntity)
    {
        user = ((UserAccountEntity) principal);
    }

    TrialEntity trialEntity = service.getById(trialId);
    System.out.println("TrialController: retrieveTrial: trialEntity=" + trialEntity);
    return trialEntity;
}

我是否需要让每个安全 URL 都尝试获取委托人?

现在这是我的 spring-security.XML 安全文件。这可能不正确,但这是我开始的地方:

<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd">

<http realm="Protected API" 
    use-expressions="true"                     
    create-session="stateless" 
    entry-point-ref="unauthorizedEntryPoint" 
    authentication-manager-ref="restAuthenticationManager">

    <custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
     <intercept-url pattern="/corelabs" access="permitAll" />
    <intercept-url pattern="/sponsors" access="permitAll" />
    <intercept-url pattern="/trials/trialId/**" access="hasRole('trialAdmin')" />
    <intercept-url pattern="/subjects" access="hasRole('siteSender') and hasRole('trialManager')" />
</http>

<authentication-manager>
  <authentication-provider ref="customAuthenticationProvider"/>
</authentication-manager>   

 <beans:bean id="customUserDetailsService" class="com.agmednet.server.security.CustomUserDetailsService" />

</beans:beans>

现在这是后端的一些安全代码。这是返回数据库以根据用户名获取用户详细信息的代码,我还可以为该用户加载角色。我是从 Spring Security 4 示例之一中得到的。

@Service("customUserDetailsService")
@Transactional
public class CustomUserDetailsService implements UserDetailsService
{
private static final Log logger = LogFactory.getLog(CustomUserDetailsService.class);

@Autowired
private UserAccountDao userAccountDao;

@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
    System.out.println("CustomUserDetailsService: username : " + username);
    UserAccountEntity userAccountEntity = userAccountDao.getByUsername(username);
    System.out.println("CustomUserDetailsService: userAccountEntity : " + userAccountEntity);
    if (userAccountEntity == null)
    {
        System.out.println("CustomUserDetailsService: userAccountEntity not found");
        throw new UsernameNotFoundException("Username not found");
    }

    System.out.println("CustomUserDetailsService: START: springframework.user");
    // this "User" object is: org.springframework.security.core.userdetails.User
    User user = new User(userAccountEntity.getUsername(), userAccountEntity.getPassword(), true, true, true, true,
        getGrantedAuthorities(userAccountEntity));

    System.out.println("CustomUserDetailsService: FINISH: springframework.user = " + user);
    return user;
}

private List<GrantedAuthority> getGrantedAuthorities(UserAccountEntity userAccountEntity)
{
    System.out.println("getGrantedAuthorities: START");
    List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

    for (UserRoleEntity userRoleEntity : userAccountEntity.getUserRoleList())
    {
        System.out.println("getGrantedAuthorities: UserRoleEntity : " + userRoleEntity);
        authorities.add(new SimpleGrantedAuthority("ROLE_" + userRoleEntity.getUserRoleName()));
    }
    System.out.print("authorities :" + authorities);
    return authorities;
}

}

这是另一段安全代码,我不确定它是否适合我。因为在这种情况下,我真的在看预认证策略,我不确定它们中的任何一个是否真的适用于这种情况。我还看到我可以实现一个 RequestHeaderFilter,但我也不确定它是如何适应的。

public class CustomAuthenticationProvider implements AuthenticationProvider
{
@Autowired
private CustomUserDetailsService userService;

public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
    String username = authentication.getName();
    String password = (String) authentication.getCredentials();

    UserDetails user = userService.loadUserByUsername(username);

    if (user == null || !user.getUsername().equalsIgnoreCase(username))
    {
        throw new BadCredentialsException("Username not found.");
    }

    if (!password.equals(user.getPassword()))
    {
        throw new BadCredentialsException("Wrong password.");
    }

    Collection<? extends GrantedAuthority> authorities = user.getAuthorities();

    return new UsernamePasswordAuthenticationToken(user, password, authorities);
}

public boolean supports(Class<?> arg0)
{
    return true;
}

}

因此,我正在提取一些代码,并尝试根据其他人的工作解决方案的许多细微变化来理解这一切是如何工作的。 同样,我有一个来自第三方 API 并放入 cookie 的令牌。所以,我正在研究对我来说绝对有意义的记住我的场景……除了当令牌无效、过期或不存在时,我会被重定向到第三方登录页面。我以为我看到了一个自定义登录表单的示例,该表单给出了一个 URL,所以如果身份验证无效(令牌不存在或无效),那么我们没有被授权并被发送回那个可以工作的自定义登录 url对我来说。

根据我已经添加的内容,在将令牌传递给 OpenAM 后,我应该能够从令牌中获取用户名……但我是否需要某种请求头过滤器才能首先获取它……然后使用加载用户详细信息和角色并将其放入 SecurityContext 的用户名?

我只是所有这些 Spring Security 管道的菜鸟,所以对这个特定设置的任何帮助都会非常有帮助。顺便说一句,我不介意将 XML 用于 spring 安全配置。如果我需要添加我的 web.xml 文件,我也可以这样做,但请假设我确实正确设置了我的 web.xml。谢谢!

更新: 我有一个看起来像这样的单元测试:

@Test
public void testMockGetById() throws Exception
{
    MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/trialId/149");
    this.mockMvc.perform(requestBuilder).andDo(print()).andExpect(status().isOk());
}

基于下面建议的 SiteMinder 示例。我现在正在寻找一个名为“openam_token”而不是“SM_USER”的请求标头

所以,现在我需要更改我的单元测试以在请求标头中添加一个令牌。此令牌将返回有权访问此 URL 的正确用户。第二个测试是拥有来自不同用户的令牌,该用户无权访问此 URL。如果我的测试条件有效,我会认为这是一场胜利。

【问题讨论】:

    标签: spring-mvc authentication spring-security token openam


    【解决方案1】:

    Siteminder 身份验证的工作方式与在负载平衡器 (Apache) 上设置请求标头的方式类似。我建议查看有关如何使用 Spring Security 设置 siteminder 的示例。请参阅this 和弹簧文档here

    不要认为您需要 CustomAuthenticationProvider,因为在请求到达您的 REST API 之前正在处理身份验证。

    【讨论】:

    • 好的...所以,我正在尝试实现这个示例,看来我必须添加的唯一代码是我已经拥有的 CustomUserDetailsS​​ervice,这很好。但是这个 siteMinderFilter,它正在寻找 SM_USER 的请求标头,我想我可以将其更改为任何其他标头?它不是用户名,而是令牌……所以我想知道我是否可以更改 loadUserName 以取而代之的是令牌字符串,然后调用 OpenAM 以获取用户名……您认为这可行吗?
    • 是的,标题名称可以是您想要的任何名称(principalRequestHeader)。用户详细信息服务是自定义实现,可以根据此令牌获取详细信息。
    • 我刚刚更新了单元测试的问题。我需要向请求构建器添加一个标头,这应该不会太难,但是在尝试我得到一个错误,即 SecurityContext 在我的测试中为空。有关正确单元测试的任何提示以确保这是正确的吗?谢谢!
    • 没用过mockmvc,所以给不了有用的反馈。从堆栈中跟踪错误可能会有所帮助。
    猜你喜欢
    • 2017-07-10
    • 2015-06-12
    • 2013-06-12
    • 1970-01-01
    • 2018-06-21
    • 2014-03-26
    • 2015-08-23
    • 2013-09-25
    • 2018-10-05
    相关资源
    最近更新 更多