简体   繁体   English

如何在UserDetailsS​​ervice中获取当前的用户身份验证

[英]How to get current user authentication inside UserDetailsService

In my application I am trying to unite ActiveDirectory authentication with OAuth2 refresh tokens. 在我的应用程序中,我试图将ActiveDirectory身份验证与OAuth2刷新令牌结合在一起。

I was able to successfully authenticate via ActiveDirectoryLdapAuthenticationProvider . 我能够通过ActiveDirectoryLdapAuthenticationProvider成功进行身份验证。 I have also provided my custom implementation of LdapUserDetailsMapper that populates the UserDetails with some custom attributes taken from ActiveDirectory . 我还提供了LdapUserDetailsMapper自定义实现,该实现使用从ActiveDirectory获取的一些自定义属性填充UserDetails Key thing here is that these attributes have a confidentialty flag set on them and are only available to the user itself (ie authenticated user could read the values of these attributes for himself but not for the others). 这里的关键是,这些属性上设置了机密性标记,并且仅对用户本身可用(即,经过身份验证的用户可以自己读取这些属性的值,而其他用户则不能读取)。 These attributes are stored in Authentication object and are used by an application in a context of an authenticated user. 这些属性存储在Authentication对象中,并由应用程序在经过身份验证的用户的上下文中使用。

Things get tricky when I try to add refresh tokens to the picture. 当我尝试向图片添加刷新令牌时,事情变得棘手。 Refresh tokens require me to implement a UserDetailsService where I have to provide new UserDetails having just a user name. 刷新令牌要求我实现UserDetailsService ,其中我必须提供仅具有用户名的新UserDetails This is not feasible due to confidentialty flag. 由于保密标志,这是不可行的。 Even if I have some master account in my application with the ability to browse ActiveDirectory I will not be able to retrieve the confidential attributes. 即使我的应用程序中有一些能够浏览ActiveDirectory主帐户,我也将无法检索机密属性。

So I would rather prefer to provide more atomic implementations like the function that checks if the user is still active or the function that provides a renewed set of user authorities. 因此,我宁愿提供更多的原子实现,例如检查用户是否仍处于活动状态的函数或提供一组新的用户权限的函数。 Unfortunately I did not find this level of atomicity in Spring Security . 不幸的是,我在Spring Security没有发现这种原子性。 So it looks like for refresh tokens I have to provide an implementation of UserDetailsService . 因此,对于刷新令牌而言,我必须提供UserDetailsService的实现。

If I have to provide new user details I would like to have an access to previous user Authentication object. 如果必须提供新的用户详细信息,我希望可以访问以前的用户Authentication对象。 In this case I will check the user and if it is still active I will copy all the confidential information from previous Authentication . 在这种情况下,我将检查用户,如果该用户仍处于活动状态,我将复制先前Authentication所有机密信息。 The problem is that it does not seem to be available. 问题在于它似乎不可用。 At the moment when UserDetailsService::loadUserByUsername() is called SecurityContextHolder.getContext() does not contain the user authentication. 当前调用UserDetailsService::loadUserByUsername()SecurityContextHolder.getContext()不包含用户身份验证。 Authentication is also not available from UserDetailsService API - I only get the user name. UserDetailsService API也无法提供身份验证-我仅获得用户名。 At the same time user's Authentication object is present just one stack frame up in UserDetailsByNameServiceWrapper class: 同时,在UserDetailsByNameServiceWrapper类中,用户的Authentication对象仅存在一个堆栈帧:

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

The least thing I want to do here is to implement some in-memory storage for all user confidential information to be used whenever I need to provide new UserDetails . 我在这里要做的至少一件事情是为我需要提供新的UserDetails时要使用的所有用户机密信息实现一些内存存储。 I already have all the required information in user authentication managed by Spring and doing this on my end seems to be just surplus. 我已经在Spring管理的用户身份验证中拥有了所有必需的信息,而最终这样做似乎是多余的。

And here comes question list: 这里是问题列表:

  1. If you feel that I am doing something terribly wrong from the perspective of application security architecture, please tell me 如果从应用程序安全体系结构的角度来看,我觉得自己做错了什么,请告诉我
  2. Is there a way to tell Spring during refresh token procedure to use previous UserDetails object so that application could just answer the question if the user is still active and should be issued a new access token (and not provide the UserDetailsService at all)? 有没有一种方法可以告诉Spring在刷新令牌过程中使用先前的UserDetails对象,以便应用程序可以回答用户是否仍处于活动状态并应被授予新的访问令牌(根本不提供UserDetailsService )的问题?
  3. Is there a way to get previous user Authentication object during the call to UserDetailsService::loadUserByUsername() so that I could use it as a source of confidential info? 有没有一种方法可以在调用UserDetailsService::loadUserByUsername()过程中获取先前的用户Authentication对象,以便可以将其用作机密信息的来源?
  4. Is there some other approach that I do not see at the moment to add refresh tokens to my application? 目前还没有其他将刷新令牌添加到应用程序中的方法吗?

Update: 更新:

Here I saw a comment that you could implement your own AuthenticationUserDetailsService to work around the problem. 在这里,我看到一条评论,您可以实现自己的AuthenticationUserDetailsService来解决此问题。 This I do not see how to do. 这个我看不出来怎么办。 It is hardcoded in AuthorizationServerEndpointsConfigurer that it always creates an instance of UserDetailsByNameServiceWrapper so to provide your own implementation you would have to interfere into AuthorizationServerEndpointsConfigurer initialization process. AuthorizationServerEndpointsConfigurer中进行了硬编码,它始终创建UserDetailsByNameServiceWrapper的实例,因此要提供您自己的实现,您将不得不干预AuthorizationServerEndpointsConfigurer初始化过程。

OK, looks like the answer with Spring Security 4.0 is you can't . 好的,看起来Spring Security 4.0的答案是“ 不能”

So I had to apply the following hack which works, but I do not like it very much. 因此,我不得不应用以下可行的技巧,但我不太喜欢。 Since it works I am posting it here. 既然可以了,我就在这里发布。 Since it does not solve the original problem, but works around it I will not mark it as accepted by the author. 由于它不能解决原始问题,但可以解决该问题,因此我不会将其标记为作者接受。

  1. Switch to JWT tokens 切换到JWT令牌
  2. Use custom TokenEnhancer to inject all information that is required to recreate the user (user secret in my case) to the token directly. 使用自定义TokenEnhancer可以将重新创建用户(本例中为用户机密)所需的所有信息直接注入令牌中。 Of course, the value must be encrypted by the server with symmetrical crypto algorithm before adding it to the token. 当然,在将值添加到令牌之前,该值必须由服务器使用对称加密算法进行加密。
  3. Instruct authorization server to use custom AccessTokenConverter . 指示授权服务器使用自定义AccessTokenConverter This implementation of AccessTokenConverter would extract the secret value from the token, decrypt it and put it to ThreadLocal field. AccessTokenConverter此实现将从令牌中提取秘密值,对其进行解密并将其放入ThreadLocal字段。
  4. Instruct custom UserDetailsService to retrieve the user secret from the ThreadLocal field set in step 3 . 指示自定义UserDetailsServicestep 3设置的ThreadLocal字段中检索用户密码。 This is the best way I found so far to deliver the current authorization context to UserDetailsService . 这是到目前为止我发现的将当前授权上下文传递给UserDetailsService的最佳方法。 And this is the part that I do not like most in my solution. 这是我在解决方案中最不喜欢的部分。
  5. Use custom security filter to erase the value set in step 3 from ThreadLocal field. 使用自定义安全筛选器从ThreadLocal字段中删除在step 3设置的值。

PS I still do not see the possibility to implement custom AuthenticationUserDetailsService that was mentioned earlier. PS我仍然看不到实现前面提到的自定义AuthenticationUserDetailsService的可能性。 If such possibility exists it could have been another way to solve the problem. 如果存在这种可能性,那可能是解决问题的另一种方法。

Some useful links: 一些有用的链接:

I've got the response from Joe Grandja on spring-security-oauth github page . 我在spring-security-oauth github页面上收到了Joe Grandja的回复。

Posting it here since it actually provides an answer to the original question. 将其张贴在这里,因为它实际上为原始问题提供了答案。

Hi @masm22. 嗨@ masm22。 To help with question 1 and 2, below is a custom configuration that will allow you to hook into the refresh_token grant and provide your own behaviour or delegate to super to proceed with current behaviour. 为了帮助解决问题1和2,下面是一个自定义配置,该配置将使您可以进入refresh_token授予并提供自己的行为或委托super来继续当前的行为。 It will also allow you to access the user Authentication so you can read your custom (confidential) attributes. 它还将允许您访问用户身份验证,以便您可以读取自定义(机密)属性。

@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;
        }
    }
}

The other thing I want to point out for your information is in DefaultTokenServices.refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) has the following code: 我想指出的另一件事是在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);
    }

The user is being re-authenticated. 正在重新验证用户。 Possibly something you may want to do in your custom implementation if need be. 如果需要,可能需要在自定义实现中执行某些操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM