我应该如何实现';基于令牌的身份验证';使用PHP和MySQL(不使用OAuth)以安全的方式连接到一组we


How should I implement 'Token Based Authentication' to a set of web APIs in a secure way using PHP and MySQL (without using OAuth)?

我使用Slim框架在PHP中开发了一些web API,移动应用程序(iOS和Android)使用这些API来处理请求并获取所需数据。

最终,在每个API中,我都会将从移动应用程序接收到的请求发送到我网站代码库中的相应功能。然后,相应的函数接受请求和请求参数,处理请求并返回所需的数据。然后API以JSON格式将数据返回到移动应用程序。这是当前的工作流程。

现在,我想让网站资源的可用性(即来自网站代码库和数据的功能)受到用户身份验证。简而言之,我想在这个场景中实现"基于令牌的身份验证"方案。

以下应该是我实现"基于令牌的身份验证"后的流程:

  1. 当用户通过向登录API发送请求中的用户名和密码首次登录系统时,在成功验证该特定用户的用户名和口令组合后,将生成一个安全令牌。这个安全令牌还将与用户名/密码/一些散列值一起存储到一些MySQL数据库表中,以在进一步处理中识别用户。如果验证失败,则不应生成安全令牌,用户也不应登录

  2. 登录成功后,生成的安全令牌将在登录API成功响应中发送回用户。现在,直到用户使用每个后续请求登录,该令牌将被发送到相关的API,并最终被发送到身份验证功能,以验证其有效性。

  3. 如果有任何请求发送了无效令牌,则应发送"请先登录"消息作为响应,并且网站资源不应获得访问权限。

  4. 一旦用户注销,应该从数据库中删除此安全令牌条目,或者对其采取任何适当的操作

由于我职业生涯中第一次从事"基于令牌的身份验证",我可能对上述方法有误。如果我做错了什么,请纠正我的错误。

我发现了以下链接,但我觉得这些链接没有多大用处,因为它们缺乏逐步描述的工作代码示例:

使用Phalcon Micro框架的PHP HMAC Restful API

JWT 的PEAR包

如果你能为我提供完整的代码,包括数据库表的创建、PHP和MySQL之间的连接,然后创建安全令牌,检查当前登录用户的安全令牌的有效性等,这对我来说真的很棒。

此外,如果你能给出上面两个(或两个)选项中任何一个的工作代码示例作为你对这个问题的回答,那也将是非常棒的。如果除了我提供的两个完整的工作代码示例之外,你还有其他选择,欢迎你给出答案。

注意:-请不要建议我使用OAuth身份验证过程。

以下是我自己尝试的代码,但我不知道它是对是错。我的方法是正确的还是错误的?

为了创建一个令牌,我使用这个函数,它将用户的数据作为参数

define('SECRET_KEY', "fakesecretkey");
function createToken($data)
{
    /* Create a part of token using secretKey and other stuff */
    $tokenGeneric = SECRET_KEY.$_SERVER["SERVER_NAME"]; // It can be 'stronger' of course
    /* Encoding token */
    $token = hash('sha256', $tokenGeneric.$data);
    return array('token' => $token, 'userData' => $data);
}

因此,用户可以对自己进行身份验证,并接收一个数组,该数组包含一个令牌(genericPart+his数据,已编码)和hisData未编码:

function auth($login, $password)
{
    // we check user. For instance, it's ok, and we get his ID and his role.
    $userID = 1;
    $userRole = "admin";
    // Concatenating data with TIME
    $data = time()."_".$userID."-".$userRole;
    $token = createToken($data);
    echo json_encode($token);
}

然后用户可以向我发送他的代币+他的未编码数据,以便检查:

define('VALIDITY_TIME', 3600);
function checkToken($receivedToken, $receivedData)
{
    /* Recreate the generic part of token using secretKey and other stuff */
    $tokenGeneric = SECRET_KEY.$_SERVER["SERVER_NAME"];
    // We create a token which should match
    $token = hash('sha256', $tokenGeneric.$receivedData);   
    // We check if token is ok !
    if ($receivedToken != $token)
    {
        echo 'wrong Token !';
        return false;
    }
    list($tokenDate, $userData) = explode("_", $receivedData);
    // here we compare tokenDate with current time using VALIDITY_TIME to check if the token is expired
    // if token expired we return false
    // otherwise it's ok and we return a new token
    return createToken(time()."#".$userData);   
}
$check = checkToken($_GET['token'], $_GET['data']);
if ($check !== false)
    echo json_encode(array("secureData" => "Oo")); // And we add the new token for the next request

我说得对吗?

根据我们的讨论,您可以执行类似于OAuth2.0的操作。我建议实现完整的规范,但由于这是您的应用程序,您可以进行更改。

这是一张来自RFC 6750 的图表

+--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|  (Slim API)   |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+

在slim中,您可能只有三个端点:

POST用户名/密码:

/oauth/v1/authenticate/

返回{ token: foo }

获取{token}是您的唯一代币的位置

/oauth/v1/token/{token}

返回{ username: joe, permissions['page:admin','users:full'], expires: 123456}

DELETE通过{token}

/oauth/v1/token/rerevoke

用CCD_ 3和一个空的正文进行回复。

现在,它是如何工作的:

  • 当他们进行身份验证时,只需返回一个带有token的JSON对象,客户端将其存储在类似cookie的东西中
  • 客户端在RFC第2.1节中标识的标头中将其传递给您的资源服务器(该标头在数据库/nosql/whathing中查找用户权限):

GET/resource HTTP/1.1

 Host: server.example.com
 Authorization: Bearer mF_9.B5f-4.1JqM

您的资源服务器与后端的Slim API联系以确定您的权限。然后服务器决定允许您查看什么。

如果您不喜欢将其作为标头发送,请参阅第2.2节,其中描述了如何在正文中发送它,或者第2.3节,其中将其作为URI查询发送。

这些显然不需要是不同的服务器。你可以随心所欲地实现它。

您可以这样做。

每个用户都有一个私钥(一个随机的字母数字x长字符串),这对每个用户来说都是唯一的。

在每个请求中,您可以使用他们的私钥hash_hmac请求,该私钥将为每个用户生成一个唯一的请求密钥。例如:

 Request:
 GET /v1/products/coffee
 Private key: 
 ww9k6fcysu30sbuzu7ez57z2kzvefyxwosrjcnwo

然后我会生成一个请求密钥,比如;

hash_hmac("sha1", "GET /v1/products/coffee", "ww9k6fcysu30sbuzu7ez57z2kzvefyxwosrjcnwo");

这将仅为该GET请求提供请求密钥:45751dce6ef93655a71e7b82a6179591c346c2c1。它还将确保用于该端点的客户端没有被篡改。

在接收端,您将使用用户私钥执行相同的hash_hmac例程(例如,他们需要在请求中输入用户名,以执行查找以获取私钥),并比较两个结果。

hash_hmac("sha1", $_SERVER['REQUEST_METHOD'] . " " . $_SERVER['REDIRECT_URL'], $user_private_key);

为了增加额外的好处,您将获得POST/PUT主体内容的哈希,并将其附加在请求查询字符串中,并在接收端对其进行身份验证。例如

$bodyhash = md5(implode(",", $_POST));

当用户注销时,停用私钥,并在下次登录时为其分配一个新的私钥。