09.使用refresh token刷新令牌出现信息丢失

原因追踪

俺们都知道生成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);
	}
}

追踪源码看看是哪丢失了信息:

image-20231201155359229

这一行中,只将USERNAME赋给了principal,导致信息的丢失,

image-20231201155803064

调用链大概是: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();
    }
}

测试成功!!!巴适

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值