使用信号量修复并发问题

Use a Semaphore to fix a Concurrency Issue

我最近问了一个问题:link 关于在我的 autodesk-forge Web 应用程序中存储刷新令牌的最佳方式。我目前将刷新令牌存储在只有一行和一列的 SQL 数据库中,其中包含刷新令牌。有关令牌的步骤如下:

  1. 当用户登录时,调用 GET 方法从数据库中检索最新的令牌。 Returndata.php 简单地连接到 SQL 数据库并从 table 检索行。获取方法代码:
function getRefreshToken() {
    $.get("returndata.php",
        function(response) {
            var res = JSON.parse(response);
           console.log(response);
            console.log(res);
            refreshToken = res[0].Value;
           // console.log(refreshToken);
            useRefresh();
           // console.log(response.value);
            //var times = response.times;
        }, 
    );  
}
  1. 返回令牌并用于为用户获取访问令牌。
  2. 检索访问令牌时,它会附带一个刷新令牌,该令牌随后将保存在 SQL 数据库中与前一个刷新令牌相同的行中。 Savesettings.php 只需连接到数据库并使用新的刷新令牌更新单行。 POST 存储刷新令牌的方法代码:
function saveRefreshToken() {
    
    $.post("savesettings.php",
    {
       Value: refreshToken,
    },
    function(data, status){
    console.log(data);
  });
    
}

在大多数情况下,此方法工作正常,但也有几次出现错误。我相信这是由于并发问题。如果两个用户在非常相似的时间访问数据库,数据可能会损坏或提供错误的数据。要解决这个问题,我知道我需要使用信号量,以便其他请求等到第一个请求完成后再执行。我如何使用信号量来实现这一目标?任何帮助将不胜感激,因为我非常困在这个问题上。谢谢。干杯!!

编辑

function getToken(){
    $.ajax({
    method: 'POST',
    url: 'https://developer.api.autodesk.com/authentication/v1/gettoken',
    headers: {
        'Content-Type':'application/x-www-form-urlencoded'
    },
    data:'client_id=xxxxxxxxxxxxxxxxxxxx&client_secret=xxxxxxxxxxxxxxx&grant_type=authorization_code&code=' + code + '&redirect_uri=xxxxxxxxxxxxxxxxxxxxxx',
    
    success:function(response){
       // console.log(response);
        access_token = response.access_token;
        console.log(access_token);
        console.log(response);
        refreshToken = response.refresh_token;
        saveRefreshToken()
        
    }
})
}

function useRefresh(){
$.ajax({
    method:'POST',
    url: 'https://developer.api.autodesk.com/authentication/v1/refreshtoken',
    headers: {
        'Content-Type':'application/x-www-form-urlencoded'
    },
    data:'client_id=xxxxxxxxxxxxxxxxxxxxxxxxx&client_secret=xxxxxxxxxxxxxxxxx&grant_type=refresh_token&refresh_token='+refreshToken+'&scope=data:read',
success:function(response){
    console.log(response);
    refreshToken = response.refresh_token;
    //console.log(refreshToken);
    access_token = response.access_token;
    saveRefreshToken();
}
})
}

如果您 运行 遇到 PHP 和 MySQL 的并发问题,信号量不是解决此问题的正确级别。例如,如果您想要 运行 第二台服务器来处理 PHP 请求怎么办。

处理此问题的正确方法是在您的数据库中。如果损坏发生在单个请求期间,可能是您的查询有误,或者您需要使用事务或锁。

Evert 仅在与 MySQL 赛车问题相关时是正确的。在您的情况下,您还调用了另一台服务器来获取访问令牌;这意味着我们需要在刷新时为 read/write 锁定 table。

我写了一个小例子here,它做了最少的事情来解决这个问题。但是,正如 Evert 所说,如果您选择信号量方法,它只能在单个服务器上处理 HTTP 请求。如果你在做负载均衡,我们就需要使用 mysql/redis 锁的方法。我说 Redis,因为在该主题上它比 mysql 更好。

该示例还将向您展示在 Forge 应用程序中获取令牌的正确方法。在上面的代码中,我注意到您正在使用 $.ajax() 代码,这意味着您正在从 HTML 页面(在客户端?)的 OAuth 服务器上进行 HTTP 调用 - 这样做会使您暴露于高安全性问题,您正在泄漏访问令牌和 clientID/secret 密钥。任何人都可以窃取您的 clientID/secret 并在您不知情的情况下开始使用您的帐户。

您应该颁发 2 个访问令牌,一个 public 具有最低权限(这个仅供查看者使用)。具有额外权限的第二个令牌将保留在服务器上,永远不会暴露给任何客户端应用程序。

该示例执行 2 个简单操作:

  • 使用 public 标记在查看器中显示模型
  • 使用内部令牌列出存储桶或文件夹中的模型

该示例还显示了 2 条腿的流程和 3 条腿的代码流程。虽然理论上两者的工作方式应该相同,但有一个重要的区别需要了解。

  1. 您可以随时生成任意数量的 2 条腿令牌。这意味着您将始终获得有效令牌,即使您同时多次使用令牌也是如此。实际上,您不刷新令牌,每次都创建一个新令牌,这样做不会使先前生成的令牌无效。该示例有点懒惰,只要需要就立即获取令牌,而不会打扰服务器处理的 HTTP 请求。

  2. 虽然您也可以生成任意多的三足令牌(代码流),但每次创建新令牌时都需要登录这一事实会限制您的使用。但是,当您创建访问令牌时,您会获得一个刷新令牌,它可以让您在每次需要时更新 access_token。但是,刷新令牌有 3 个注意事项:

    • 刷新令牌的有效期只有 14 天,因此您至少需要每 14 天刷新一次令牌。
    • 当您第一次创建令牌时,您需要提供您可能需要的所有范围。刷新令牌时,您可以降级范围,但永远无法添加新范围。
    • 每当您使用刷新令牌时,这个都会丢失,但您会得到一个新的。最后一个令牌是您接下来需要使用的令牌。

要使用 2legged 版本,只需进入服务器的根目录,例如 http://localhost/

要使用 3legged 版本,请转到 http:///localhost/login 创建一对新的令牌,否则请转到 http:///localhost/www/bim360.html

还有一个小测试示例,可以在您的服务器上同时触发 5 个 HTTP 请求。我限制我的 Apache 实例一次处理 2 个请求,这是为了验证在更新令牌时,所有请求不会相互竞争,这会使您的 token/refresh 令牌不同步。

readme 应该有足够的详细信息来部署示例和浏览代码。