【问题标题】:How to get current user authentication inside UserDetailsService如何在 UserDetailsS​​ervice 中获取当前用户身份验证
【发布时间】:2018-02-28 12:15:01
【问题描述】:

在我的应用程序中,我试图将ActiveDirectory 身份验证与OAuth2 刷新令牌结合起来。

我能够通过ActiveDirectoryLdapAuthenticationProvider 成功进行身份验证。我还提供了LdapUserDetailsMapper 的自定义实现,它使用来自ActiveDirectory 的一些自定义属性填充UserDetails。这里的关键是这些属性上设置了机密标志,并且仅对用户本身可用(即,经过身份验证的用户可以为自己读取这些属性的值,但不能为其他人读取)。这些属性存储在Authentication 对象中,供应用程序在经过身份验证的用户的上下文中使用。

当我尝试向图片添加刷新令牌时,事情变得很棘手。刷新令牌要求我实现一个UserDetailsService,我必须提供新的UserDetails,只有一个用户名。由于机密标志,这是不可行的。即使我的应用程序中有一些能够浏览ActiveDirectory 的主帐户,我也无法检索机密属性。

所以我宁愿提供更多的原子实现,例如检查用户是否仍然处于活动状态的函数或提供一组更新的用户权限的函数。不幸的是,我在Spring Security 中没有找到这种级别的原子性。因此,对于刷新令牌,我似乎必须提供UserDetailsService 的实现。

如果我必须提供新的用户详细信息,我希望能够访问以前的用户 Authentication 对象。在这种情况下,我将检查用户,如果它仍然处于活动状态,我将从之前的Authentication 复制所有机密信息。问题是它似乎不可用。在UserDetailsService::loadUserByUsername() 被称为SecurityContextHolder.getContext() 的那一刻不包含用户身份验证。 UserDetailsService API 也无法进行身份验证 - 我只获取用户名。同时,用户的 Authentication 对象仅存在于 UserDetailsByNameServiceWrapper 类中的一个堆栈帧:

public UserDetails loadUserDetails(T authentication) throws UsernameNotFoundException { return this.userDetailsService.loadUserByUsername(authentication.getName()); }

我最不想在这里做的事情是为所有用户机密信息实现一些内存存储,以便在我需要提供新的UserDetails 时使用。我已经拥有由Spring 管理的用户身份验证所需的所有信息,而我这样做似乎只是多余的。

问题列表如下:

  1. 如果您从应用程序安全架构的角度觉得我做错了什么,请告诉我
  2. 有没有办法在刷新令牌过程中告诉Spring 使用以前的UserDetails 对象,这样如果用户仍然处于活动状态,应用程序就可以回答这个问题,并且应该发出一个新的访问令牌(而不是提供UserDetailsService)?
  3. 有没有办法在调用 UserDetailsService::loadUserByUsername() 期间获取以前的用户 Authentication 对象,以便我可以将其用作机密信息的来源?
  4. 是否有其他我目前看不到的方法可以将刷新令牌添加到我的应用程序?

更新:

Here 我看到一个评论说你可以实现你自己的AuthenticationUserDetailsService 来解决这个问题。这个我不明白怎么办。它在AuthorizationServerEndpointsConfigurer 中硬编码,它始终创建UserDetailsByNameServiceWrapper 的实例,因此要提供您自己的实现,您必须干预AuthorizationServerEndpointsConfigurer 初始化过程。

【问题讨论】:

    标签: java spring-security oauth-2.0 active-directory refresh-token


    【解决方案1】:

    好的,看起来Spring Security 4.0 的答案是你不能

    所以我不得不应用以下有效的技巧,但我不太喜欢它。因为它有效,所以我把它贴在这里。由于它没有解决原始问题,但可以解决它,我不会将其标记为作者已接受。

    1. 切换到 JWT 令牌
    2. 使用自定义TokenEnhancer 将重新创建用户所需的所有信息(在我的情况下为用户密码)直接注入令牌。当然,该值必须由服务器使用对称加密算法加密,然后才能添加到令牌中。
    3. 指示授权服务器使用自定义AccessTokenConverterAccessTokenConverter 的这种实现将从令牌中提取秘密值,对其进行解密并将其放入 ThreadLocal 字段。
    4. 指示自定义UserDetailsServicestep 3 中设置的ThreadLocal 字段中检索用户密码。这是迄今为止我发现的将当前授权上下文传递给UserDetailsService 的最佳方式。这是我的解决方案中我最不喜欢的部分。
    5. 使用自定义安全过滤器从ThreadLocal 字段中删除step 3 中设置的值。

    附:我仍然看不到实现前面提到的自定义AuthenticationUserDetailsService 的可能性。如果存在这种可能性,它可能是解决问题的另一种方法。

    一些有用的链接:

    【讨论】:

      【解决方案2】:

      我收到了来自Joe Grandjaspring-security-oauth github page 的回复。

      在此处发布它,因为它实际上提供了原始问题的答案。

      嗨@masm22。为了帮助解决问题 1 和 2,下面是一个自定义配置,它允许您挂钩 refresh_token 授权并提供您自己的行为或委托给 super 以继续当前行为。它还允许您访问用户身份验证,以便您可以读取您的自定义(机密)属性。

      @Configuration
      @EnableAuthorizationServer
      public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
      
              .....   // other config
      
          @Autowired
          private ClientDetailsService clientDetailsService;
      
          @Override
          public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
              endpoints.tokenServices(this.customTokenServices());
          }
      
          private DefaultTokenServices customTokenServices() {
              DefaultTokenServices tokenServices = new CustomTokenServices();
              tokenServices.setTokenStore(new InMemoryTokenStore());
              tokenServices.setSupportRefreshToken(true);
              tokenServices.setReuseRefreshToken(true);
              tokenServices.setClientDetailsService(this.clientDetailsService);
              return tokenServices;
          }
      
          private static class CustomTokenServices extends DefaultTokenServices {
              private TokenStore tokenStore;
      
              @Override
              public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) throws AuthenticationException {
                  OAuth2RefreshToken refreshToken = this.tokenStore.readRefreshToken(refreshTokenValue);
                  OAuth2Authentication authentication = this.tokenStore.readAuthenticationForRefreshToken(refreshToken);
      
                  // Check attributes in the authentication and
                  // decide whether to grant the refresh token
                  boolean allowRefresh = true;
      
                  if (!allowRefresh) {
                      // throw UnauthorizedClientException or something similar
      
                  }
      
                  return super.refreshAccessToken(refreshTokenValue, tokenRequest);
              }
      
              @Override
              public void setTokenStore(TokenStore tokenStore) {
                  super.setTokenStore(tokenStore);
                  this.tokenStore = tokenStore;
              }
          }
      }
      

      我想指出的另一件事是DefaultTokenServices.refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) 有以下代码:

          OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
          if (this.authenticationManager != null && !authentication.isClientOnly()) {
              // The client has already been authenticated, but the user authentication might be old now, so give it a
              // chance to re-authenticate.
              Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());
              user = authenticationManager.authenticate(user);
              Object details = authentication.getDetails();
              authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);
              authentication.setDetails(details);
          }
      

      正在重新对用户进行身份验证。如果需要,您可能希望在自定义实现中做一些事情。

      【讨论】:

        猜你喜欢
        • 2015-06-02
        • 1970-01-01
        • 1970-01-01
        • 2015-10-12
        • 2015-10-16
        • 1970-01-01
        • 2017-05-10
        • 1970-01-01
        相关资源
        最近更新 更多