当前位置:   article > 正文

Oauth刷新令牌失效,InvalidGrantException问题_alist刷新令牌刷新不出来

alist刷新令牌刷新不出来

问题描述

最近项目组同事反应,系统经常在使用的过程中弹出会话超时提示。我觉得这个问题属实有点狗,平台设计之初保存了refresh_token,不仅开发了定时刷新令牌的机制,也对这个机制进行了反复测试,只要access_token快到期了就会取回新的令牌,运行非常顺畅。

不过有问题还是要解决。先翻一遍日志,看见这么个东西:

2023-11-06 11:33:42.222 [http-nio-9101-exec-4] ERRORc.xxx.oauth.client.api.local.OauthLocalApi-400 : [<InvalidGrantException><error>invalid_grant</error><error_description>Invalid refresh token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiIxNCIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI1YmRmMGVlYi0zN2... (470 bytes)]
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 : [<InvalidGrantException><error>invalid_grant</error><error_description>Invalid refresh token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiIxNCIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI1YmRmMGVlYi0zN2... (470 bytes)]
	at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:101)
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:170)
	at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:112)
	at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
	at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:782)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:740)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:674)
	at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:583)
	……
	……
	……
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

显然,调用oauth/token接口刷新令牌时,oauth抛出了异常,提示我们传入的refreshToken是非法的。复测该问题,在测试系统登录后用refreshToken调用oauth/token接口,发现接口可以正常返回新的令牌信息,没有抛出上面的异常。

排查

没有简洁的办法,又开始了扒代码大法。顺着提示信息,找到了DefaultTokenServices类,其中这段代码是刷新令牌的逻辑:

@Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
	public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
			throws AuthenticationException {

		if (!supportRefreshToken) {
			throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
		}

		OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
		if (refreshToken == null) {
			throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
		}

		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);
		}
		String clientId = authentication.getOAuth2Request().getClientId();
		if (clientId == null || !clientId.equals(tokenRequest.getClientId())) {
			throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
		}

		// clear out any access tokens already associated with the refresh
		// token.
		tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);

		if (isExpired(refreshToken)) {
			tokenStore.removeRefreshToken(refreshToken);
			throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken);
		}

		authentication = createRefreshedAuthentication(authentication, tokenRequest);

		if (!reuseRefreshToken) {
			tokenStore.removeRefreshToken(refreshToken);
			refreshToken = createRefreshToken(authentication);
		}

		OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
		tokenStore.storeAccessToken(accessToken, authentication);
		if (!reuseRefreshToken) {
			tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
		}
		return accessToken;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

可以看到方法开始的部分,有两项对refreshToken的验证,第一个是验证当前环境是否支持刷新令牌,第二个是验证传入的refreshToken是否合法。debug这段代码好几次,终于发现了问题:
第一次进入这个方法,refreshToken可以验证通过,且生成新的access_token和refresh_token;
使用新的refresh_token第二次进入这个方法,发现tokenStore.readRefreshToken(refreshTokenValue)返回null,验证不通过。
debug跟踪进入tokenStore.readRefreshToken(refreshTokenValue)方法,发现refreshTokenStore中没有我们使用的新的refresh_token,只有第一次登录时使用的refresh_token。
在这里插入图片描述
此时我们基本确定了问题方向:第一次刷新令牌之后,oauth返回了一个新的refresh_token,但是这个refresh_token并没有存到refreshTokenStore中。

结论

回到上面的DefaultTokenServices代码中,可见

		if (!reuseRefreshToken) {
			tokenStore.removeRefreshToken(refreshToken);
			refreshToken = createRefreshToken(authentication);
		}

		OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
		tokenStore.storeAccessToken(accessToken, authentication);
		if (!reuseRefreshToken) {
			tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里有个reuseRefreshToken配置,默认为true,此时可以重复使用refreshToken,而且新生成的refresh_token不会被存入refreshTokenStore

找到问题后,回复前端小伙伴,刷新令牌以后,不要将服务器返回的新refresh_token保存起来,而是一直重复使用一开始的refresh_token去刷新,这样问题就解决了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/277241
推荐阅读
相关标签
  

闽ICP备14008679号