Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WxOpenComponentServiceImpl高并发情况下有不停刷新access_token的风险 #1300

Closed
ZHAMoonlight opened this issue Nov 26, 2019 · 9 comments

Comments

@ZHAMoonlight
Copy link

`private String get(String uri, String accessTokenKey) throws WxErrorException {
String componentAccessToken = this.getComponentAccessToken(false);
String uriWithComponentAccessToken = uri + (uri.contains("?") ? "&" : "?") + accessTokenKey + "=" + componentAccessToken;

    try {
        return this.getWxOpenService().get(uriWithComponentAccessToken, (String)null);
    } catch (WxErrorException var7) {
        WxError error = var7.getError();
        if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) {
            this.getWxOpenConfigStorage().expireComponentAccessToken();
            if (this.getWxOpenConfigStorage().autoRefreshToken()) {
                return this.get(uri, accessTokenKey);
            }
        }

        if (error.getErrorCode() != 0) {
            throw new WxErrorException(error, var7);
        } else {
            return null;
        }
    }
}`

当access_token过期的时候,如果大量请求同时进入到 this.getWxOpenConfigStorage().expireComponentAccessToken();这行代码,会导致刚刚刷新的access_token被过期,相当于是并发情况下释放了非自己的锁

@ZHAMoonlight
Copy link
Author

同样如果大量线程同时进入了如下去微信获取access_token的方法,那么一样会雪崩
public String getComponentAccessToken(boolean forceRefresh) throws WxErrorException {

@KingZD
Copy link

KingZD commented Nov 29, 2019

我也遇到了,在使用获取authorizer_access_token 接口调用令牌的时候,我对appid加锁,同appid进入的请求串行,不同appid的并行,在使用WxJava之前 进行一个过滤了

@ZHAMoonlight
Copy link
Author

ZHAMoonlight commented Nov 30, 2019

重写了两个方法解决,第一个是从微信获取access_token,另外一个是去expireAccessToken,对每个方法加锁,双重检查

 public String getComponentAccessToken(boolean forceRefresh) throws WxErrorException {
        if (this.getWxOpenConfigStorage().isComponentAccessTokenExpired() || forceRefresh) {
            String requestId = Thread.currentThread().getName();
            try {
                boolean getLock = RedisDistributedLockTool
                    .getDistributedLock(RedisUtilWrapper.getRedisClient(), GET_COMP_AT_KEY,
                        requestId, 3000, 3000);
                if (getLock) {
                    if(!this.getWxOpenConfigStorage().isComponentAccessTokenExpired()){
                        LOGGER.info(requestId +"getLock and accessToken not expire");
                        return this.getWxOpenConfigStorage().getComponentAccessToken();
                    }
                    LOGGER.info(requestId +" getLock and try to get accessToken from weixin");
                    JsonObject jsonObject = new JsonObject();
                    jsonObject.addProperty("component_appid",
                        this.getWxOpenConfigStorage().getComponentAppId());
                    jsonObject.addProperty("component_appsecret",
                        this.getWxOpenConfigStorage().getComponentAppSecret());
                    jsonObject.addProperty("component_verify_ticket",
                        this.getWxOpenConfigStorage().getComponentVerifyTicket());
                    String responseContent = this.getWxOpenService()
                        .post("https://api.weixin.qq.com/cgi-bin/component/api_component_token",
                            jsonObject.toString());
                    WxOpenComponentAccessToken componentAccessToken = WxOpenComponentAccessToken
                        .fromJson(responseContent);
                    this.getWxOpenConfigStorage().updateComponentAccessToken(componentAccessToken);
                }
            } finally {
                RedisDistributedLockTool.releaseDistributedLock(RedisUtilWrapper.getRedisClient(),GET_COMP_AT_KEY,requestId);
            }
        }
        return this.getWxOpenConfigStorage().getComponentAccessToken();
    }
/**
   * 调用前加全局锁,避免太多线程排队在redis.expire这行命令,一直过期刚刚写入的最新accessToken
   * @param currentToken
   */
  @Override
  public void expireComponentAccessToken(String currentToken){
    String requestId = Thread.currentThread().getName();
    try {
      boolean getLock = RedisDistributedLockTool
          .getDistributedLock(redis, EXPIRE_COMP_AT_KEY, requestId, 3000, 3000);
      if (getLock) {
        LOGGER.info(requestId + " get expireComponentAccessToken lock");
        String tokenInRedis = redis.get(this.componentAccessTokenKey);
        if (StringUtils.equals(currentToken, tokenInRedis)) {
          LOGGER.info(requestId + " get lock then expireComponentAccessToken");
          redis.expire(this.componentAccessTokenKey, 0);
        }
      }
    } finally {
      RedisDistributedLockTool.releaseDistributedLock(redis, EXPIRE_COMP_AT_KEY, requestId);
    }
  }

@binarywang
Copy link
Owner

@007gzs 如果有时间的话,能否帮忙看下?

@007gzs
Copy link

007gzs commented Apr 4, 2020

getComponentAccessToken 里应该做下加锁的处理,我看下

@binarywang
Copy link
Owner

楼主提供了加锁实现的解决方案,看起来可行

@binarywang
Copy link
Owner

#1487

@binarywang
Copy link
Owner

3.7.6.B已修复

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants