孙宇的技术专栏 大数据/机器学习

oauth-php-server 部署

2018-05-03

阅读:


oauth-php

利用 bshaffer/oauth2-server-php 部署自己的 oauth 服务。https://github.com/bshaffer/oauth2-server-php

初始化测试项目

mkdir my-oauth2
cd my-oauth2
git clone https://github.com/bshaffer/oauth2-server-php.git -b master

或者在自己项目里:

require bshaffer/oauth2-server-php "^1.10"

或直接设置 composer.json:

require": {
    "php": ">=7.1.0",
    "bshaffer/oauth2-server-php": "^1.10"
},

然后 composer install.如果采用 composer 管理,后面代码里引用的方式就会不大一样。要引入 vendor/autoload.php

创建认证用的库

创建一个数据库,如 oauth2db

CREATE TABLE oauth_clients (
  client_id             VARCHAR(80)   NOT NULL,
  client_secret         VARCHAR(80),
  redirect_uri          VARCHAR(2000),
  grant_types           VARCHAR(80),
  scope                 VARCHAR(4000),
  user_id               VARCHAR(80),
  PRIMARY KEY (client_id)
);

CREATE TABLE oauth_access_tokens (
  access_token         VARCHAR(40)    NOT NULL,
  client_id            VARCHAR(80)    NOT NULL,
  user_id              VARCHAR(80),
  expires              TIMESTAMP      NOT NULL,
  scope                VARCHAR(4000),
  PRIMARY KEY (access_token)
);

CREATE TABLE oauth_authorization_codes (
  authorization_code  VARCHAR(40)     NOT NULL,
  client_id           VARCHAR(80)     NOT NULL,
  user_id             VARCHAR(80),
  redirect_uri        VARCHAR(2000),
  expires             TIMESTAMP       NOT NULL,
  scope               VARCHAR(4000),
  id_token            VARCHAR(1000),
  PRIMARY KEY (authorization_code)
);

CREATE TABLE oauth_refresh_tokens (
  refresh_token       VARCHAR(40)     NOT NULL,
  client_id           VARCHAR(80)     NOT NULL,
  user_id             VARCHAR(80),
  expires             TIMESTAMP       NOT NULL,
  scope               VARCHAR(4000),
  PRIMARY KEY (refresh_token)
);

CREATE TABLE oauth_users (
  username            VARCHAR(80),
  password            VARCHAR(80),
  first_name          VARCHAR(80),
  last_name           VARCHAR(80),
  email               VARCHAR(80),
  email_verified      BOOLEAN,
  scope               VARCHAR(4000),
  PRIMARY KEY (username)
);

CREATE TABLE oauth_scopes (
  scope               VARCHAR(80)     NOT NULL,
  is_default          BOOLEAN,
  PRIMARY KEY (scope)
);

CREATE TABLE oauth_jwt (
  client_id           VARCHAR(80)     NOT NULL,
  subject             VARCHAR(80),
  public_key          VARCHAR(2000)   NOT NULL
);

创建验证服务

创建公用的逻辑文件 server.php

require_once('App/oauth2-server-php/src/OAuth2/Autoloader.php');

/** 配置 */
$dsn = 'mysql:dbname=oauth2db;host=localhost';
$username = 'root';
$password = 'root';

// 错误报告(这毕竟是一个演示!)
ini_set('display_errors', 1);
error_reporting(E_ALL);

OAuth2\Autoloader::register();

// 这里可以用 mysql, redis, mongodb 等多种方式.如果不是用 db,则不用建中间表
$storage = new OAuth2\Storage\Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));

$server = new OAuth2\Server($storage);

$server->addGrantType(new OAuth2\GrantType\ClientCredentials($storage));

$server->addGrantType(new OAuth2\GrantType\AuthorizationCode($storage));

token 验证服务

获取 token

1.创建文件 token.php

require_once __DIR__.'/server.php';

$server->handleTokenRequest(OAuth2\Request::createFromGlobals())->send();

2.添加测试数据

INSERT INTO oauth_clients (client_id, client_secret, redirect_uri) VALUES ("testclient", "testpass", "http://localhost/");

3.测试请求

curl -u testclient:testpass http://localhost:8080/token.php -d 'grant_type=client_credentials'

返回数据:

{"access_token":"3e928644bc91c6522292c2704739735734052eb5","expires_in":3600,"token_type":"Bearer","scope":null}

API验证

1.创建文件 resource.php

require_once __DIR__ . '/server.php';

if (!$server->verifyResourceRequest(OAuth2\Request::createFromGlobals())) {
    $server->getResponse()->send();
    die;
}
echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!'));

2.测试请求

curl http://localhost:8080/resource.php -d 'access_token=3e928644bc91c6522292c2704739735734052eb5'

返回内容:

{"success":true,"message":"You accessed my APIs!"}

表明成功.把 token 改一下可以看到不成功.

三方认证服务

1.创建授权用文件 authorize.php

require_once __DIR__ . '/server.php';

$request = OAuth2\Request::createFromGlobals();
$response = new OAuth2\Response();

if (!$server->validateAuthorizeRequest($request, $response)) {
    $response->send();
    die;
}
if (empty($_POST)) {
    exit('
<form method="post">
  <label>Do You Authorize TestClient?</label><br />
  <input type="submit" name="authorized" value="yes">
  <input type="submit" name="authorized" value="no">
</form>');
}

$is_authorized = ($_POST['authorized'] === 'yes');
$server->handleAuthorizeRequest($request, $response, $is_authorized);
if ($is_authorized) {
    $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=') + 5, 40);
    exit("SUCCESS! Authorization Code: $code");
}
$response->send();

在浏览器中访问: http://localhost:8080/authorize.php?response_type=code&client_id=testclient&state=xyz

可以看到授权是否同意的两个按钮。点击 Yes 后,会生成认证码,如:609c064a18ae1d7780c45380dd2284f07e08628f, 该码会存在我们建好的表 oauth_authorization_codes中。

通过该认证码,我们可以获得 token:

curl -u testclient:testpass http://localhost:8080/token.php -d 'grant_type=authorization_code&code=609c064a18ae1d7780c45380dd2284f07e08628f'

得到 token:

{"access_token":"9fa8b9bb6eef8f0a084df8c1ae5fa008aee59938","expires_in":3600,"token_type":"Bearer","scope":null,"refresh_token":"08a3fa732339b523a31439e36de7c73090b46ed6"}

注意:生成的 code 只有 30 秒的有效期。如果获取 token 太晚了就过期了。

本地用户ID

三方登录成功后,我们可以把该用户记在表中,他们再请求时,我们就知道该 token 是哪个用户了。修改 authorize.php 如:

$userid = 1234;
$server->handleAuthorizeRequest($request, $response, $is_authorized, $userid);

这时候再在浏览器里访问 http://localhost:8080/authorize.php?response_type=code&client_id=testclient&state=xyz并授权。得到授权 code。这时候表 oauth_authorization_codes 除了记 code 外还会把用户 ID 也记下来。

再请求的时候,可以通过如下逻辑得到用户ID。修改 resource.php:

if (!$server->verifyResourceRequest(OAuth2\Request::createFromGlobals())) {
    $server->getResponse()->send();
    die;
}

$token = $server->getAccessTokenData(OAuth2\Request::createFromGlobals());
echo "User ID associated with this token is {$token['user_id']}";

通过上面的 code 请求 token, 然后再请求 resource.php 就可以得到用户ID,如:

User ID associated with this token is 1234

JWT

如果不想本地维护一份token库(不管是 db, redis, mongodb),可以使用JWT形式。它是用加密和解密的方式来处理的。所以不用本地存储。

生成公钥和密钥

openssl genrsa -out privkey.pem 2048
openssl rsa -in privkey.pem -pubout -out pubkey.pem

测试文件 jwt.php

require_once('App/oauth2-server-php/src/OAuth2/Autoloader.php');

// 错误报告(这毕竟是一个演示!)
ini_set('display_errors', 1);
error_reporting(E_ALL);

OAuth2\Autoloader::register();

$publicKey = file_get_contents('pubkey.pem');
$privateKey = file_get_contents('privkey.pem');

$storage = new OAuth2\Storage\Memory(array(
    'keys' => array(
        'public_key' => $publicKey,
        'private_key' => $privateKey,
    ),
    'client_credentials' => array(
        'CLIENT_ID' => array('client_secret' => 'CLIENT_SECRET')
    ),
));
$server = new OAuth2\Server($storage, array(
    'use_jwt_access_tokens' => true,
));

$server->addGrantType(new OAuth2\GrantType\ClientCredentials($storage));

$server->handleTokenRequest(OAuth2\Request::createFromGlobals())->send();

测试访问

curl -i -v http://localhost:8080/jwt.php -u 'CLIENT_ID:CLIENT_SECRET' -d "grant_type=client_credentials"

得到结果:

{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpZCI6IjI1NmExYjkzOGRhZDkzZGM2YmE1ODk0ODE3NmZjY2UzM2VmYjI2MDgiLCJqdGkiOiIyNTZhMWI5MzhkYWQ5M2RjNmJhNTg5NDgxNzZmY2NlMzNlZmIyNjA4IiwiaXNzIjoiIiwiYXVkIjoiQ0xJRU5UX0lEIiwic3ViIjpudWxsLCJleHAiOjE1MjU0MjI2NjEsImlhdCI6MTUyNTQxOTA2MSwidG9rZW5fdHlwZSI6ImJlYXJlciIsInNjb3BlIjpudWxsfQ.aEoSbwg4R2nK4MHXAImgkkP3GDAWnXE5ow8aH17VUP2vYxnj1vhjkuVAiaDXijPzLR1mdk2raPd1U4nLm1MsfeVnb7QfUsedQ_BGFyeAket97RKvbMfN0XGmzgMRcnO4M_tKjWFjYtEyvxphSFxIfAl8KMLMmXp5rGqaZxv_SkNEW3BBp4j66YDt5X6ktRtLkFUpYZAOsnCR7_z3_bz57RHz_C_amQcBRTxV0mQcKKeIVceq3Ny3ezF84GEoLBSk5z7B4VFAOpb3B6lzJLELPZGLfuocO-XzUHaSepfjZRwLAe5ywTQf9gruswWtFEMecCIPZpLZmqQbBpfln4gskA","expires_in":3600,"token_type":"bearer","scope":null}

接口权限控制

如果接口和 oauth 是分开部署的。接口服务这边就只需要用到 pubkey。首先,我们请求 oauth 服务,得到 token,然后带着 token 去请求接口,jwt_resource.php:

require_once('App/oauth2-server-php/src/OAuth2/Autoloader.php');

// 错误报告(这毕竟是一个演示!)
ini_set('display_errors', 1);
error_reporting(E_ALL);

OAuth2\Autoloader::register();

$publicKey = file_get_contents('pubkey.pem');

// no private key necessary
$keyStorage = new OAuth2\Storage\Memory(array('keys' => array(
    'public_key' => $publicKey,
)));

$server = new OAuth2\Server($keyStorage, array(
    'use_jwt_access_tokens' => true,
));

if (!$server->verifyResourceRequest(OAuth2\Request::createFromGlobals())) {
    exit("Failed");
}
echo "Success!";

请求:

curl "http://localhost:8080/jwt_resource.php?access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpZCI6IjExZmQ1MjY3ZDUyNWU2NzlmYTExMjdhZmJhNmE5NmZhNWJkNDIzNGIiLCJqdGkiOiIxMWZkNTI2N2Q1MjVlNjc5ZmExMTI3YWZiYTZhOTZmYTViZDQyMzRiIiwiaXNzIjoiIiwiYXVkIjoiQ0xJRU5UX0lEIiwic3ViIjpudWxsLCJleHAiOjE1MjU0MjMxODUsImlhdCI6MTUyNTQxOTU4NSwidG9rZW5fdHlwZSI6ImJlYXJlciIsInNjb3BlIjpudWxsfQ.ZLNpLTqNIZwCrrxTZvG6O9xQ0jipCRUL2fyXiIaLkTQBL08OrjHV6WYBv9Ibzn6ce75x615DDRzsJU8cDZ6vAcIxlRb9c9OB6glJAiVIl6cZVHzQmnwbWiXssjUZgSEoYeKu0vkkwRLk1G77woFqg9soqG6XQwKbVnEitF7n9bkR0lPk4ue-4rINJ3jsed8PzgVWbeW-7SWmERmRrZA_QWagRZGUTzjGE8CXkhOXKyLgu3VIdpYaor3n3e6doPTQz2aq1XPYDOq7tf3rCxGdWEsEFX1Ia6_L_8oQNETvBYIapMIrc7TYi5MaAHUH4CmWWVakuG7okcFONMvJMj8RPA"

评论

文章