原因追踪
俺们都知道生成token在Granter中需要两步:1.获取authentication 2.根据authentication生成token
而refresh_token模式中authentication是从refreshToken中获取的,所以在Granter中一步到位:1.获取refreshToken 2.根据refreshToken生成authentication 3.根据Authentication生成token
public class RefreshTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "refresh_token";
@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
}
}
追踪源码看看是哪丢失了信息:
这一行中,只将USERNAME赋给了principal,导致信息的丢失,
调用链大概是:TokenEndPoint->CompositeTokenGranter->AbstractTokenGranter->RefreshTokenGranter->DefaultTokenServices->JwtTokenStore (这里开始与认证不一样 )->JwtTokenStoreConverter->DefaultAccessTokenConverter->DefaultUserAuthenticationConverter
在DefaultUserAuthenticationConverter.java中:
//这个方法好像只有refresh_token调用,作用是将refreshToken中读取到的信息map转换为authentication
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Object principal = map.get(USERNAME);//只将USERNAME设入,map中有我们的user_info
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
if (userDetailsService != null) {
UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
return null;
}
通过认证的原理得知,认证分为两步,1.获取Authentication(之前的做法将扩展信息存入Authentication的principal中,可以看05.扩展jwt) 2.通过Authentication生成token(可以看05.怎么扩展token)。而在此之前我们已经完成了根据Authentication生成token,并添加扩展信息,所以现在我们只需要将扩展信息存入Authentication的principal即可。
根据源码的分析,就是在这个方法中没有将map(map中存的是refreshToken的信息)中我们扩展的信息赋值给principal,所以我们可以想办法重写这个方法。
但是这个方法有个弊端:从refreshToken中读出数据生成token和refreshToken,如果用户更新了自己的数据,这个数据我们又封装在了jwt里面,例如手机号:如果没有重写登录的话。那么它通过刷新token获取的手机号不会更新。
所以看源码时又看到一个方法:
DefaultTokenServices.java中
@Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
throws AuthenticationException {
...
//这一步是通过refreshToken生成authentication的,也就是前一个方案
OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
//这一步是读取数据库重新认证的,this.authenticationManager默认为null
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.
//一个类型的token
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);
}
...
return accessToken;
}
从上面这段代码可以看出,要想使用数据库重新认证,就需要提供authenticationManager,还要提供一个可以认证
PreAuthenticatedAuthenticationToken的Provider
编写:
/**
* @ClassName PreAuthenticatedAuthenticationTokenProvider
* @Description TODO
* @Author CY
* @Date 2023/12/2 16:13
* @Version 1.0
*/
@Data
@Component("PreAuthenticatedAuthenticationTokenProvider")
public class PreAuthenticatedAuthenticationTokenProvider implements AuthenticationProvider {
@Autowired
private UsernamePasswordUserDetailService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
PreAuthenticatedAuthenticationToken authenticationToken = (PreAuthenticatedAuthenticationToken) authentication;
//这里有点特殊,可以开debug看看,principal不是username
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) authenticationToken.getPrincipal();
String username= (String) usernamePasswordAuthenticationToken.getPrincipal();
UserDetails user = userDetailsService.loadUserByUsername(username);
if (user == null) {
throw new InvalidInformationException("用户名错误!!!");
}
PreAuthenticatedAuthenticationToken authenticationResult = new PreAuthenticatedAuthenticationToken(user, null, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication);
}
}
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
//自配置用户信息服务
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
//添加自定义的Provider
auth.authenticationProvider(emailAuthenticationProvider);
//添加自定义的Provider,短信验证码登录
auth.authenticationProvider(smsCodeAuthenticationProvider);
auth.authenticationProvider(myUsernamePasswordProvider);
auth.authenticationProvider(preAuthenticatedAuthenticationTokenProvider);
}
//将自配置的AuthenticationManager暴露为Bean
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
测试成功!!!巴适