[英]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
管理的用戶身份驗證中擁有了所有必需的信息,而最終這樣做似乎是多余的。
這里是問題列表:
Spring
在刷新令牌過程中使用先前的UserDetails
對象,以便應用程序可以回答用戶是否仍處於活動狀態並應被授予新的訪問令牌(根本不提供UserDetailsService
)的問題? UserDetailsService::loadUserByUsername()
過程中獲取先前的用戶Authentication
對象,以便可以將其用作機密信息的來源? 更新:
在這里,我看到一條評論,您可以實現自己的AuthenticationUserDetailsService
來解決此問題。 這個我看不出來怎么辦。 在AuthorizationServerEndpointsConfigurer
中進行了硬編碼,它始終創建UserDetailsByNameServiceWrapper
的實例,因此要提供您自己的實現,您將不得不干預AuthorizationServerEndpointsConfigurer
初始化過程。
好的,看起來Spring Security 4.0
的答案是“ 不能” 。
因此,我不得不應用以下可行的技巧,但我不太喜歡。 既然可以了,我就在這里發布。 由於它不能解決原始問題,但可以解決該問題,因此我不會將其標記為作者接受。
TokenEnhancer
可以將重新創建用戶(本例中為用戶機密)所需的所有信息直接注入令牌中。 當然,在將值添加到令牌之前,該值必須由服務器使用對稱加密算法進行加密。 AccessTokenConverter
。 AccessTokenConverter
此實現將從令牌中提取秘密值,對其進行解密並將其放入ThreadLocal
字段。 UserDetailsService
從step 3
設置的ThreadLocal
字段中檢索用戶密碼。 這是到目前為止我發現的將當前授權上下文傳遞給UserDetailsService
的最佳方法。 這是我在解決方案中最不喜歡的部分。 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.