• 聊聊 OAuth 2.0 的 Token 续期处理


    Token 校验逻辑

    // CheckTokenEndpoint.checkToken
    @RequestMapping(value = "/oauth/check_token")
    @ResponseBody
    public Map<String, ?> checkToken(@RequestParam("token") String value) {
        
        // 根据 token 查询保存在 tokenStore 的令牌全部信息
    	OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
    	if (token == null) {
    		throw new InvalidTokenException("Token was not recognised");
    	}
    
    	if (token.isExpired()) {
    		throw new InvalidTokenException("Token has expired");
    	}
        
        // 根据 token 查询保存的 认证信息 还有权限角色等 (业务信息)
    	OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
    
    	return accessTokenConverter.convertAccessToken(token, authentication);
    }
    
      1. 当客户端带着 header token 访问 oauth2 资源服务器,资源服务器会自动拦截 token
      1. 发送 token 到 认证服务器 校验 token 合法性
      1. 若认证服务器返回给资源服务器是token不合法,则资源服务器返回给客户端对应的信息

    Token 生成逻辑

        //DefaultTokenServices.createAccessToken 代码逻辑
    	public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
            
            // 根据用户信息(username),查询已下发的token 
    		OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
    		OAuth2RefreshToken refreshToken = null;
    	    
    	    // 存在已下发的token
    		if (existingAccessToken != null) {
    		    // 1. token 已经被标志过期,则删除 
    			if (existingAccessToken.isExpired()) {
    				if (existingAccessToken.getRefreshToken() != null) {
    					refreshToken = existingAccessToken.getRefreshToken();
    					tokenStore.removeRefreshToken(refreshToken);
    				}
    				tokenStore.removeAccessToken(existingAccessToken);
    			}
    			else {
    				// 直接返回存在的 token,并保存一下token 和 用户信息的关系 (username)
    				tokenStore.storeAccessToken(existingAccessToken, authentication);
    				return existingAccessToken;
    			}
    		}
    
            // 不存在则创建新的 token
    		OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
    		tokenStore.storeAccessToken(accessToken, authentication);
    		// In case it was modified
    		refreshToken = accessToken.getRefreshToken();
    		if (refreshToken != null) {
    			tokenStore.storeRefreshToken(refreshToken, authentication);
    		}
    		return accessToken;
    
    	}
    
    
      1. 当我们通过oauth2 去获取 token 时,若当前用户已经存在对应的token,直接返回而不不会创建新 token。
      1. 这就意味着,虽然设置了对应客户端获取 token 的有效时间,这里获取到的token
        若是已下发旧token,有效时间不会和session 机制一样自动续期。
      1. 综上情况,在操作过程中token 过期是一个常态化的问题。

    Token 刷新逻辑

    curl --location --request POST 'http://auth-server/oauth/token?grant_type=refresh_token' 
    --header 'Authorization: Basic dGVzdDp0ZXN0' 
    --header 'VERSION: dev' 
    --data-urlencode 'scope=server' 
    --data-urlencode 'refresh_token=eccda61e-0c68-43af-8f67-6302cb389612'
    

    若上,当 前端拿着正确的(未过期且未使用过)refresh_token 去调用 认证中心的刷新 端点刷新时,会 触发RefreshTokenGranter, 返回新的 Token

    public class RefreshTokenGranter extends AbstractTokenGranter {
    	
    	@Override
    	protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
    		String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
    		return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
    	}
    	
    }
    
    
    • refreshAccessToken 代码实现,调用 tokenStore 生成新的token
    @Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
    	public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
    			throws AuthenticationException {
    
    	
    	   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. 客户端携带 token 访问资源服务器资源

    2. 资源服务器拦截 token 去认证服务器 check_token

    3. 认证服务器返回 token 过期错误,资源服务器包装错误信息返回给客户端

    4. 客户端根据返回错误信息(响应码),直接调用认证服务器 refresh_token

    5. 认证服务器返回新的 token 给客户端, 然后再次发起 资源调用

    被动请求的缺点是,用户当次请求会失败(返回token失败),对一些业务连贯的操作不是很友好

    主动刷新

    1. 客户端存在计算逻辑,计算下发token 有效期

    2. 若token要过期之前,主动发起刷新

    主动请求的缺点是,客户端占用部分计算资源来处理 token 失效问题

      // 10S检测token 有效期
      refreshToken() {
        this.refreshTime = setInterval(() => {
          const token = getStore({
            name: 'access_token',
            debug: true
          })
          if (this.validatenull(token)) {
            return
          }
          if (this.expires_in <= 1000 && !this.refreshLock) {
            this.refreshLock = true
            this.$store
              .dispatch('RefreshToken')
              .catch(() => {
                clearInterval(this.refreshTime)
              })
            this.refreshLock = false
          }
          this.$store.commit('SET_EXPIRES_IN', this.expires_in - 10)
        }, 10000)
      },
    

    『★★★★★』 基于Spring Boot 2.2、 Spring Cloud Hoxton & Alibaba、 OAuth2 的RBAC 权限管理系统

    项目推荐: Spring Cloud 、Spring Security OAuth2的RBAC权限管理系统 欢迎关注

  • 相关阅读:
    MDK(keil)4.7中文注释乱码解决
    小型功率放大器的设计与制作
    增强输出的电路
    晶体管电路设计学习笔记(一)
    MOSFET学习
    sysTick系统定时器
    C#面向对象 什么是面向对象
    JS基础 超链接、数列的用法,行内元素和块级元素
    JS基础 常用函数、事件、阻止事件冒泡
    JS基础 定时器【setTimeout、setInterval、clearInterval 】
  • 原文地址:https://www.cnblogs.com/leng-leng/p/13064887.html
Copyright © 2020-2023  润新知