基于thinkphp5.1框架搭建OAuth2.0服务端

OAuth是用于服务端与客户端授权登录的协议,OAuth2.0是OAuth的第二个版本,关于OAuth2.0的基础知识,可以阅读阮一峰的一篇博文,对OAuth2.0的介绍非常详细,只要理解了OAuth2.0的授权过程,在自己网站实现OAuth2.0并不复杂。

本文将讲解如何基于thinkphp5.1的框架实现OAuth2.0的服务端。

1 环境搭建

首先确保你已经搭建好了服务器,并且已经能够正常访问你的服务器。我的环境Xampp+thinkphp5.1.

2 安装OAuth2.0 php包

你页根据OAuth2.0的协议自己去实现代码,但是最快捷最安全最可靠的方法当然是移植第三方OAuth2.0包。OAuth官网提供了很多第三方包,详见网站https://oauth.net/code/, 如下图,因为thinkphp是基于php语言,因此我选择了PHP下第一个。

点击PHP OAuth2 Server会跳入源码下载库,将其下载到电脑即可。

下载后解压,我们只需要将里面/src/OAuth文件夹整个拷贝到tp5/extend/目录下,就可以自动注册对应的命名空间。之后我们就可以使用\OAuth2\...的方式去使用OAuth里面的任何方法。

3 实现OAuth服务端

3.1 创建数据库

由于我们之前下载的OAuth包有用到很多数据表,所以需要按照其要求创建好数据表,创建代码如下:

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)

);

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

);

3.2 创建控制器

需要在tp5/application/index/controller下创建一个控制器,命名为OAuth.php,写入以下代码,控制器就创建完成了。

<?php

namespace app\index\controller;

class OAuth extends \think\Controller

{

}

3.3 实现authorize

OAuth 2.0的运行流程如下图。

所以第一步是实现authorization。

我们在之前创建好的控制器中添加一个函数authorize()

代码如下(注意,dbname需要换成你自己的数据库的名字,下同):

<?php

namespace app\index\controller;

class OAuth extends \think\Controller

{

    public function authorize()

    {

        global $server;

        $dsn      = 'mysql:dbname=XXX;host=127.0.0.1';

        $username = 'root';

        $password = '';

        \OAuth2\Autoloader::register();

        // $dsn is the Data Source Name for your database, for exmaple "mysql:dbname=my_oauth2_db;host=localhost"

        $storage = new \OAuth2\Storage\Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));

        // Pass a storage object or array of storage objects to the OAuth2 server class

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

        // Add the "Client Credentials" grant type (it is the simplest of the grant types)

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

        // Add the "Authorization Code" grant type (this is where the oauth magic happens)

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

        $request = \OAuth2\Request::createFromGlobals();

        $response = new \OAuth2\Response();

        // validate the authorize request

        if (!$server->validateAuthorizeRequest($request, $response)) {

            die;

        }

        // display an authorization form

        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>');

        }

        // print the authorization code if the user has authorized your client

        $is_authorized = ($_POST['authorized'] === 'yes');

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

        if ($is_authorized) {

          // this is only here so that you get to see your code in the cURL request. Otherwise, we'd redirect back to the client

          $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=')+5, 40);

          exit("SUCCESS! Authorization Code: $code");

        }

        $response->send();

    }

}

在tp5/route/route.php中创建相应路由,post方法和get方法都创建

Route::get('authorize', 'OAuth/authorize');

Route::post('authorize', 'OAuth/authorize');

接下来验证创建的authorize是否成功,通过以下链接去访问,在浏览器中输入以下链接,回车后就会显示一个验证表单,当你点击yes按钮后,如果窗口显示一串字符,那么就表示authorize创建成功了,这串字符就是code,接下来需要通过这个code去获取token。

http://localhost/authorize.php?response_type=code&client_id=testclient&state=xyz

3.4 实现token申请方法

在OAuth.php控制器中添加函数token(),代码如下

public function token(){

        global $server;

        $dsn      = 'mysql:dbname=XXX;host=127.0.0.1';

        $username = 'root';

        $password = '';

        \OAuth2\Autoloader::register();

        // $dsn is the Data Source Name for your database, for exmaple "mysql:dbname=my_oauth2_db;host=localhost"

        $storage = new \OAuth2\Storage\Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));

        // Pass a storage object or array of storage objects to the OAuth2 server class

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

        // Add the "Client Credentials" grant type (it is the simplest of the grant types)

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

        // Add the "Authorization Code" grant type (this is where the oauth magic happens)

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


        // Handle a request for an OAuth2.0 Access Token and send the response to the client

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

    }

在tp5/route/route.php中创建相应路由,post方法和get方法都创建

Route::get('token', 'OAuth/token');

Route::post('token', 'OAuth/token');

在测试是否获取token之前,我们需要在oauth_clients表中加一条数据,可执行如下SQL:

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

接下来从CMD运行以下内容,注意:code的值需要换成你上一步生成的code

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

如果成功的话,你应该会得到access token,如下内容

{"access_token":"6f05ad622a3d32a5a81aee5d73a5826adb8cbf63","expires_in":3600,"token_type":"bearer","scope":null}

3.5 实现Resource获取

在OAuth.php控制器中添加函数resource(),代码如下

public function resource()

    {

        // include our OAuth2 Server object

        global $server;

        $dsn      = 'mysql:dbname=XXX;host=127.0.0.1';

        $username = 'root';

        $password = '';

        \OAuth2\Autoloader::register();

        // $dsn is the Data Source Name for your database, for exmaple "mysql:dbname=my_oauth2_db;host=localhost"

        $storage = new \OAuth2\Storage\Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));

        // Pass a storage object or array of storage objects to the OAuth2 server class

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

        // Add the "Client Credentials" grant type (it is the simplest of the grant types)

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

        // Add the "Authorization Code" grant type (this is where the oauth magic happens)

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

        // Handle a request to a resource and authenticate the access token

        if (!$server->verifyResourceRequest(\OAuth2\Request::createFromGlobals())) {

            $server->getResponse()->send();

            die;

        }

        echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!'));

    }

在tp5/route/route.php中创建相应路由,post方法和get方法都创建

Route::get('resource', 'OAuth/resource');

Route::post('resource', 'OAuth/resource');

验证:通过CMD运行以下内容(将access token的值换成上一次获取的access token):

curl http://localhost/resource.php -d 'access_token=YOUR_TOKEN'

如果成功,将会获得以下响应:

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

4 总结

至此,OAuth所有相关的都实现了,整个过程就是客户端去想服务端申请token,然后拿着这个token向服务端获取资源的过程。后续有什么不明白的地方,大家可以在下面评论,我有时间会回答大家。关于oauth2-server-php库的更多详情,大家可以访问http://bshaffer.github.io/oauth2-server-php-docs/。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容