原文连接: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));
}
复制代码