簡體   English   中英

如何在UserDetailsS​​ervice中獲取當前的用戶身份驗證

[英]How to get current user authentication inside UserDetailsService

在我的應用程序中,我試圖將ActiveDirectory身份驗證與OAuth2刷新令牌結合在一起。

我能夠通過ActiveDirectoryLdapAuthenticationProvider成功進行身份驗證。 我還提供了LdapUserDetailsMapper自定義實現,該實現使用從ActiveDirectory獲取的一些自定義屬性填充UserDetails 這里的關鍵是,這些屬性上設置了機密性標記,並且僅對用戶本身可用(即,經過身份驗證的用戶可以自己讀取這些屬性的值,而其他用戶則不能讀取)。 這些屬性存儲在Authentication對象中,並由應用程序在經過身份驗證的用戶的上下文中使用。

當我嘗試向圖片添加刷新令牌時,事情變得棘手。 刷新令牌要求我實現UserDetailsService ,其中我必須提供僅具有用戶名的新UserDetails 由於保密標志,這是不可行的。 即使我的應用程序中有一些能夠瀏覽ActiveDirectory主帳戶,我也將無法檢索機密屬性。

因此,我寧願提供更多的原子實現,例如檢查用戶是否仍處於活動狀態的函數或提供一組新的用戶權限的函數。 不幸的是,我在Spring Security沒有發現這種原子性。 因此,對於刷新令牌而言,我必須提供UserDetailsService的實現。

如果必須提供新的用戶詳細信息,我希望可以訪問以前的用戶Authentication對象。 在這種情況下,我將檢查用戶,如果該用戶仍處於活動狀態,我將復制先前Authentication所有機密信息。 問題在於它似乎不可用。 當前調用UserDetailsService::loadUserByUsername()SecurityContextHolder.getContext()不包含用戶身份驗證。 UserDetailsService API也無法提供身份驗證-我僅獲得用戶名。 同時,在UserDetailsByNameServiceWrapper類中,用戶的Authentication對象僅存在一個堆棧幀:

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. 目前還沒有其他將刷新令牌添加到應用程序中的方法嗎?

更新:

在這里,我看到一條評論,您可以實現自己的AuthenticationUserDetailsService來解決此問題。 這個我看不出來怎么辦。 AuthorizationServerEndpointsConfigurer中進行了硬編碼,它始終創建UserDetailsByNameServiceWrapper的實例,因此要提供您自己的實現,您將不得不干預AuthorizationServerEndpointsConfigurer初始化過程。

好的,看起來Spring Security 4.0的答案是“ 不能”

因此,我不得不應用以下可行的技巧,但我不太喜歡。 既然可以了,我就在這里發布。 由於它不能解決原始問題,但可以解決該問題,因此我不會將其標記為作者接受。

  1. 切換到JWT令牌
  2. 使用自定義TokenEnhancer可以將重新創建用戶(本例中為用戶機密)所需的所有信息直接注入令牌中。 當然,在將值添加到令牌之前,該值必須由服務器使用對稱加密算法進行加密。
  3. 指示授權服務器使用自定義AccessTokenConverter AccessTokenConverter此實現將從令牌中提取秘密值,對其進行解密並將其放入ThreadLocal字段。
  4. 指示自定義UserDetailsServicestep 3設置的ThreadLocal字段中檢索用戶密碼。 這是到目前為止我發現的將當前授權上下文傳遞給UserDetailsService的最佳方法。 這是我在解決方案中最不喜歡的部分。
  5. 使用自定義安全篩選器從ThreadLocal字段中刪除在step 3設置的值。

PS我仍然看不到實現前面提到的自定義AuthenticationUserDetailsService的可能性。 如果存在這種可能性,那可能是解決問題的另一種方法。

一些有用的鏈接:

我在spring-security-oauth github頁面上收到了Joe Grandja的回復。

將其張貼在這里,因為它實際上為原始問題提供了答案。

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

正在重新驗證用戶。 如果需要,可能需要在自定義實現中執行某些操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM