PHP使用JWT(JSON Web Tokens)鉴权

时间:2022-06-18作者:klpeng分类:PHP浏览:192评论:1

让我们在基于 PHP 的应用程序中使用 JWT

现在您已经了解了 JWT 是什么,现在是学习如何在 PHP 应用程序中使用它们的时候了。

有很多方法可以实现集成 JWT,但我们将采用以下方法。

除登录和注销页面外,对应用程序的所有请求都需要通过 JWT 进行身份验证。如果用户在没有 JWT 的情况下发出请求,他们将被重定向到登录页面。

用户填写并提交登录表单后,表单将通过 JavaScript 提交到authenticate.php我们应用程序中的登录端点 。然后端点将从请求中提取凭据(用户名和密码)并检查它们是否有效。

如果是,它将生成一个 JWT 并将其发送回客户端。当客户端收到 JWT 时,它将存储它并在以后对应用程序的每个请求中使用它。

对于一个简单的场景,用户只能请求一个资源——一个恰当命名为resource.php. 它不会做太多,只是返回一个字符串,其中包含请求时的当前时间戳。

发出请求时有几种使用 JWT 的方法。在我们的应用程序中,JWT 将在Bearer 授权标头中发送。

如果您不熟悉承载授权,它是一种 HTTP 身份验证形式,其中令牌(例如 JWT)在请求标头中发送。服务器可以检查令牌并确定是否应将访问权限授予令牌的“持有者”。

PHP使用JWT(JSON Web Tokens)鉴权

这是标题的示例:

Authorization: Bearer ab0dde18155a43ee83edba4a4542b973

对于我们的应用程序收到的每个请求,PHP 将尝试从 Bearer 标头中提取令牌。如果存在,则对其进行验证。如果它有效,用户将看到该请求的正常响应。但是,如果 JWT 无效,则不允许用户访问该资源。


请注意,JWT并非旨在替代会话 cookie。

首先,我们需要在我们的系统上安装PHP和Composer 。


在项目的根目录中,运行composer install这将引入 Firebase PHP-JWT,一个简化使用 JWT 的第三方库,以及 laminas -config,旨在简化对应用程序中配置数据的访问


登录表格

使用 HTML 和 JavaScript 的示例登录表单

安装完库后,让我们单步执行authenticate.php. 我们首先进行常规设置,确保 Composer 生成的自动加载器可用。


<?php
declare(strict_types=1);
use Firebase\JWT\JWT;
require_once('../vendor/autoload.php');
<?php
// extract credentials from the request
if ($hasValidCredentials) {
$secretKey  = 'bGS6lzFqvvSQ8ALbOxatm7/Vk7mLQyzqaS34Q4oR1ew=';
$issuedAt   = new DateTimeImmutable();
$expire     = $issuedAt->modify('+6 minutes')->getTimestamp();      // Add 60 seconds
$serverName = "your.domain.name";
$username   = "username";                                           // Retrieved from filtered POST data
$data = [
    'iat'  => $issuedAt->getTimestamp(),         // Issued at: time when the token was generated
    'iss'  => $serverName,                       // Issuer
    'nbf'  => $issuedAt->getTimestamp(),         // Not before
    'exp'  => $expire,                           // Expire
    'userName' => $username,                     // User name
];

准备好有效负载数据后,我们接下来使用 php-jwt 的静态encode方法创建 JWT。

方法:

将数组转换为 JSON

产生标题

签署有效载荷

编码最终的字符串

它需要三个参数:

有效载荷信息

密钥

用于签署令牌的算法

通过调用echo函数的结果,返回生成的令牌:


<?php
    // Encode the array to a JWT string.
    echo JWT::encode(
        $data,
        $secretKey,
        'HS512'
    );
}

使用 JWT

使用 JavaScript 和 JWT 检索资源


现在客户端有了令牌,您可以使用 JavaScript 或您喜欢的任何机制来存储它。这是一个如何使用 vanilla JavaScript 执行此操作的示例。中index.html,表单提交成功后,返回的JWT存储在内存中,登录表单隐藏,显示请求时间戳的按钮:


const store = {};
const loginButton = document.querySelector('#frmLogin');
const btnGetResource = document.querySelector('#btnGetResource');
const form = document.forms[0];
// Inserts the jwt to the store object
store.setJWT = function (data) {
  this.JWT = data;
};
loginButton.addEventListener('submit', async (e) => {
  e.preventDefault();
  const res = await fetch('/authenticate.php', {
    method: 'POST',
    headers: {
      'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
    },
    body: JSON.stringify({
      username: form.inputEmail.value,
      password: form.inputPassword.value
    })
  });
  if (res.status >= 200 && res.status <= 299) {
    const jwt = await res.text();
    store.setJWT(jwt);
    frmLogin.style.display = 'none';
    btnGetResource.style.display = 'block';
  } else {
    // Handle errors
    console.log(res.status, res.statusText);
  }
});
//使用智威汤逊
//单击“获取当前时间戳”按钮时,会向 发出 GET 请求,该请求会resource.php在 Authorization 标头中设置身份验证后收到的 JWT。
btnGetResource.addEventListener('click', async (e) => {
  const res = await fetch('/resource.php', {
    headers: {
      'Authorization': `Bearer ${store.JWT}`
    }
  });
  const timeStamp = await res.text();
  console.log(timeStamp);
});

当我们单击该按钮时,会发出类似于以下的请求:


GET /resource.php HTTP/1.1
Host: yourhost.com
Connection: keep-alive
Accept: */*
X-Requested-With: XMLHttpRequest
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0MjU1ODg4MjEsImp0aSI6IjU0ZjhjMjU1NWQyMjMiLCJpc3MiOiJzcC1qd3Qtc2ltcGxlLXRlY25vbTFrMy5jOS5pbyIsIm5iZiI6MTQyNTU4ODgyMSwiZXhwIjoxNDI1NTkyNDIxLCJkYXRhIjp7InVzZXJJZCI6IjEiLCJ1c2VyTmFtZSI6ImFkbWluIn19.HVYBe9xvPD8qt0wh7rXI8bmRJsQavJ8Qs29yfVbY-A0

假设 JWT 有效,我们会看到资源,然后将响应写入控制台。


验证 JWT

最后,让我们看看如何在 PHP 中验证令牌。和往常一样,我们会包含 Composer 的自动加载器。然后,我们可以选择检查是否使用了正确的请求方法。我跳过了执行此操作的代码,继续关注 JWT 特定的代码:


<?php
chdir(dirname(__DIR__));
require_once('../vendor/autoload.php');
// Do some checking for the request method here, if desired.
//然后,代码将尝试从 Bearer 标头中提取令牌。我已经使用preg_match做到了。如果您不熟悉该函数,它会对字符串执行正则表达式匹配
//这里使用的正则表达式将尝试从 Bearer 标头中提取令牌,并转储其他所有内容。如果未找到,则返回 HTTP 400 错误请求:
if (! preg_match('/Bearer\s(\S+)/', $_SERVER['HTTP_AUTHORIZATION'], $matches)) {
    header('HTTP/1.0 400 Bad Request');
    echo 'Token not found in request';
    exit;
}

请注意,默认情况下,Apache不会将标HTTP_AUTHORIZATION头传递给 PHP。这背后的原因是:


只有通过 HTTPS 进行连接时,基本授权标头才是安全的,否则凭据将通过网络以编码的纯文本(未加密)发送,这是一个巨大的安全问题。


我完全理解这个决定的逻辑。但是,为了避免很多混淆,请将以下内容添加到您的 Apache 配置中。然后代码将按预期运行。如果您使用的是 NGINX,则代码应按预期运行:


RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

接下来,我们尝试提取匹配的 JWT,它将位于$matches变量的第二个元素中。如果它不可用,则没有提取 JWT,并返回 HTTP 400 错误请求:


$jwt = $matches[1];
if (! $jwt) {
    // No token was able to be extracted from the authorization header
    header('HTTP/1.0 400 Bad Request');
    exit;
}
//如果它能够被成功解码,我们就会尝试验证它。我在这里的例子非常简单,因为它只使用发行者,而不是之前和到期时间戳。在实际应用程序中,您可能还会使用许多其他声明。

$secretKey  = 'bGS6lzFqvvSQ8ALbOxatm7/Vk7mLQyzqaS34Q4oR1ew=';
$token = JWT::decode($jwt, $secretKey, ['HS512']);
$now = new DateTimeImmutable();
$serverName = "your.domain.name";

if ($token->iss !== $serverName ||
    $token->nbf > $now->getTimestamp() ||
    $token->exp < $now->getTimestamp())
{
    header('HTTP/1.1 401 Unauthorized');
    exit;
}

如果令牌无效,例如令牌已过期,则会向用户发送 HTTP 401 Unauthorized 标头,并且脚本将退出。

如果解码 JWT 的过程失败,可能是:


提供的段数与前面描述的标准三不匹配。

标头或有效负载不是有效的 JSON 字符串

签名无效,说明数据被篡改!

nbf当当前时间戳小于该时间戳时,该声明在 JWT 中设置为时间戳。

iat当当前时间戳小于该时间戳时,该声明在 JWT 中设置为时间戳。

当exp当前时间戳大于该时间戳时,声明会在 JWT 中设置一个时间戳。

如您所见,JWT 有一组很好的控件,可以将其标记为无效,而无需手动撤销它或根据有效令牌列表检查它。

如果解码和验证过程成功,用户将被允许发出请求,并将被发送适当的响应。

这是对 JSON Web 令牌或 JWT 以及如何在基于 PHP 的应用程序中使用它们的快速介绍。从这里开始,您可以尝试在您的下一个 API 中实现 JWT,也许可以尝试使用其他一些使用非对称密钥(如 RS256)的签名算法,或者将其集成到现有的 OAUTH2 身份验证服务器中作为 API 密钥。


文章原创出自 彭超的博客 | 深入研究web系统架构 https://ligphp.com/


打赏
文章版权声明:除非注明,否则均为彭超的博客原创文章,转载或复制请以超链接形式并注明出处。
相关推荐

  • 评论列表:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

猜你喜欢