应用程序身份验证曾经只依赖于用户名/邮箱和密码等凭据,会话用于维护用户状态直至用户注销。之后,我们开始使用身份验证API。最近,JSON Web Tokens (JWT) 越来越多地用于对服务器请求进行身份验证。
本文将介绍JWT是什么以及如何使用PHP进行基于JWT的用户请求身份验证。
要点
JWT与会话
首先,为什么会话不是那么好呢?主要有三个原因:
JWT
现在,让我们开始学习JWT。JSON Web Token规范(RFC 7519)于2010年12月28日首次发布,最近一次更新是在2015年5月。
JWT比API密钥具有许多优势,包括:
JWT长什么样?
这是一个JWT示例:
<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E</code>
乍一看,这个字符串似乎只是用句点或点字符连接的随机字符组。因此,它似乎与API密钥没有什么不同。但是,如果您仔细观察,就会发现有三个单独的字符串。
第一个字符串是JWT标头。它是一个Base64 URL编码的JSON字符串。它指定了用于生成签名的加密算法以及令牌的类型,该类型始终设置为JWT。该算法可以是对称的或非对称的。
对称算法使用单个密钥来创建和验证令牌。该密钥在JWT的创建者和使用者之间共享。务必确保只有创建者和使用者知道密钥。否则,任何人都可以创建有效的令牌。
非对称算法使用私钥来签署令牌,并使用公钥来验证令牌。当共享密钥不切实际或其他方只需要验证令牌的完整性时,应使用这些算法。
第二个字符串是JWT的有效负载。它也是一个Base64 URL编码的JSON字符串。它包含一些标准字段,称为“声明”。声明有三种类型:注册的、公共的和私有的。
注册的声明是预定义的。您可以在JWT的RFC中找到它们的列表。以下是一些常用的声明:
您可以根据需要定义公共声明。但是,它们不能与注册的声明或已存在的公共声明的声明相同。您可以随意创建私有声明。它们仅供双方使用:生产者和消费者。
JWT的签名是一种加密机制,旨在使用对令牌内容唯一的数字签名来保护JWT的数据。签名确保JWT的完整性,以便使用者可以验证它没有被恶意行为者篡改。
JWT的签名是三件事的组合:
这三者使用JWT标头中指定的算法进行数字签名(未加密)。如果我们解码上面的示例,我们将得到以下JSON字符串:
JWT的标头
<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E</code>
JWT的数据
<code>{ "alg": "HS256", "typ": "JWT" }</code>
您可以自己尝试jwt.io,在那里您可以尝试编码和解码您自己的JWT。
在基于PHP的应用程序中使用JWT
既然您已经了解了JWT是什么,那么现在是时候学习如何在PHP应用程序中使用它们了。在深入研究之前,您可以随意克隆本文的代码,或者按照我们的步骤进行操作。
您可以采用多种方法来集成JWT,但以下是我们将要采用的方法。
除登录和注销页面外,对应用程序的所有请求都需要通过JWT进行身份验证。如果用户在没有JWT的情况下发出请求,他们将被重定向到登录页面。
用户填写并提交登录表单后,表单将通过JavaScript提交到我们应用程序中的登录端点authenticate.php。然后,端点将从请求中提取凭据(用户名和密码),并检查它们是否有效。
如果有效,它将生成一个JWT并将其发送回客户端。当客户端收到JWT时,它将存储JWT并将其用于对应用程序的未来每次请求。
对于一个简单的场景,用户只能请求一个资源——一个恰当命名的PHP文件resource.php。它不会做太多事情,只是返回一个包含请求时当前时间戳的字符串。
在发出请求时,可以使用多种方法来使用JWT。在我们的应用程序中,JWT将发送在Bearer授权标头中。
如果您不熟悉Bearer Authorization,它是一种HTTP身份验证形式,其中令牌(例如JWT)发送在请求标头中。服务器可以检查令牌并确定是否应授予令牌的“持有者”访问权限。
这是一个标头的示例:
<code>{ "iat": 1416929109, "jti": "aa7f8d0a95c", "scopes": [ "repo", "public_repo" ] }</code>
对于我们的应用程序收到的每个请求,PHP都将尝试从Bearer标头中提取令牌。如果存在,则对其进行验证。如果有效,用户将看到该请求的正常响应。但是,如果JWT无效,则不允许用户访问资源。
请注意,JWT并非旨在替代会话cookie。
首先,我们需要在我们的系统上安装PHP和Composer。
在项目的根目录中,运行composer install。这将引入Firebase PHP-JWT,这是一个简化JWT操作的第三方库,以及用于简化应用程序中对配置数据访问的laminas-config。
安装库后,让我们逐步完成authenticate.php中的登录代码。我们首先进行通常的设置,确保Composer生成的自动加载器可用。
<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E</code>
收到表单提交后,凭据将针对数据库或其他一些数据存储进行验证。出于本示例的目的,我们将假设它们有效,并将$hasValidCredentials设置为true。
<code>{ "alg": "HS256", "typ": "JWT" }</code>
接下来,我们初始化一组变量,用于生成JWT。请记住,由于JWT可以在客户端进行检查,因此不要在其中包含任何敏感信息。
另一件值得指出的是,$secretKey不会像这样初始化。您可能会在环境中设置它并提取它,使用phpdotenv等库,或在配置文件中设置它。在本例中,我避免这样做,因为我想关注JWT代码。
切勿泄露它或将其存储在版本控制下!
<code>{ "iat": 1416929109, "jti": "aa7f8d0a95c", "scopes": [ "repo", "public_repo" ] }</code>
准备好有效负载数据后,我们接下来使用php-jwt的静态encode方法来创建JWT。
该方法:
它接受三个参数:
通过对函数结果调用echo,返回生成的令牌:
<code>Authorization: Bearer ab0dde18155a43ee83edba4a4542b973</code>
现在客户端有了令牌,您可以使用JavaScript或您喜欢的任何机制来存储它。以下是如何使用原生JavaScript进行操作的示例。在index.html中,成功提交表单后,收到的JWT将存储在内存中,登录表单将被隐藏,并且将显示请求时间戳的按钮:
<?php declare(strict_types=1); use Firebase\JWT\JWT; require_once('../vendor/autoload.php');
单击“获取当前时间戳”按钮时,将向resource.php发出GET请求,该请求在Authorization标头中设置身份验证后收到的JWT。
<?php // 从请求中提取凭据 if ($hasValidCredentials) {
当我们单击按钮时,将发出类似于以下内容的请求:
$secretKey = 'bGS6lzFqvvSQ8ALbOxatm7/Vk7mLQyzqaS34Q4oR1ew='; $issuedAt = new DateTimeImmutable(); $expire = $issuedAt->modify('+6 minutes')->getTimestamp(); // 添加60秒 $serverName = "your.domain.name"; $username = "username"; // 从过滤后的POST数据中检索 $data = [ 'iat' => $issuedAt->getTimestamp(), // 颁发时间:生成令牌的时间 'iss' => $serverName, // 颁发者 'nbf' => $issuedAt->getTimestamp(), // 不早于 'exp' => $expire, // 过期 'userName' => $username, // 用户名 ];
假设JWT有效,我们将看到资源,之后响应将写入控制台。
最后,让我们看看如何在PHP中验证令牌。与往常一样,我们将包含Composer的自动加载器。然后,我们可以选择检查是否使用了正确的请求方法。为了继续关注JWT特定的代码,我已经跳过了执行此操作的代码:
<?php // 将数组编码为JWT字符串。 echo JWT::encode( $data, $secretKey, 'HS512' ); }
然后,代码将尝试从Bearer标头中提取令牌。我已经使用preg_match这样做了。如果您不熟悉该函数,它将在字符串上执行正则表达式匹配。
我在这里使用的正则表达式将尝试从Bearer标头中提取令牌,并转储其他所有内容。如果找不到,则返回HTTP 400错误请求:
const store = {}; const loginButton = document.querySelector('#frmLogin'); const btnGetResource = document.querySelector('#btnGetResource'); const form = document.forms[0]; // 将jwt插入到store对象中 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 < 300) { const jwt = await res.text(); store.setJWT(jwt); frmLogin.style.display = 'none'; btnGetResource.style.display = 'block'; } else { // 处理错误 console.log(res.status, res.statusText); } });
请注意,默认情况下,Apache不会将HTTP_AUTHORIZATION标头传递给PHP。其背后的原因是:
基本授权标头只有在您的连接通过HTTPS完成时才安全,因为否则凭据将以编码的明文(未加密)形式通过网络发送,这是一个巨大的安全问题。
我完全理解这一决定的逻辑。但是,为了避免很多混淆,请将以下内容添加到您的Apache配置中。然后代码将按预期工作。如果您使用的是NGINX,则代码应该按预期工作:
<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E</code>
接下来,我们尝试提取匹配的JWT,它将位于$matches变量的第二个元素中。如果不可用,则没有提取JWT,并返回HTTP 400错误请求:
<code>{ "alg": "HS256", "typ": "JWT" }</code>
如果我们到达此点,则已提取JWT,因此我们转到解码和验证阶段。为此,我们再次需要我们的密钥,它将从环境或应用程序的配置中提取。然后,我们使用php-jwt的静态decode方法,将JWT、密钥和一组用于解码JWT的算法传递给它。
如果能够成功解码,我们就会尝试验证它。我这里的示例非常简单,因为它只使用颁发者、不早于和过期时间戳。在实际应用程序中,您可能还会使用许多其他声明。
<code>{ "iat": 1416929109, "jti": "aa7f8d0a95c", "scopes": [ "repo", "public_repo" ] }</code>
如果令牌无效,例如令牌已过期,则用户将收到HTTP 401未授权标头,并且脚本将退出。
如果解码和验证过程失败,则可能是:
如您所见,JWT具有一套不错的控制措施,无需手动撤销或针对有效令牌列表进行检查即可将其标记为无效。
如果解码和验证过程成功,则用户将被允许发出请求,并将收到相应的响应。
总结
这是一个关于JSON Web Tokens(或JWT)以及如何在基于PHP的应用程序中使用它们的快速介绍。从这里开始,您可以尝试在下一个API中实现JWT,也许尝试一些使用非对称密钥(如RS256)的其他签名算法,或者将其集成到现有的OAUTH2身份验证服务器中以用作API密钥。
如果您有任何意见或问题,请随时通过Twitter与我们联系。
关于使用JWT进行PHP授权的常见问题解答
您确实可以在PHP中使用JWT来在Web应用程序中建立身份验证和授权机制。要开始使用,您需要使用Composer安装PHP JWT库,例如“firebase/php-jwt”或“lcobucci/jwt”。这些库提供了创建、编码、解码和验证JWT的必要工具。 要创建JWT,您可以使用库来构建包含发行者、受众、过期时间等声明的令牌。创建后,您可以使用密钥签署令牌。接收JWT时,您可以使用库对其进行解码和验证以确保其真实性。如果令牌有效且已验证,您可以访问其声明以确定用户身份和权限,从而允许您在PHP应用程序中实现安全的身份验证和授权。 保护密钥并遵循使用JWT时的安全最佳实践对于防止未经授权访问应用程序资源至关重要。
PHP中的JWT(JSON Web Token)身份验证是一种广泛用于在Web应用程序中实现用户身份验证和授权的方法。它基于令牌的身份验证,能够进行安全且无状态的用户验证。以下是JWT身份验证在PHP中的工作方式: 首先,在用户身份验证或登录期间,服务器会生成一个JWT,这是一个紧凑的、自包含的令牌,其中包含与用户相关的信息(声明),例如用户ID、用户名和角色。这些声明通常是JSON数据。然后,服务器使用密钥签署此令牌以确保其完整性和真实性。 其次,成功进行身份验证后,服务器会将JWT发送回客户端,客户端通常将其存储在安全位置,例如HTTP cookie或本地存储。此令牌作为身份验证的证明。 最后,对于对服务器上受保护资源的后续请求,客户端会在请求标头中附加JWT,通常使用带有“Bearer”方案的“Authorization”标头。服务器收到JWT后,会使用在令牌创建期间使用的相同密钥来验证其签名。此外,它还会检查令牌是否未过期且包含有效的声明。成功验证后,它会从令牌声明中提取用户信息,并实现授权逻辑以确保用户具有访问请求资源所需的权限。这种方法允许在PHP应用程序中进行安全、无状态的身份验证,而无需服务器端会话存储。 虽然PHP中的JWT身份验证提供了许多好处,例如可扩展性和无状态性,但保护密钥并采用令牌管理的最佳实践对于维护应用程序的安全至关重要。使用已建立的PHP JWT库可以简化令牌处理并增强安全性。
PHP中用于身份验证和授权的JWT(JSON Web Tokens)的替代方案是基于会话的身份验证。在基于会话的身份验证中,服务器为每个已验证的用户维护一个会话。当用户登录时,会创建一个唯一的会话标识符(通常存储为客户端浏览器上的会话cookie)。此标识符用于将用户与服务器端会话数据关联起来,包括与用户相关的信息,例如用户ID、用户名和权限。 基于会话的身份验证提供简单性和易于实现,使其适用于各种Web应用程序,尤其是在您不需要JWT的无状态性和可扩展性功能时。它本质上是有状态的,当您需要在用户会话期间管理特定于用户的数据(例如购物车内容或用户首选项)时,这可能是有利的。 但是,使用基于会话的身份验证时,需要考虑一些事项。对于需要无状态身份验证的分布式或基于微服务的架构,它可能不太适用。此外,管理用户会话可能会增加服务器负载,尤其是在用户群较大的应用程序中。最终,在PHP中选择JWT和基于会话的身份验证应与应用程序的特定需求和设计考虑因素相符,确保安全有效的身份验证机制最能满足您的需求。
使用JWT(JSON Web Tokens)保护PHP API涉及一个多步骤过程,该过程结合了身份验证和授权。首先,选择合适的PHP JWT库(如“firebase/php-jwt”或“lcobucci/jwt”)来处理与令牌相关的操作,并使用Composer管理依赖项。 对于身份验证,您需要在PHP应用程序中实现用户身份验证系统。此系统会针对您的数据库或身份验证提供程序验证用户凭据。成功进行身份验证后,您将生成一个JWT令牌,其中包含与用户相关的声明,例如用户的ID、用户名和角色。务必设置过期时间以控制令牌的有效性,然后使用密钥签署令牌。此已签名的令牌作为身份验证响应的一部分发送回客户端。 客户端安全地存储收到的JWT,通常存储在HTTP cookie或本地存储中。对于后续的API请求,客户端会在请求标头中包含JWT,作为带有“Bearer”方案的“Authorization”标头。在您的PHP API中,您可以通过使用创建令牌时使用的相同密钥来验证其签名来验证传入的JWT。此外,您还会检查令牌是否未过期且包含有效的声明。成功验证后,您将从令牌声明中提取用户信息,并实现授权逻辑以确保用户具有访问请求资源所需的权限。 保持密钥安全至关重要,因为它对于签署和验证令牌都至关重要。此密钥的任何泄露都可能导致严重的安全性漏洞。
以上是JWT(JSON Web令牌)的PHP授权的详细内容。更多信息请关注PHP中文网其他相关文章!