在本文中,我们将探索 Auth0 服务,该服务将身份验证和授权作为服务提供。Auth0 允许您在眨眼之间为您的应用程序设置基本的身份验证和授权功能。
什么是 Auth0?
Auth0 是一种身份验证即服务工具,它使在您的站点中实现与身份验证相关的功能变得轻而易举。如果您已经构建了一个应用程序并且只想外包身份验证和授权功能,那么您应该考虑使用像 Auth0 这样的服务。
让我快速总结一下 Auth0 提供的功能:
单点登录
多因素身份验证
无密码登录
用户管理
以及更多
在本文中,我们将介绍几种可以在 Web 应用程序中实现的单点登录方法,以利用 Auth0 服务提供的身份验证功能。
在本文的前半部分,我们将探讨如何在服务器端 php Web 应用程序中设置基本身份验证功能。在后半部分,我将解释如何通过使用 Auth0 服务设置 oauth 授权来保护您的自定义api 。
服务器端身份验证集成
在本节中,我们将介绍如何使用 Auth0 为服务器端 Web 应用程序快速设置基本身份验证。事实上,Auth0 团队已经提供了一个方便的 GitHub 示例来演示基本示例,因此我们将使用它而不是重新发明轮子。
在继续之前,请确保安装 Composer,因为它将用于使用该composer.json文件安装实际的 Auth0 SDK。此外,如果您想按照本文中的示例进行操作,请继续使用 Auth0 为自己获取一个免费帐户。
设置项目
让我们继续获取示例项目的克隆。
git clone https://github.com/auth0-samples/auth0-php-web-app.git .
继续运行composer install命令以安装依赖项。
cd 00-Starter-Seed composer install
根据composer.json文件,它应该已经安装了 vlucas/phpdotenv和auth0/auth0-php包。
{ "name": "auth0/basic-webapp-sample", "description": "Basic sample for securing a WebApp with Auth0", "require": { "vlucas/phpdotenv": "2.4.0", "auth0/auth0-php": "~5.0" }, "license": "MIT", "authors": [ { "name": "Martin Gontovnikas", "email": "martin@gon.to" }, { "name": "Germán Lena", "email": "german.lena@gmail.com" } ] }
该vlucas/phpdotenv库用于从.env文件初始化环境变量。因此,它允许您将配置与在环境之间更改的代码分开。
另一方面,auth0/auth0-php包将帮助我们在应用程序中设置授权。
接下来,让我们在.env文件中为我们的应用程序设置配置。继续并通过从.env.example文件中复制来创建.env文件。
cp .env.example .env
它包含 Auth0 库将使用的配置值。
AUTH0_CLIENT_ID={CLIENT_ID} AUTH0_domAIN={DOMAIN_NAME} AUTH0_CLIENT_SECRET={CLIENT_SECRET} AUTH0_callback_URL={CALLBACK_URL} AUTH0_AUDIENCE=
您应该能够 在 Auth0 仪表板上的Applications > Default App > Settings下找到大部分设置。请注意,我使用的是系统创建的默认应用程序。当然,如果您愿意,您可以继续创建一个新应用程序。
这AUTH0_CALLBACK_URL是您的应用程序的 URL,Auth0 将在登录和注销后重定向用户。您在此字段中设置的值必须Allowed Callback URLs在 Auth0 仪表板上的应用程序设置下进行配置。
您会发现实现大部分身份验证逻辑的三个主要文件。
index.php:这是根据用户状态显示登录或注销按钮的主页。
login.php:当你点击登录按钮时会启动这个脚本,它会将用户重定向到 Auth0 登录界面进行登录。登录后,他们将被重定向回AUTH0_CALLBACK_URL.
logout.php:当您单击注销按钮时,将启动此脚本,它将在后台将用户重定向到 Auth0 ,将他们注销,然后将他们返回到AUTH0_CALLBACK_URL.
重点项目文件
让我们快速浏览一下启动项目中的每个文件。
登录脚本
我们将从login.php文件开始。
<?php require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/dotenv-loader.php'; use Auth0\SDK\Auth0; $domain = getenv('AUTH0_DOMAIN'); $client_id = getenv('AUTH0_CLIENT_ID'); $client_secret = getenv('AUTH0_CLIENT_SECRET'); $redirect_uri = getenv('AUTH0_CALLBACK_URL'); $audience = getenv('AUTH0_AUDIENCE'); if($audience == ''){ $audience = 'https://' . $domain . '/userinfo'; } $auth0 = new Auth0([ 'domain' => $domain, 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'audience' => $audience, 'scope' => 'openid profile', 'persist_id_token' => true, 'persist_access_token' => true, 'persist_refresh_token' => true, ]); $auth0->login();
一开始,我们已经包含了负责加载 Auth0 和环境变量相关类的自动加载器。之后,我们使用该函数从.env文件初始化配置变量。getenv
接下来,我们实例化 Auth0 对象并调用 login 方法将用户重定向到 Auth0 进行登录。登录后,用户将被重定向回我们的网站。
您可以使用 Facebook、Google 等社交帐户登录,或在登录时创建新帐户。无论哪种情况,Auth0 都会为新用户创建记录。 您可以在 Auth0 仪表板上的Connections > Social下启用不同的社交登录 。此外,您可以在用户链接下的 Auth0 仪表板上查看使用 Auth0 登录的用户列表。
注销脚本
接下来,让我们快速浏览一下logout.php文件。
<?php require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/dotenv-loader.php'; use Auth0\SDK\Auth0; $domain = getenv('AUTH0_DOMAIN'); $client_id = getenv('AUTH0_CLIENT_ID'); $client_secret = getenv('AUTH0_CLIENT_SECRET'); $redirect_uri = getenv('AUTH0_CALLBACK_URL'); $audience = getenv('AUTH0_AUDIENCE'); if($audience == ''){ $audience = 'https://' . $domain . '/userinfo'; } $auth0 = new Auth0([ 'domain' => $domain, 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'audience' => $audience, 'scope' => 'openid profile', 'persist_id_token' => true, 'persist_refresh_token' => true, ]); $auth0->logout(); $return_to = 'https://' . $_SERVER['HTTP_HOST']; $logout_url = sprintf('http://%s/v2/logout?client_id=%s&returnTo=%s', $domain, $client_id, $return_to); header('Location: ' . $logout_url); die();
这与login.php文件的工作方式几乎相同,只是它会在用户注销时被调用。调用该logout方法以使您的应用程序中的用户会话到期。之后,用户将被重定向到 Auth0,以便通知服务有关用户的注销活动。最后,用户将被重定向回您的应用程序。
索引文件
最后,让我们浏览index.php文件,它是我们应用程序的入口点。
<?php // Require composer autoloader require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/dotenv-loader.php'; use Auth0\SDK\Auth0; $domain = getenv('AUTH0_DOMAIN'); $client_id = getenv('AUTH0_CLIENT_ID'); $client_secret = getenv('AUTH0_CLIENT_SECRET'); $redirect_uri = getenv('AUTH0_CALLBACK_URL'); $audience = getenv('AUTH0_AUDIENCE'); if($audience == ''){ $audience = 'https://' . $domain . '/userinfo'; } $auth0 = new Auth0([ 'domain' => $domain, 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'audience' => $audience, 'scope' => 'openid profile', 'persist_id_token' => true, 'persist_access_token' => true, 'persist_refresh_token' => true, ]); $userInfo = $auth0->getUser(); ?> <html> <head> <script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- font awesome from BootstrapCDN --> <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"> <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet"> <link href="public/app.css" rel="stylesheet"> </head> <body class="home"> <div class="container"> <div class="login-page clearfix"> <?php if(!$userInfo): ?> <div class="login-box auth0-box before"> <img src=" cloudup.com/StzWWrY34s.png" /> <h3>Auth0 Example</h3> <p>Zero friction identity infrastructure, built for developers</p> <a class="btn btn-primary btn-lg btn-login btn-block" href="login.php">Sign In</a> </div> <?php else: ?> <div class="logged-in-box auth0-box logged-in"> <h1 id="logo"><img src="//cdn.auth0.com/samples/auth0_logo_final_blue_RGB.png" /></h1> <img class="avatar" src="<?php echo $userInfo['picture'] ?>"/> <h2>Welcome <span class="nickname"><?php echo $userInfo['nickname'] ?></span></h2> <a class="btn btn-warning btn-logout" href="/logout.php">Logout</a> </div> <?php endif ?> </div> </div> </body> </html>
在这里,我们使用了对象的getUser方法$auth0来查看是否有任何活动会话。如果没有活动会话,我们将显示登录链接,该链接将用户带到 login.php并启动登录流程。另一方面, 如果用户已经登录,我们将问候用户并显示注销链接。
这就是服务器端应用程序的基本身份验证流程的实现。
使用oauth2保护您的自定义 API
在本节中,我们将了解如何通过实施 OAuth2 授权代码授予流程来保护您的自定义 API。我希望您熟悉授权码授予的标准流程,因为我们不会对此进行详细介绍。
相反,我们将立即深入到实际的实现中。继续创建一个 包含以下内容的auth_code_grant_example.php文件。
<?php session_start(); if (!isset($_GET['code'])) { // Check if we need to show the "Sign In" link $params = array ( 'audience' => '{AUDIENCE}', 'scope' => 'profile', 'response_type' => 'code', 'client_id' => '{CLIENT_ID}', 'state' => 'SomerandomString', 'redirect_uri' => '{CALLBACK_URL}' ); $_SESSION['oauth2state']=$params['state']; $str_params = ''; foreach($params as $key=>$value) { $str_params .= $key . "=" . urlencode($value) . "&"; } ?> <a href="https://{AUTH0_DOMAIN}/authorize?<?php echo $str_params;?>"> Sign In </a> <?php } elseif (empty($_GET['state']) || (isset($_SESSION['oauth2state']) && $_GET['state'] !== $_SESSION['oauth2state'])) { // If the "state" var is present in the $_GET, let's validate it if (isset($_SESSION['oauth2state'])) { unset($_SESSION['oauth2state']); } exit('Invalid state'); } elseif(isset($_GET['code']) && !empty($_GET['code'])) { // If the auth "code" is present in the $_GET // let's exchange it for the access token $params = array ( 'grant_type' => 'authorization_code', 'client_id' => '{CLIENT_ID}', 'client_secret' => '{CLIENT_SECRET}', 'code' => $_GET['code'], 'redirect_uri' => '{CALLBACK_URL}' ); $str_params = ''; foreach($params as $key=>$value) { $str_params .= $key . "=" . urlencode($value) . "&"; } $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => "https://{AUTH0_DOMAIN}/oauth/token", CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => "post", CURLOPT_POSTFIELDS => $str_params )); $curl_response = curl_exec($curl); $curl_error = curl_error($curl); curl_close($curl); if ($curl_error) { echo "Error in the CURL response:" . $curl_error; } else { $arr_json_data = json_decode($curl_response); if (isset($arr_json_data->access_token)) { $access_token = $arr_json_data->access_token; $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => "http://{YOUR_API_DOMAIN}/demo_api_server.php", CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => "GET", CURLOPT_HTTPHEADER => array( "Authorization: Bearer {$access_token}" ) )); $curl_response = curl_exec($curl); $curl_error = curl_error($curl); curl_close($curl); if ($curl_error) { echo "Error in the CURL response from DEMO API:" . $curl_error; } else { echo "Demo API Response:" . $curl_response; } } else { echo 'Invalid response, no access token was found.'; } } }
让我们看看这段代码是如何工作的!
开始授权流程
首先,我们准备了一个链接,将用户发送到 Auth0 服务器以开始授权流程。
// Check if we need to show the "Sign In" link $params = array ( 'audience' => '{AUDIENCE}', 'scope' => 'profile', 'response_type' => 'code', 'client_id' => '{CLIENT_ID}', 'state' => '{SOME_RANDOM_STRING}', 'redirect_uri' => '{CALLBACK_URL}' ); $_SESSION['oauth2state']=$params['state']; $str_params = ''; foreach($params as $key=>$value) { $str_params .= $key . "=" . urlencode($value) . "&"; } ?> <a href="https://{AUTH0_DOMAIN}/authorize?<?php echo $str_params;?>"> Sign In </a>
请将、和替换 为与您的应用程序对应的值。该 参数应替换为在 Auth0 仪表板上的APIs > {YOUR API APPLICATION} > Settings下找到的Identifier字段的值。{AUDIENCE}{CLIENT_ID}{CALLBACK_URL}{AUDIENCE}
{SOME_RANDOM_STRING}应替换为难以猜测的唯一值。此字符串用于 pr事件 csrf攻击。此外,请确保替换{AUTH0_DOMAIN}为您的域名,正如我们之前讨论的那样。
获取访问令牌
当用户单击登录链接时,他们将被带到 Auth0 服务器进行身份验证。身份验证后,他们将被要求授权应用程序访问您的个人资料。code授权后,用户将使用参数重定向回您的应用程序$_GET。
接下来,我们可以交换它code来获取访问令牌。
// If the auth "code" is present in the $_GET // let's exchange it for the access token $params = array ( 'grant_type' => 'authorization_code', 'client_id' => '{CLIENT_ID}', 'client_secret' => '{CLIENT_SECRET}', 'code' => $_GET['code'], 'redirect_uri' => '{CALLBACK_URL}' ); $str_params = ''; foreach($params as $key=>$value) { $str_params .= $key . "=" . urlencode($value) . "&"; } $curl = curl_init(); $curl_response = curl_exec($curl); curl_setopt_array($curl, array( CURLOPT_URL => "https://{AUTH0_DOMAIN}/oauth/token", CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => $str_params ));
如您所见,只需一次 CURL 调用即可获取访问令牌。
调用您的自定义 API 端点
获得访问令牌后,您可以通过将其包含在标头中来调用自定义 API 端点。
$access_token = $arr_json_data->access_token; $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => "http://{YOUR_API_DOMAIN}/demo_api_server.php", CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => "GET", CURLOPT_HTTPHEADER => array( "Authorization: Bearer {$access_token}" ) )); $curl_response = curl_exec($curl); $curl_error = curl_error($curl); curl_close($curl); if ($curl_error) { echo "Error in the CURL response from DEMO API:" . $curl_error; } else { echo "Demo API Response:" . $curl_response; }
受 Auth0 保护的 API 端点
虚构的 API 资源文件demo_api_server.php可能如下所示:
<?php // Require composer autoloader require __DIR__ . '/vendor/autoload.php'; use Auth0\SDK\JWTVerifier; try { $verifier = new JWTVerifier([ 'supported_algs' => ['{HASHING_ALGORITHM}'], 'valid_audiences' => ['{AUDIENCE}'], 'authorized_iss' => ['{DOMAIN}'] ]); $access_token = getBearerToken(); $token_info = $verifier->verifyAndDecode($access_token); echo json_encode(array('date'=>'API Resource Data!!')); } catch(\Auth0\SDK\Exception\CoreException $e) { throw $e; echo json_encode(array('error'=>$e->getMessage())); } function getAuthorizationHeader() { $headers = null; if (isset($_SERVER['Authorization'])) { $headers = trim($_SERVER["Authorization"]); } else if (isset($_SERVER['HTTP_AUTHORIZATION'])) { //Nginx or fast CGI $headers = trim($_SERVER["HTTP_AUTHORIZATION"]); } elseif (function_exists('apache_request_headers')) { $requestHeaders = apache_request_headers(); // Server-side fix for bug in old android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization) $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders)); //print_r($requestHeaders); if (isset($requestHeaders['Authorization'])) { $headers = trim($requestHeaders['Authorization']); } } return $headers; } function getBearerToken() { $headers = getAuthorizationHeader(); // HEADER: Get the access token from the header if (!empty($headers)) { if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) { return $matches[1]; } } return null; }
让我们快速浏览一下这段代码的重要部分。
验证访问令牌
在授予对受保护资源的访问权限之前,您有责任验证传入的访问令牌。这正是我们在以下代码段中所做的。我们使用 JWTVerifier 实用程序类来验证访问令牌。
try { $verifier = new JWTVerifier([ 'supported_algs' => ['{SIGNING_ALGORITHM}'], 'valid_audiences' => ['{AUDIENCE}'], 'authorized_iss' => ['{DOMAIN}'] ]); $access_token = getBearerToken(); $token_info = $verifier->verifyAndDecode($access_token); echo json_encode(array('date'=>'API Resource Data!!')); } catch(\Auth0\SDK\Exception\CoreException $e) { throw $e; echo json_encode(array('error'=>$e->getMessage())); }
{SIGNING_ALGORITHM}应替换为APIs > {YOUR API APPLICATION} > Settings下的Signing Algorithm字段 的值。
因此,如果您希望在 Auth0 服务中使用 OAuth2 流,这就是您可以保护自定义 API 的方式。
结论
今天,我们通过了 Auth0 服务,它提供了身份验证和授权作为服务。在介绍了 Auth0 服务之后,我们通过几个实际示例来演示如何将它与您的 PHP 应用程序集成。
- 设置项目
- 重点项目文件
- 登录脚本
- 注销脚本
- 索引文件
- 开始授权流程
- 获取访问令牌
- 调用您的自定义 API 端点
- 受 Auth0 保护的 API 端点
- 验证访问令牌