原文連接:leozzy.com/?p=119java
SecurityContextHolder 是用來保存 SecurityContext 的,經過 SecurityContextHolder.getContext() 靜態方法能夠得到當前 SecurityContext 對象。緩存
SecurityContext 持有表明當前用戶相關信息的 Authentication 的引用, Authentication 經過 SecurityContext 對象的 getAuthentication() 方法得到。ide
經過 Authentication.getPrincipal() 能夠獲取到表明當前用戶的信息,這個對象一般是 UserDetails 的實例。ui
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
複製代碼
獲取到的 UserDetail 中包含用戶名和密碼等用戶信息,可是該密碼是通過 Spring 加密的而且不可逆(hash + salt)的。加密
那麼咱們如何才能拿到明文的密碼呢?spa
每次登陸時,表單中填寫用戶名密碼,以這裏做爲切入點,找到登錄接口以及 Spring Security 對錶單中的帳號密碼進行認證的地方,那就是本身定義的 AuthenticationProvider 的實現類,authenticate() 方法參數中有 Authentication 對象,這是 Spring 本身已經封裝好的對象,其中包含帳號密碼信息。調試
String username = authentication.getName();
String password = (String) authentication.getCredentials();
複製代碼
但這個 authenticate() 是由 SpringSecurity 來調用的,咱們沒法在其餘方法中調用這個方法獲取帳號密碼。那麼就直接模仿該方法,經過 SecurityContextHolder.getContext().getAuthentication() 得到 Authentication 對象,而不是文章開頭那樣拿到 UserDetail 對象(包含的是加密後的密碼),再經過 getCredentials 便可得到明文密碼。code
就這麼簡單嗎?經過調試發現除了在 AuthenticationProvider 實現類的 authenticate() 認證方法中可以經過這種方式得到明文密碼,其餘地方使用 Authentication 拿到的密碼都是 null,緣由以下:對象
默認狀況下,在認證成功後,ProviderManager 會清除返回的 Authentication 中的憑證信息,如密碼。因此若是你在無狀態的應用中將返回的 Authentication 信息緩存起來了,那麼之後你再利用緩存的信息去認證將會失敗,由於它已經不存在密碼這樣的憑證信息了。因此在使用緩存的時候你應該考慮到這個問題。一種解決辦法是設置 ProviderManager 的 eraseCredentialsAfterAuthentication 屬性爲 false,或者想辦法在緩存時將憑證信息一塊兒緩存。繼承
那麼關鍵是如何設置 eraseCredentialsAfterAuthentication 屬性呢?
在繼承了 WebSecurityConfigurerAdapter 的類中,重寫 configure(AuthenticationManagerBuilder auth) 方法,設置 ProviderManager 的屬性便可。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(false);
}
複製代碼
在頁面退出登陸時,會經過 clearAuthentication(true) 方法清空 SecurityContext Authentication 相關信息,以不一樣帳號登陸,保存的都是當時登陸的 Authentication 信息。
若是在頁面修改密碼,那麼 Authentication 默認不會更新,須要本身手動更新 SecurityContext 中 Authentication 的信息。若是不退出登陸,使用了 Authentication 的地方依然使用的舊密碼。
private void updateSecurityContext(String newPwd) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
UserDetails user = kylinUserService.loadUserByUsername(username);
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user, newPwd, authorities));
}
複製代碼