如何在UserDetailsS​​ervice

时间:2018-02-28 12:15:01

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

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

我能够通过ActiveDirectoryLdapAuthenticationProvider成功进行身份验证。我还提供了LdapUserDetailsMapper的自定义实现,其中使用UserDetails中的一些自定义属性填充ActiveDirectory。这里的关键是这些属性在它们上面设置了一个机密标志,并且只对用户本身可用(即,经过身份验证的用户可以为自己读取这些属性的值,但不能为其他人读取这些属性的值)。这些属性存储在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. 在调用Authentication期间,有没有办法获取以前的用户UserDetailsService::loadUserByUsername()对象,以便我可以将其用作保密信息的来源?
  4. 目前还没有其他方法可以为我的应用程序添加刷新令牌吗?
  5. 更新

    Here我看到了一条评论,您可以实施自己的AuthenticationUserDetailsService来解决问题。这个我看不怎么做。在AuthorizationServerEndpointsConfigurer中对其进行硬编码,它始终会创建UserDetailsByNameServiceWrapper的实例,因此,为了提供您自己的实现,您必须干扰AuthorizationServerEndpointsConfigurer初始化过程。

2 个答案:

答案 0 :(得分:0)

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

所以我必须应用以下有效的黑客,但我不太喜欢它。既然它有效,我就在这里发帖。由于它没有解决原始问题,但解决了它,我不会将其标记为作者接受。

  1. 切换到JWT代币
  2. 使用自定义TokenEnhancer将重新创建用户所需的所有信息(在我的情况下为用户机密)直接注入令牌。当然,在将服务器添加到令牌之前,必须使用对称加密算法对服务器进行加密。
  3. 指示授权服务器使用自定义AccessTokenConverterAccessTokenConverter的此实现将从令牌中提取秘密值,对其进行解密并将其放入ThreadLocal字段。
  4. 指示自定义UserDetailsServiceThreadLocal中设置的step 3字段中检索用户密码。这是我到目前为止发现的最佳方式,即将当前授权上下文传递给UserDetailsService。这是我在解决方案中最不喜欢的部分。
  5. 使用自定义安全过滤器从step 3字段中删除ThreadLocal中设置的值。
  6. P.S。我仍然没有看到实现前面提到的自定义AuthenticationUserDetailsService的可能性。如果存在这种可能性,它可能是另一种解决问题的方法。

    一些有用的链接:

答案 1 :(得分:0)

我在Joe Grandja上收到spring-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);
    }

正在重新验证用户身份。如果需要,您可能希望在自定义实现中执行某些操作。