oauth2.0服務端與客戶端搭建

oauth2.0服務端與客戶端搭建 - 推酷php

今天搭建了oauth2.0服務端與客戶端。把搭建的過程記錄一下。具體實現的功能是:client.ruanwenwu.cn的用戶可以經過 server.ruanwenwu.cn的用戶名和密碼登錄client.ruanwenwu.cn。而且登錄 後,client.ruanwenwu.cn的用戶能獲取server.ruanwenwu.cn哪裏的一些資源。html

個人我的博客原文地址: http://www.ruanwenwu.cn/2015/12/oauth-server-and-client-install.html html5

1、oauth2.0的做用mysql

一、搭建第三方登陸平臺(就像你用不少網站支持qq登陸。實際上是qq提供的oauth2.0服務端支持。網站做爲第三方,只要用oauth2.0標準請求服務端,求能獲得用戶信息,並實現登陸)。web

二、公共資源管理。ajax

就 像你(Y)想經過一個打印照片的網站(A)來打印你存放在google(B)的照片。你就要經過這個A來獲取B的照片。B確定要驗證是不是他的用戶Y的請 求,驗證經過才把照片傳遞給A實現打印。問題是Y不想把帳號密碼直接給A。用oauth2.0就是來解決這個問題的:sql

A先 把Y導向B的站點進行受權。受權經過,B向A發送oauth_code。A拿到這個oauth_code,連同A的client_id和 oauth_secrete發送給B,若是數據正確,B就會給A發送oauth_token。有了這個token,A就能夠獲取B的相關資源的。mongodb

基本的流程是這樣。下面是具體的實現過程。thinkphp

2、oauth2.0服務端的搭建。數據庫

oauth2.0的服務端分爲驗證服務器和資源服務器。這兩個服務器也能夠是同一個服務器(只不過把這兩個功能用一個 服務器來完成罷了)。

說明:個人實現都在Thinkphp3.2.3框架下實現。

一、下載thinkphp3.2.3。

二、配置虛擬機server.ruanwenwu.cn

三、在Thinkphp的Home應用下修改配置文件(server.ruanwenwu.cn/Application/Home/Conf/config.php)以下所示:

<?php return array( //'配置項'=>'配置值' //數據庫設置 'DB_TYPE' => 'mysql', 'DB_HOST' => '127.0.0.1',//localhost 'DB_NAME' => 'oauth', 'DB_USER' => 'root', 'DB_PWD' => 'root', 'DB_PORT' => '3306', 'SCOPE_INFO' => array( //這是受權選項。根據你本身的項目來 array( 'name' => '我的信息', 'value' => 'basicinfo' ), array( 'name' => '論壇發帖回帖', 'value' => 'bbsinfo' ), ), 'OAUTH2_CODES_TABLE' =>'oauth_code', //這裏是oauth項目須要用的三個基礎表 'OAUTH2_CLIENTS_TABLE' =>'oauth_client', 'OAUTH2_TOKEN_TABLE' =>'oauth_token', 'SECRETKYE' => 'Mumayi!@#', //下面是一些網站自定義的項目。能夠根據本身的狀況來寫或者不寫 //session 有效期 'SESSION_EXPIRES' => 1200, //key 有效期 'PASS_KEY_EXPIRES' => 86400, //key 有效期 'PHONE_KEY_EXPIRES' => 300, //key 加密 整型 數字 必須爲 int 'PASS_KEY_CALC' => 1314, );

四、建oauth表。

SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for oauth_client -- ---------------------------- DROP TABLE IF EXISTS `oauth_client`; CREATE TABLE `oauth_client` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `client_id` varchar(32) NOT NULL, `client_secret` varchar(32) NOT NULL, `redirect_uri` varchar(200) NOT NULL, `create_time` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for oauth_code -- ---------------------------- DROP TABLE IF EXISTS `oauth_code`; CREATE TABLE `oauth_code` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `client_id` varchar(32) NOT NULL, `user_id` int(11) NOT NULL DEFAULT '1', `code` varchar(40) NOT NULL, `redirect_uri` varchar(200) NOT NULL, `expires` int(11) NOT NULL, `scope` varchar(250) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=57 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for oauth_token -- ---------------------------- DROP TABLE IF EXISTS `oauth_token`; CREATE TABLE `oauth_token` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `client_id` varchar(32) NOT NULL, `user_id` int(11) NOT NULL, `access_token` varchar(40) NOT NULL, `refresh_token` varchar(40) NOT NULL, `expires_in` int(11) NOT NULL, `scope` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;

五、引入oauth2.0服務端類文件。

5-一、在\server.ruanwenwu.cn\ThinkPHP\Library\Vendor\目錄下創建oauth目錄。

5-二、引入oauth2.0服務端PHP腳本。

在剛創建的oauth目錄下引入OAuth2.class.php這個文件基本不用作修改。咱們的操做都在繼承這個類的子類上完成。類的代碼以下:

<?php /** * @mainpage * OAuth 2.0 server in PHP, originally written for * <a href="http://www.opendining.net/"> Open Dining</a>. Supports * <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-10">IETF draft v10</a>. * * Source repo has sample servers implementations for * <a href="http://php.net/manual/en/book.pdo.php"> PHP Data Objects</a> and * <a href="http://www.mongodb.org/">MongoDB</a>. Easily adaptable to other * storage engines. * * PHP Data Objects supports a variety of databases, including MySQL, * Microsoft SQL Server, SQLite, and Oracle, so you can try out the sample * to see how it all works. * * We're expanding the wiki to include more helpful documentation, but for * now, your best bet is to view the oauth.php source - it has lots of * comments. * * @author Tim Ridgely <tim.ridgely@gmail.com> * @author Aaron Parecki <aaron@parecki.com> * @author Edison Wong <hswong3i@pantarei-design.com> * * @see http://code.google.com/p/oauth2-php/ */ /** * The default duration in seconds of the access token lifetime. */ define("OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME", 3600); /** * The default duration in seconds of the authorization code lifetime. */ define("OAUTH2_DEFAULT_AUTH_CODE_LIFETIME", 30); /** * The default duration in seconds of the refresh token lifetime. */ define("OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME", 1209600); /** * @defgroup oauth2_section_2 Client Credentials * @{ * * When interacting with the authorization server, the client identifies * itself using a client identifier and authenticates using a set of * client credentials. This specification provides one mechanism for * authenticating the client using password credentials. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2 */ /** * Regex to filter out the client identifier (described in Section 2 of IETF draft). * * IETF draft does not prescribe a format for these, however I've arbitrarily * chosen alphanumeric strings with hyphens and underscores, 3-32 characters * long. * * Feel free to change. */ define("OAUTH2_CLIENT_ID_REGEXP", "/^[a-z0-9-_]{3,32}$/i"); /** * @} */ /** * @defgroup oauth2_section_3 Obtaining End-User Authorization * @{ * * When the client interacts with an end-user, the end-user MUST first * grant the client authorization to access its protected resources. * Once obtained, the end-user access grant is expressed as an * authorization code which the client uses to obtain an access token. * To obtain an end-user authorization, the client sends the end-user to * the end-user authorization endpoint. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3 */ /** * Denotes "token" authorization response type. */ define("OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN", "token"); /** * Denotes "code" authorization response type. */ define("OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE", "code"); /** * Denotes "code-and-token" authorization response type. */ define("OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN", "code-and-token"); /** * Regex to filter out the authorization response type. */ define("OAUTH2_AUTH_RESPONSE_TYPE_REGEXP", "/^(token|code|code-and-token)$/"); /** * @} */ /** * @defgroup oauth2_section_4 Obtaining an Access Token * @{ * * The client obtains an access token by authenticating with the * authorization server and presenting its access grant (in the form of * an authorization code, resource owner credentials, an assertion, or a * refresh token). * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4 */ /** * Denotes "authorization_code" grant types (for token obtaining). */ define("OAUTH2_GRANT_TYPE_AUTH_CODE", "authorization_code"); /** * Denotes "password" grant types (for token obtaining). */ define("OAUTH2_GRANT_TYPE_USER_CREDENTIALS", "password"); /** * Denotes "assertion" grant types (for token obtaining). */ define("OAUTH2_GRANT_TYPE_ASSERTION", "assertion"); /** * Denotes "refresh_token" grant types (for token obtaining). */ define("OAUTH2_GRANT_TYPE_REFRESH_TOKEN", "refresh_token"); /** * Denotes "none" grant types (for token obtaining). */ define("OAUTH2_GRANT_TYPE_NONE", "none"); /** * Regex to filter out the grant type. */ define("OAUTH2_GRANT_TYPE_REGEXP", "/^(authorization_code|password|assertion|refresh_token|none)$/"); /** * @} */ /** * @defgroup oauth2_section_5 Accessing a Protected Resource * @{ * * Clients access protected resources by presenting an access token to * the resource server. Access tokens act as bearer tokens, where the * token string acts as a shared symmetric secret. This requires * treating the access token with the same care as other secrets (e.g. * end-user passwords). Access tokens SHOULD NOT be sent in the clear * over an insecure channel. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5 */ /** * Used to define the name of the OAuth access token parameter (POST/GET/etc.). * * IETF Draft sections 5.1.2 and 5.1.3 specify that it should be called * "oauth_token" but other implementations use things like "access_token". * * I won't be heartbroken if you change it, but it might be better to adhere * to the spec. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.2 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.3 */ define("OAUTH2_TOKEN_PARAM_NAME", "oauth_token"); /** * @} */ /** * @defgroup oauth2_http_status HTTP status code * @{ */ /** * "Found" HTTP status code. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3 */ define("OAUTH2_HTTP_FOUND", "302 Found"); /** * "Bad Request" HTTP status code. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 */ define("OAUTH2_HTTP_BAD_REQUEST", "400 Bad Request"); /** * "Unauthorized" HTTP status code. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 */ define("OAUTH2_HTTP_UNAUTHORIZED", "401 Unauthorized"); /** * "Forbidden" HTTP status code. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 */ define("OAUTH2_HTTP_FORBIDDEN", "403 Forbidden"); /** * @} */ /** * @defgroup oauth2_error Error handling * @{ * * @todo Extend for i18n. */ /** * The request is missing a required parameter, includes an unsupported * parameter or parameter value, or is otherwise malformed. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 */ define("OAUTH2_ERROR_INVALID_REQUEST", "invalid_request"); /** * The client identifier provided is invalid. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 */ define("OAUTH2_ERROR_INVALID_CLIENT", "invalid_client"); /** * The client is not authorized to use the requested response type. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 */ define("OAUTH2_ERROR_UNAUTHORIZED_CLIENT", "unauthorized_client"); /** * The redirection URI provided does not match a pre-registered value. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 */ define("OAUTH2_ERROR_REDIRECT_URI_MISMATCH", "redirect_uri_mismatch"); /** * The end-user or authorization server denied the request. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 */ define("OAUTH2_ERROR_USER_DENIED", "access_denied"); /** * The requested response type is not supported by the authorization server. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 */ define("OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE", "unsupported_response_type"); /** * The requested scope is invalid, unknown, or malformed. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2.1 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 */ define("OAUTH2_ERROR_INVALID_SCOPE", "invalid_scope"); /** * The provided access grant is invalid, expired, or revoked (e.g. invalid * assertion, expired authorization token, bad end-user password credentials, * or mismatching authorization code and redirection URI). * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 */ define("OAUTH2_ERROR_INVALID_GRANT", "invalid_grant"); /** * The access grant included - its type or another attribute - is not * supported by the authorization server. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3.1 */ define("OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE", "unsupported_grant_type"); /** * The access token provided is invalid. Resource servers SHOULD use this * error code when receiving an expired token which cannot be refreshed to * indicate to the client that a new authorization is necessary. The resource * server MUST respond with the HTTP 401 (Unauthorized) status code. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 */ define("OAUTH2_ERROR_INVALID_TOKEN", "invalid_token"); /** * The access token provided has expired. Resource servers SHOULD only use * this error code when the client is expected to be able to handle the * response and request a new access token using the refresh token issued * with the expired access token. The resource server MUST respond with the * HTTP 401 (Unauthorized) status code. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 */ define("OAUTH2_ERROR_EXPIRED_TOKEN", "expired_token"); /** * The request requires higher privileges than provided by the access token. * The resource server SHOULD respond with the HTTP 403 (Forbidden) status * code and MAY include the "scope" attribute with the scope necessary to * access the protected resource. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2.1 */ define("OAUTH2_ERROR_INSUFFICIENT_SCOPE", "insufficient_scope"); /** * @} */ /** * OAuth2.0 draft v10 server-side implementation. * * @author Originally written by Tim Ridgely <tim.ridgely@gmail.com>. * @author Updated to draft v10 by Aaron Parecki <aaron@parecki.com>. * @author Debug, coding style clean up and documented by Edison Wong <hswong3i@pantarei-design.com>. */ abstract class OAuth2 { /** * Array of persistent variables stored. */ protected $conf = array(); /** * Returns a persistent variable. * * To avoid problems, always use lower case for persistent variable names. * * @param $name * The name of the variable to return. * @param $default * The default value to use if this variable has never been set. * * @return * The value of the variable. */ public function getVariable($name, $default = NULL) { return isset($this->conf[$name]) ? $this->conf[$name] : $default; } /** * Sets a persistent variable. * * To avoid problems, always use lower case for persistent variable names. * * @param $name * The name of the variable to set. * @param $value * The value to set. */ public function setVariable($name, $value) { $this->conf[$name] = $value; return $this; } // Subclasses must implement the following functions. /** * Make sure that the client credentials is valid. * * @param $client_id * Client identifier to be check with. * @param $client_secret * (optional) If a secret is required, check that they've given the right one. * * @return * TRUE if client credentials are valid, and MUST return FALSE if invalid. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2.1 * * @ingroup oauth2_section_2 */ abstract protected function checkClientCredentials($client_id, $client_secret = NULL); /** * Get the registered redirect URI of corresponding client_id. * * OAuth says we should store request URIs for each registered client. * Implement this function to grab the stored URI for a given client id. * * @param $client_id * Client identifier to be check with. * * @return * Registered redirect URI of corresponding client identifier, and MUST * return FALSE if the given client does not exist or is invalid. * * @ingroup oauth2_section_3 */ abstract protected function getRedirectUri($client_id); /** * Look up the supplied oauth_token from storage. * * We need to retrieve access token data as we create and verify tokens. * * @param $oauth_token * oauth_token to be check with. * * @return * An associative array as below, and return NULL if the supplied oauth_token * is invalid: * - client_id: Stored client identifier. * - expires: Stored expiration in unix timestamp. * - scope: (optional) Stored scope values in space-separated string. * * @ingroup oauth2_section_5 */ abstract protected function getAccessToken($oauth_token); /** * Store the supplied access token values to storage. * * We need to store access token data as we create and verify tokens. * * @param $oauth_token * oauth_token to be stored. * @param $client_id * Client identifier to be stored. * @param $expires * Expiration to be stored. * @param $user_id * User ID * @param $scope * (optional) Scopes to be stored in space-separated string. * * @ingroup oauth2_section_4 */ abstract protected function setAccessToken($oauth_token, $client_id, $expires, $user_id, $scope=NULL, $refresh_token=''); // Stuff that should get overridden by subclasses. // // I don't want to make these abstract, because then subclasses would have // to implement all of them, which is too much work. // // So they're just stubs. Override the ones you need. /** * Return supported grant types. * * You should override this function with something, or else your OAuth * provider won't support any grant types! * * @return * A list as below. If you support all grant types, then you'd do: * @code * return array( * OAUTH2_GRANT_TYPE_AUTH_CODE, * OAUTH2_GRANT_TYPE_USER_CREDENTIALS, * OAUTH2_GRANT_TYPE_ASSERTION, * OAUTH2_GRANT_TYPE_REFRESH_TOKEN, * OAUTH2_GRANT_TYPE_NONE, * ); * @endcode * * @ingroup oauth2_section_4 */ protected function getSupportedGrantTypes() { return array(); } /** * Return supported authorization response types. * * You should override this function with your supported response types. * * @return * A list as below. If you support all authorization response types, * then you'd do: * @code * return array( * OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE, * OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN, * OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN, * ); * @endcode * * @ingroup oauth2_section_3 */ protected function getSupportedAuthResponseTypes() { return array( OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE, OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN, OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN ); } /** * Return supported scopes. * * If you want to support scope use, then have this function return a list * of all acceptable scopes (used to throw the invalid-scope error). * * @return * A list as below, for example: * @code * return array( * 'my-friends', * 'photos', * 'whatever-else', * ); * @endcode * * @ingroup oauth2_section_3 */ protected function getSupportedScopes() { return array(); } /** * Check restricted authorization response types of corresponding Client * identifier. * * If you want to restrict clients to certain authorization response types, * override this function. * * @param $client_id * Client identifier to be check with. * @param $response_type * Authorization response type to be check with, would be one of the * values contained in OAUTH2_AUTH_RESPONSE_TYPE_REGEXP. * * @return * TRUE if the authorization response type is supported by this * client identifier, and FALSE if it isn't. * * @ingroup oauth2_section_3 */ protected function checkRestrictedAuthResponseType($client_id, $response_type) { return TRUE; } /** * Check restricted grant types of corresponding client identifier. * * If you want to restrict clients to certain grant types, override this * function. * * @param $client_id * Client identifier to be check with. * @param $grant_type * Grant type to be check with, would be one of the values contained in * OAUTH2_GRANT_TYPE_REGEXP. * * @return * TRUE if the grant type is supported by this client identifier, and * FALSE if it isn't. * * @ingroup oauth2_section_4 */ protected function checkRestrictedGrantType($client_id, $grant_type) { return TRUE; } // Functions that help grant access tokens for various grant types. /** * Fetch authorization code data (probably the most common grant type). * * Retrieve the stored data for the given authorization code. * * Required for OAUTH2_GRANT_TYPE_AUTH_CODE. * * @param $code * Authorization code to be check with. * * @return * An associative array as below, and NULL if the code is invalid: * - client_id: Stored client identifier. * - redirect_uri: Stored redirect URI. * - expires: Stored expiration in unix timestamp. * - scope: (optional) Stored scope values in space-separated string. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.1 * * @ingroup oauth2_section_4 */ protected function getAuthCode($code) { return NULL; } /** * Take the provided authorization code values and store them somewhere. * * This function should be the storage counterpart to getAuthCode(). * * If storage fails for some reason, we're not currently checking for * any sort of success/failure, so you should bail out of the script * and provide a descriptive fail message. * * Required for OAUTH2_GRANT_TYPE_AUTH_CODE. * * @param $code * Authorization code to be stored. * @param $client_id * Client identifier to be stored. * @param $redirect_uri * Redirect URI to be stored. * @param $expires * Expiration to be stored. * @param $scope * (optional) Scopes to be stored in space-separated string. * * @ingroup oauth2_section_4 */ protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $userId, $scope = NULL) { } /** * Grant access tokens for basic user credentials. * * Check the supplied username and password for validity. * * You can also use the $client_id param to do any checks required based * on a client, if you need that. * * Required for OAUTH2_GRANT_TYPE_USER_CREDENTIALS. * * @param $client_id * Client identifier to be check with. * @param $username * Username to be check with. * @param $password * Password to be check with. * * @return * TRUE if the username and password are valid, and FALSE if it isn't. * Moreover, if the username and password are valid, and you want to * verify the scope of a user's access, return an associative array * with the scope values as below. We'll check the scope you provide * against the requested scope before providing an access token: * @code * return array( * 'scope' => <stored scope values (space-separated string)>, * ); * @endcode * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.2 * * @ingroup oauth2_section_4 */ protected function checkUserCredentials($client_id, $username, $password) { return FALSE; } /** * Grant access tokens for assertions. * * Check the supplied assertion for validity. * * You can also use the $client_id param to do any checks required based * on a client, if you need that. * * Required for OAUTH2_GRANT_TYPE_ASSERTION. * * @param $client_id * Client identifier to be check with. * @param $assertion_type * The format of the assertion as defined by the authorization server. * @param $assertion * The assertion. * * @return * TRUE if the assertion is valid, and FALSE if it isn't. Moreover, if * the assertion is valid, and you want to verify the scope of an access * request, return an associative array with the scope values as below. * We'll check the scope you provide against the requested scope before * providing an access token: * @code * return array( * 'scope' => <stored scope values (space-separated string)>, * ); * @endcode * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3 * * @ingroup oauth2_section_4 */ protected function checkAssertion($client_id, $assertion_type, $assertion) { return FALSE; } /** * Grant refresh access tokens. * * Retrieve the stored data for the given refresh token. * * Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN. * * @param $refresh_token * Refresh token to be check with. * * @return * An associative array as below, and NULL if the refresh_token is * invalid: * - client_id: Stored client identifier. * - expires: Stored expiration unix timestamp. * - scope: (optional) Stored scope values in space-separated string. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.4 * * @ingroup oauth2_section_4 */ protected function getRefreshToken($refresh_token) { return NULL; } /** * Take the provided refresh token values and store them somewhere. * * This function should be the storage counterpart to getRefreshToken(). * * If storage fails for some reason, we're not currently checking for * any sort of success/failure, so you should bail out of the script * and provide a descriptive fail message. * * Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN. * * @param $refresh_token * Refresh token to be stored. * @param $client_id * Client identifier to be stored. * @param $expires * expires to be stored. * @param $scope * (optional) Scopes to be stored in space-separated string. * * @ingroup oauth2_section_4 */ protected function setRefreshToken($refresh_token, $client_id, $expires, $scope = NULL) { return; } /** * Expire a used refresh token. * * This is not explicitly required in the spec, but is almost implied. * After granting a new refresh token, the old one is no longer useful and * so should be forcibly expired in the data store so it can't be used again. * * If storage fails for some reason, we're not currently checking for * any sort of success/failure, so you should bail out of the script * and provide a descriptive fail message. * * @param $refresh_token * Refresh token to be expirse. * * @ingroup oauth2_section_4 */ protected function unsetRefreshToken($refresh_token) { return; } /** * Grant access tokens for the "none" grant type. * * Not really described in the IETF Draft, so I just left a method * stub... Do whatever you want! * * Required for OAUTH2_GRANT_TYPE_NONE. * * @ingroup oauth2_section_4 */ protected function checkNoneAccess($client_id) { return FALSE; } /** * Get default authentication realm for WWW-Authenticate header. * * Change this to whatever authentication realm you want to send in a * WWW-Authenticate header. * * @return * A string that you want to send in a WWW-Authenticate header. * * @ingroup oauth2_error */ protected function getDefaultAuthenticationRealm() { return "Service"; } // End stuff that should get overridden. /** * Creates an OAuth2.0 server-side instance. * * @param $config * An associative array as below: * - access_token_lifetime: (optional) The lifetime of access token in * seconds. * - auth_code_lifetime: (optional) The lifetime of authorization code in * seconds. * - refresh_token_lifetime: (optional) The lifetime of refresh token in * seconds. * - display_error: (optional) Whether to show verbose error messages in * the response. */ public function __construct($config = array()) { foreach ($config as $name => $value) { $this->setVariable($name, $value); } } // Resource protecting (Section 5). /** * Check that a valid access token has been provided. * * The scope parameter defines any required scope that the token must have. * If a scope param is provided and the token does not have the required * scope, we bounce the request. * * Some implementations may choose to return a subset of the protected * resource (i.e. "public" data) if the user has not provided an access * token or if the access token is invalid or expired. * * The IETF spec says that we should send a 401 Unauthorized header and * bail immediately so that's what the defaults are set to. * * @param $scope * A space-separated string of required scope(s), if you want to check * for scope. * @param $exit_not_present * If TRUE and no access token is provided, send a 401 header and exit, * otherwise return FALSE. * @param $exit_invalid * If TRUE and the implementation of getAccessToken() returns NULL, exit, * otherwise return FALSE. * @param $exit_expired * If TRUE and the access token has expired, exit, otherwise return FALSE. * @param $exit_scope * If TRUE the access token does not have the required scope(s), exit, * otherwise return FALSE. * @param $realm * If you want to specify a particular realm for the WWW-Authenticate * header, supply it here. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5 * * @ingroup oauth2_section_5 */ public function verifyAccessToken($scope = NULL, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = NULL) { $token_param = $this->getAccessTokenParams(); if ($token_param === FALSE) // Access token was not provided return $exit_not_present ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_BAD_REQUEST, $realm, OAUTH2_ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', NULL, $scope) : FALSE; // Get the stored token data (from the implementing subclass) $token = $this->getAccessToken($token_param); if ($token === NULL) return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', NULL, $scope) : FALSE; // Check token expiration (I'm leaving this check separated, later we'll fill in better error messages) if (isset($token["expires"]) && time() > $token["expires"]) return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', NULL, $scope) : FALSE; // Check scope, if provided // If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->checkScope($scope, $token["scope"]))) return $exit_scope ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_FORBIDDEN, $realm, OAUTH2_ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', NULL, $scope) : FALSE; return TRUE; } /** * Check if everything in required scope is contained in available scope. * * @param $required_scope * Required scope to be check with. * @param $available_scope * Available scope to be compare with. * * @return * TRUE if everything in required scope is contained in available scope, * and False if it isn't. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5 * * @ingroup oauth2_section_5 */ private function checkScope($required_scope, $available_scope) { // The required scope should match or be a subset of the available scope if (!is_array($required_scope)) $required_scope = explode(" ", $required_scope); if (!is_array($available_scope)) $available_scope = explode(" ", $available_scope); return (count(array_diff($required_scope, $available_scope)) == 0); } /** * Pulls the access token out of the HTTP request. * * Either from the Authorization header or GET/POST/etc. * * @return * Access token value if present, and FALSE if it isn't. * * @todo Support PUT or DELETE. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1 * * @ingroup oauth2_section_5 */ private function getAccessTokenParams() { $auth_header = $this->getAuthorizationHeader(); if ($auth_header !== FALSE) { // Make sure only the auth header is set if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME]) || isset($_POST[OAUTH2_TOKEN_PARAM_NAME])) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth token found in GET or POST when token present in header'); $auth_header = trim($auth_header); // Make sure it's Token authorization if (strcmp(substr($auth_header, 0, 5), "OAuth ") !== 0) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Auth header found that doesn\'t start with "OAuth"'); // Parse the rest of the header if (preg_match('/\s*OAuth\s*="(.+)"/', substr($auth_header, 5), $matches) == 0 || count($matches) < 2) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Malformed auth header'); return $matches[1]; } if (isset($_GET[OAUTH2_TOKEN_PARAM_NAME])) { if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME])) // Both GET and POST are not allowed $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Only send the token in GET or POST, not both'); return $_GET[OAUTH2_TOKEN_PARAM_NAME]; } if (isset($_POST[OAUTH2_TOKEN_PARAM_NAME])) return $_POST[OAUTH2_TOKEN_PARAM_NAME]; return FALSE; } // Access token granting (Section 4). /** * Grant or deny a requested access token. * * This would be called from the "/token" endpoint as defined in the spec. * Obviously, you can call your endpoint whatever you want. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4 * * @ingroup oauth2_section_4 */ public function grantAccessToken() { $filters = array( "grant_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_GRANT_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), "scope" => array("flags" => FILTER_REQUIRE_SCALAR), "code" => array("flags" => FILTER_REQUIRE_SCALAR), "redirect_uri" => array("filter" => FILTER_SANITIZE_URL), "username" => array("flags" => FILTER_REQUIRE_SCALAR), "password" => array("flags" => FILTER_REQUIRE_SCALAR), "assertion_type" => array("flags" => FILTER_REQUIRE_SCALAR), "assertion" => array("flags" => FILTER_REQUIRE_SCALAR), "refresh_token" => array("flags" => FILTER_REQUIRE_SCALAR), ); $input = filter_input_array(INPUT_POST, $filters); // Grant Type must be specified. if (!$input["grant_type"]) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); // Make sure we've implemented the requested grant type if (!in_array($input["grant_type"], $this->getSupportedGrantTypes())) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE); // Authorize the client $client = $this->getClientCredentials(); if ($this->checkClientCredentials($client[0], $client[1]) === FALSE) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT); if (!$this->checkRestrictedGrantType($client[0], $input["grant_type"])) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT); // Do the granting switch ($input["grant_type"]) { case OAUTH2_GRANT_TYPE_AUTH_CODE: if (!$input["code"] || !$input["redirect_uri"]) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); $stored = $this->getAuthCode($input["code"]); // Ensure that the input uri starts with the stored uri if ($stored === NULL || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"]) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); if ($stored["expires"] < time()) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN); break; case OAUTH2_GRANT_TYPE_USER_CREDENTIALS: if (!$input["username"] || !$input["password"]) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required'); $stored = $this->checkUserCredentials($client[0], $input["username"], $input["password"]); if ($stored === FALSE) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); break; case OAUTH2_GRANT_TYPE_ASSERTION: if (!$input["assertion_type"] || !$input["assertion"]) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); $stored = $this->checkAssertion($client[0], $input["assertion_type"], $input["assertion"]); if ($stored === FALSE) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); break; case OAUTH2_GRANT_TYPE_REFRESH_TOKEN: if (!$input["refresh_token"]) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found'); $stored = $this->getRefreshToken($input["refresh_token"]); if ($stored === NULL || $client[0] != $stored["client_id"]) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); if ($stored["expires"] < time()) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN); // store the refresh token locally so we can delete it when a new refresh token is generated $this->setVariable('_old_refresh_token', $stored["token"]); break; case OAUTH2_GRANT_TYPE_NONE: $stored = $this->checkNoneAccess($client[0]); if ($stored === FALSE) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); } // Check scope, if provided if ($input["scope"] && (!is_array($stored) || !isset($stored["scope"]) || !$this->checkScope($input["scope"], $stored["scope"]))) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_SCOPE); if (!$input["scope"]) $input["scope"] = NULL; $token = $this->createAccessToken($client[0], $input["scope"], $stored["user_id"]); //TOKEN 爲 數組 (包括 TOKEN 值, 有效時間, 權限) // $this->sendJsonHeaders(); echo json_encode($token); } /** * Internal function used to get the client credentials from HTTP basic * auth or POST data. * * @return * A list containing the client identifier and password, for example * @code * return array( * $_POST["client_id"], * $_POST["client_secret"], * ); * @endcode * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-2 * * @ingroup oauth2_section_2 */ protected function getClientCredentials() { if (isset($_SERVER["PHP_AUTH_USER"]) && $_POST && isset($_POST["client_id"])) $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT); // Try basic auth if (isset($_SERVER["PHP_AUTH_USER"])) return array($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]); // Try POST if ($_POST && isset($_POST["client_id"])) { if (isset($_POST["client_secret"])) return array($_POST["client_id"], $_POST["client_secret"]); return array($_POST["client_id"], NULL); } // No credentials were specified $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT); } // End-user/client Authorization (Section 3 of IETF Draft). /** * Pull the authorization request data out of the HTTP request. * * @return * The authorization parameters so the authorization server can prompt * the user for approval if valid. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3 * * @ingroup oauth2_section_3 */ public function getAuthorizeParams() { $filters = array( "client_id" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_CLIENT_ID_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), "response_type" => array("filter" => FILTER_VALIDATE_REGEXP, "options" => array("regexp" => OAUTH2_AUTH_RESPONSE_TYPE_REGEXP), "flags" => FILTER_REQUIRE_SCALAR), "redirect_uri" => array("filter" => FILTER_SANITIZE_URL), "state" => array("flags" => FILTER_REQUIRE_SCALAR), "scope" => array("flags" => FILTER_REQUIRE_SCALAR), ); $input = filter_input_array(INPUT_GET, $filters); // Make sure a valid client id was supplied if (!$input["client_id"]) { if ($input["redirect_uri"]) $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]); $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_CLIENT); // We don't have a good URI to use } // redirect_uri is not required if already established via other channels // check an existing redirect URI against the one supplied $redirect_uri = $this->getRedirectUri($input["client_id"]); // At least one of: existing redirect URI or input redirect URI must be specified if (!$redirect_uri && !$input["redirect_uri"]) $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST); // getRedirectUri() should return FALSE if the given client ID is invalid // this probably saves us from making a separate db call, and simplifies the method set if ($redirect_uri === FALSE) $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]); // If there's an existing uri and one from input, verify that they match if ($redirect_uri && $input["redirect_uri"]) { // Ensure that the input uri starts with the stored uri if (strcasecmp(substr($input["redirect_uri"], 0, strlen($redirect_uri)), $redirect_uri) !== 0) $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_REDIRECT_URI_MISMATCH, NULL, NULL, $input["state"]); } elseif ($redirect_uri) { // They did not provide a uri from input, so use the stored one $input["redirect_uri"] = $redirect_uri; } // type and client_id are required if (!$input["response_type"]) $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', NULL, $input["state"]); // Check requested auth response type against the list of supported types if (array_search($input["response_type"], $this->getSupportedAuthResponseTypes()) === FALSE) $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE, NULL, NULL, $input["state"]); // Restrict clients to certain authorization response types if ($this->checkRestrictedAuthResponseType($input["client_id"], $input["response_type"]) === FALSE) $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNAUTHORIZED_CLIENT, NULL, NULL, $input["state"]); // Validate that the requested scope is supported if ($input["scope"] && !$this->checkScope($input["scope"], $this->getSupportedScopes())) $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, NULL, NULL, $input["state"]); return $input; } /** * Redirect the user appropriately after approval. * * After the user has approved or denied the access request the * authorization server should call this function to redirect the user * appropriately. * * @param $is_authorized * TRUE or FALSE depending on whether the user authorized the access. * @param $params * An associative array as below: * - response_type: The requested response: an access token, an * authorization code, or both. * - client_id: The client identifier as described in Section 2. * - redirect_uri: An absolute URI to which the authorization server * will redirect the user-agent to when the end-user authorization * step is completed. * - scope: (optional) The scope of the access request expressed as a * list of space-delimited strings. * - state: (optional) An opaque value used by the client to maintain * state between the request and callback. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3 * * @ingroup oauth2_section_3 */ public function finishClientAuthorization($is_authorized, $params = array()) { $params += array( // 'scope' => NULL, 'state' => NULL, ); extract($params); if ($state !== NULL) $result["query"]["state"] = $state; if ($is_authorized === FALSE) { $result["query"]["error"] = OAUTH2_ERROR_USER_DENIED; } else { if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) $result["query"]["code"] = $this->createAuthCode($client_id, $redirect_uri, $user_id, $scope); if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) $result["fragment"] = $this->createAccessToken($client_id, $scope, $user_id); } $result['display'] = !empty($display) ? $display : ''; $this->doRedirectUriCallback($redirect_uri, $result); } // Other/utility functions. /** * Redirect the user agent. * * Handle both redirect for success or error response. * * @param $redirect_uri * An absolute URI to which the authorization server will redirect * the user-agent to when the end-user authorization step is completed. * @param $params * Parameters to be pass though buildUri(). * * @ingroup oauth2_section_3 */ private function doRedirectUriCallback($redirect_uri, $params) { header("HTTP/1.1 ". OAUTH2_HTTP_FOUND); if($params['display'] == 'frame') { echo '<script>window.top.location.href="'.$this->buildUri($redirect_uri, $params).'";</script>'; } else { header("Location: " . $this->buildUri($redirect_uri, $params)); } exit; } /** * Build the absolute URI based on supplied URI and parameters. * * @param $uri * An absolute URI. * @param $params * Parameters to be append as GET. * * @return * An absolute URI with supplied parameters. * * @ingroup oauth2_section_3 */ private function buildUri($uri, $params) { session_start(); $parse_url = parse_url($uri); // Add our params to the parsed uri foreach ($params as $k => $v) { if (isset($parse_url[$k])) $parse_url[$k] .= "&" . http_build_query($v); else $parse_url[$k] = http_build_query($v); } // Put humpty dumpty back together $return = ((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "") . ((isset($parse_url["user"])) ? $parse_url["user"] . ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "") . ((isset($parse_url["host"])) ? $parse_url["host"] : "") . ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "") . ((isset($parse_url["path"])) ? $parse_url["path"] : "") . ((isset($parse_url["query"])) ? "?" . $parse_url["query"] : "") . ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "") . "&ssid=".base64_encode('2egc83'.session_id()); return $return; } /** * Handle the creation of access token, also issue refresh token if support. * * This belongs in a separate factory, but to keep it simple, I'm just * keeping it here. * * @param $client_id * Client identifier related to the access token. * @param $scope * (optional) Scopes to be stored in space-separated string. * * @ingroup oauth2_section_4 */ protected function createAccessToken($client_id, $scope = NULL, $user_id = 10) { $token = array( "access_token" => $this->genAccessToken(), "expires_in" => $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), "user_id" => $user_id, "scope" => $scope ); $refresh_token = ''; // Issue a refresh token also, if we support them if (in_array(OAUTH2_GRANT_TYPE_REFRESH_TOKEN, $this->getSupportedGrantTypes())) { $refresh_token = $this->genAccessToken(); // $this->setRefreshToken($token["refresh_token"], $client_id, time() + $this->getVariable('refresh_token_lifetime', OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME), $user_id, $scope); $token["refresh_token"] = $refresh_token; // If we've granted a new refresh token, expire the old one if ($this->getVariable('_old_refresh_token')) $this->unsetRefreshToken($this->getVariable('_old_refresh_token')); } $access_token = $token['access_token']; $this->setAccessToken($access_token, $client_id, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $token['user_id'], $scope, $refresh_token); return $token; } /** * Handle the creation of auth code. * * This belongs in a separate factory, but to keep it simple, I'm just * keeping it here. * * @param $client_id * Client identifier related to the access token. * @param $redirect_uri * An absolute URI to which the authorization server will redirect the * user-agent to when the end-user authorization step is completed. * @param $scope * (optional) Scopes to be stored in space-separated string. * * @ingroup oauth2_section_3 */ private function createAuthCode($client_id, $redirect_uri, $userId, $scope = NULL) { $code = $this->genAuthCode(); $this->setAuthCode($code, $client_id, $redirect_uri, time() + $this->getVariable('auth_code_lifetime', OAUTH2_DEFAULT_AUTH_CODE_LIFETIME), $userId, $scope); return $code; } /** * Generate unique access token. * * Implementing classes may want to override these function to implement * other access token or auth code generation schemes. * * @return * An unique access token. * * @ingroup oauth2_section_4 */ protected function genAccessToken() { return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid()))); } /** * Generate unique auth code. * * Implementing classes may want to override these function to implement * other access token or auth code generation schemes. * * @return * An unique auth code. * * @ingroup oauth2_section_3 */ protected function genAuthCode() { return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid()))); } /** * Pull out the Authorization HTTP header and return it. * * Implementing classes may need to override this function for use on * non-Apache web servers. * * @return * The Authorization HTTP header, and FALSE if does not exist. * * @todo Handle Authorization HTTP header for non-Apache web servers. * * @ingroup oauth2_section_5 */ private function getAuthorizationHeader() { if (array_key_exists("HTTP_AUTHORIZATION", $_SERVER)) return $_SERVER["HTTP_AUTHORIZATION"]; if (function_exists("apache_request_headers")) { $headers = apache_request_headers(); if (array_key_exists("Authorization", $headers)) return $headers["Authorization"]; } return FALSE; } /** * Send out HTTP headers for JSON. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.2 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3 * * @ingroup oauth2_section_4 */ private function sendJsonHeaders() { header("Content-Type: application/json"); header("Cache-Control: no-store"); } /** * Redirect the end-user's user agent with error message. * * @param $redirect_uri * An absolute URI to which the authorization server will redirect the * user-agent to when the end-user authorization step is completed. * @param $error * A single error code as described in Section 3.2.1. * @param $error_description * (optional) A human-readable text providing additional information, * used to assist in the understanding and resolution of the error * occurred. * @param $error_uri * (optional) A URI identifying a human-readable web page with * information about the error, used to provide the end-user with * additional information about the error. * @param $state * (optional) REQUIRED if the "state" parameter was present in the client * authorization request. Set to the exact value received from the client. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-3.2 * * @ingroup oauth2_error */ private function errorDoRedirectUriCallback($redirect_uri, $error, $error_description = NULL, $error_uri = NULL, $state = NULL) { $result["query"]["error"] = $error; if ($state) $result["query"]["state"] = $state; if ($this->getVariable('display_error') && $error_description) $result["query"]["error_description"] = $error_description; if ($this->getVariable('display_error') && $error_uri) $result["query"]["error_uri"] = $error_uri; $this->doRedirectUriCallback($redirect_uri, $result); } /** * Send out error message in JSON. * * @param $http_status_code * HTTP status code message as predefined. * @param $error * A single error code. * @param $error_description * (optional) A human-readable text providing additional information, * used to assist in the understanding and resolution of the error * occurred. * @param $error_uri * (optional) A URI identifying a human-readable web page with * information about the error, used to provide the end-user with * additional information about the error. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.3 * * @ingroup oauth2_error */ private function errorJsonResponse($http_status_code, $error, $error_description = NULL, $error_uri = NULL) { $result['error'] = $error; if ($this->getVariable('display_error') && $error_description) $result["error_description"] = $error_description; if ($this->getVariable('display_error') && $error_uri) $result["error_uri"] = $error_uri; header("HTTP/1.1 " . $http_status_code); $this->sendJsonHeaders(); echo json_encode($result); exit; } /** * Send a 401 unauthorized header with the given realm and an error, if * provided. * * @param $http_status_code * HTTP status code message as predefined. * @param $realm * The "realm" attribute is used to provide the protected resources * partition as defined by [RFC2617]. * @param $scope * A space-delimited list of scope values indicating the required scope * of the access token for accessing the requested resource. * @param $error * The "error" attribute is used to provide the client with the reason * why the access request was declined. * @param $error_description * (optional) The "error_description" attribute provides a human-readable text * containing additional information, used to assist in the understanding * and resolution of the error occurred. * @param $error_uri * (optional) The "error_uri" attribute provides a URI identifying a human-readable * web page with information about the error, used to offer the end-user * with additional information about the error. If the value is not an * absolute URI, it is relative to the URI of the requested protected * resource. * * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.2 * * @ingroup oauth2_error */ private function errorWWWAuthenticateResponseHeader($http_status_code, $realm, $error, $error_description = NULL, $error_uri = NULL, $scope = NULL) { $realm = $realm === NULL ? $this->getDefaultAuthenticationRealm() : $realm; $result = "WWW-Authenticate: OAuth realm='" . $realm . "'"; if ($error) $result .= ", error='" . $error . "'"; if ($this->getVariable('display_error') && $error_description) $result .= ", error_description='" . $error_description . "'"; if ($this->getVariable('display_error') && $error_uri) $result .= ", error_uri='" . $error_uri . "'"; if ($scope) $result .= ", scope='" . $scope . "'"; header("HTTP/1.1 ". $http_status_code); header($result); exit; } }

代碼比較多。有興趣的能夠細看。

因爲採用Vendor方法引入,因此這裏的文件都沒有采用命名空間。

5-三、在同目錄下,建ThinkOAuth2.php文件,這個文件裏的類繼承上面那個基類:

<?php require "oauth2.class.php"; class ThinkOAuth2 extends OAuth2{ private $db; private $table; /** * 構造 */ public function __construct() { parent::__construct(); $this -> db = M(); $this -> table = array( 'auth_codes'=>C('OAUTH2_CODES_TABLE'), 'clients'=>C('OAUTH2_CLIENTS_TABLE'), 'tokens'=>C('OAUTH2_TOKEN_TABLE') ); } /** * 析構 */ function __destruct() { $this->db = NULL; // Release db connection } private function handleException($e) { echo "Database error: " . $e->getMessage(); exit; } /** * * 增長client * @param string $client_id * @param string $client_secret * @param string $redirect_uri */ public function addClient($client_id, $client_secret, $redirect_uri) { $time = time(); $sql = "INSERT INTO {$this -> table['clients']} (client_id, client_secret, redirect_uri,create_time) VALUES ('{$client_id}', '{$client_secret}', '{$redirect_uri}','{$time}')"; $res = $this -> db -> execute($sql); return res; } /** * Implements OAuth2::checkClientCredentials() * @see OAuth2::checkClientCredentials() */ protected function checkClientCredentials($client_id, $client_secret = NULL) { $sql = "SELECT client_secret FROM {$this -> table['clients']} WHERE client_id = {$client_id}"; $result = $this -> db -> query($sql); if ($client_secret === NULL) { return $result !== FALSE; } //Log::write("checkClientCredentials : ".$result); //Log::write("checkClientCredentials : ".$result[0]); //Log::write("checkClientCredentials : ".$result[0]["client_secret"]); return $result[0]["client_secret"] == $client_secret; } /** * Implements OAuth2::getRedirectUri(). * @see OAuth2::getRedirectUri() */ protected function getRedirectUri($client_id) { $sql = "SELECT redirect_uri FROM {$this -> table['clients']} WHERE client_id = {$client_id}"; $result = $this -> db -> query($sql); if ($result === FALSE) { return FALSE; } //Log::write("getRedirectUri : ".$result); //Log::write("getRedirectUri : ".$result[0]); //Log::write("getRedirectUri : ".$result[0]["redirect_uri"]); return isset($result[0]["redirect_uri"]) && $result[0]["redirect_uri"] ? $result[0]["redirect_uri"] : NULL; } /** * Implements OAuth2::getAccessToken(). * @see OAuth2::getAccessToken() */ protected function getAccessToken($access_token) { $sql = "SELECT client_id, expires_in, scope FROM {$this -> table['tokens']} WHERE access_token = '{$access_token}'"; $result = $this -> db -> query($sql); //Log::write("getAccessToken : ".$result); //Log::write("getAccessToken : ".$result[0]); return $result !== FALSE ? $result : NULL; } /** * Implements OAuth2::setAccessToken(). * @see OAuth2::setAccessToken() */ protected function setAccessToken($access_token, $client_id, $expires, $user_id, $scope=NULL, $refresh_token='') { $sql = "INSERT INTO {$this -> table['tokens']} (access_token, client_id, expires_in, scope,user_id,refresh_token) VALUES ('{$access_token}', '{$client_id}', '{$expires}', '{$scope}',{$user_id},'{$refresh_token}')"; $this -> db -> execute($sql); } /** * Overrides OAuth2::getSupportedGrantTypes(). * @see OAuth2::getSupportedGrantTypes() */ protected function getSupportedGrantTypes() { return array( OAUTH2_GRANT_TYPE_AUTH_CODE ); } /** * Overrides OAuth2::getAuthCode(). * @see OAuth2::getAuthCode() */ protected function getAuthCode($code) { $sql = "SELECT user_id, code, client_id, redirect_uri, expires, scope FROM {$this -> table['auth_codes']} WHERE code = '{$code}'"; $result = $this -> db -> query($sql); //Log::write("getAuthcode : ".$result); //Log::write("getAuthcode : ".$result[0]); //Log::write("getAuthcode : ".$result[0]["code"]); return $result !== FALSE ? $result[0] : NULL; } /** * Overrides OAuth2::setAuthCode(). * @see OAuth2::setAuthCode() */ protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) { $time = time(); $sql = "INSERT INTO {$this -> table['auth_codes']} (code, client_id, redirect_uri, expires, scope) VALUES ('{$code}', '{$client_id}', '{$redirect_uri}', '{$expires}', '{$scope}')"; $result = $this -> db -> execute($sql); } /** * Overrides OAuth2::checkUserCredentials(). * @see OAuth2::checkUserCredentials() */ protected function checkUserCredentials($client_id, $username, $password){ return TRUE; } }

這裏涉及到一些數據庫的操做。繼承出來操做,仍是比較一目瞭然的。

5-四、準備好類了。咱們就能夠實現控制器裏的功能代碼。動手以前先搞清楚咱們的意圖。

首先,咱們須要分配一個oauth_client和一個oauth_secrete給第三方網站(也就是client.ruanwenwu.cn)來獲取oauth服務。

其次,client.ruanwenwu.cn會把用戶導入到server.ruanwenwu.cn來進行受權。因此咱們咱們還須要準備受權頁面。

由於涉及到服務端和客戶端的數據交互,因此可能服務端和客戶端的相關介紹我會交叉進行。

5-4-一、建立oauth_client。

5-4-二、獲取oauth_cliest列表。

5-4-三、準備獲取用戶受權的頁面和邏輯。(受權服務器)

5-4-四、準備提供用戶數據的邏輯。(資源服務器)

這些邏輯我都寫在一個控制器OauthController.class.php裏。代碼以下:

<?php namespace Home\Controller; use Think\Controller; class OauthController extends Controller{ private $oauth = NULL; function _initialize(){ header("Content-Type: application/json"); Vendor("oauth.ThinkOAuth2"); $this -> oauth = new \ThinkOAuth2(); } public function index(){ header("Content-Type:application/json; charset=utf-8"); $this -> ajaxReturn(null, 'oauth-server-start', 1, 'json'); } public function access_token() { $this -> oauth -> grantAccessToken(); } //權限驗證 public function authorize() { if (IS_POST) { if(true){ //這個地方驗證成功後要把用戶的uid加進來 $_POST['user_id'] = 1; $this -> oauth -> finishClientAuthorization(true, $_POST); //第一個參數很重要,是用戶是否受權 } return; } ///表單準備 $auth_params = $this -> oauth -> getAuthorizeParams(); $this->assign("auth_params",$auth_params); $this->display(); } public function addclient() { echo 'jack'; if (IS_POST) { echo 'pd'; $res = $this -> oauth -> addClient($_POST["client_id"], $_POST["client_secret"], $_POST["redirect_uri"]); if($res){ $this->redirect("/home/Oauth/clientlist"); die; } } $this->display(); } public function clientlist(){ $res = M()->table("oauth_client")->select(); $this->assign("clients",$res); var_dump($res); $this->display(); } }

順便我將用到的模板文件(server.ruanwenwu.cn\Application\Home\View\oauth目錄下)也貼出來給你們參考一下:

addclient.html

<!doctype html> <head> <meta charset="utf-8" /> <title>這裏是添加客戶端界面</title> </head> <body> <h1>添加客戶端</h1> <form action="" method="post" > clientId:<input type="text" name="client_id" /> <br /> client_secrete:<input type="text" name="client_secret" /> <br /> callbackUrl:<input type="text" name="redirect_uri" /> <br /> <input type="submit" value="submit" /> </form> </body> </html>

authorize.html

<!doctype html> <html> <form action="" method="post"> 用戶名:<input type="text" name="username" /> <br /> 密碼:<input type="text" name="pwd" /> <input type="hidden" name="response_type" value="code" /> <input type="hidden" name="client_id" value="{$auth_params['client_id']}" /> <input type="hidden" name="redirect_uri" value="{$auth_params['redirect_uri']}" /> <input type="submit" value="submit" /> </form> </html>

注意:我這裏用到了幾個隱藏域。這個是必須的,在post提交驗證時會用到。

5-4-五、建立oauth2.0用戶。

打 開server.ruanwenwu.cn/index.php/home/oauth/addcient,輸入client_id和 client_secrete還有redirect_uri建立一個oauth2.0用戶。其中redirect_uri是用戶受權後須要跳轉的地方。

到這裏服務端的部署就差很少結束了。如今開始部署客戶端。

3、oauth2.0客戶端搭建。

一、同服務端同樣搭建client.ruanwenwu.cn。(也是用thinkphp)。

二、能正常訪問後。在類庫中加入oauth2.0客戶端類。

在\client.ruanwenwu.cn\ThinkPHP\Library\Org\Util目錄下創建Oauth2.class.php,代碼以下:

<?php namespace Org\Util; /** * 與 OAuth2 服務器 通信驗證 核心類 (PHP版) * * @description OAuth2 說明請你們參考 木螞蟻接口文檔 * * * @author Mumayi-Team (zhaopan趙攀) * @version 1.0 * @datetime 2013-12-31 */ class Oauth2 { /* * 如下幾個參數 具體做用和用法 詳見 木螞蟻接口文檔 */ //分配的客戶端 ID public $client_id; //分配的客戶端 密匙 public $client_secret; //得到的 用戶受權 訪問令牌 public $access_token; //刷新 訪問令牌 (過時時使用) public $refresh_token; // 最後一個收到的HTTP代碼 (用於調試,忽略) public $http_code; //預留字段 (忽略) public $url; //設置 基礎連接 public $host = "http://server.ruanwenwu.cn/index.php/home/oauth/"; //請求超時時間 public $timeout = 30; //連接超時時間 public $connecttimeout = 30; //是否驗證 SSL 證書 public $ssl_verifypeer = FALSE; //請求結果處理格式 public $format = 'json'; //以 JSON_DECODE 方式解析 public $decode_json = TRUE; //預留字段 (忽略) public $http_info; //CURL 函數使用 (驗證使用, 忽略) public $useragent = 'Mumayi Team OAuth2 v1.0'; //是否 開啓 請求調試 (開啓時 將輸出 詳細的請求結果) public $debug = FALSE; //邊界 (CURL使用 寫入在Header的分界線, 忽略) public static $boundary = ''; //返回 OAuth 令牌 請求地址 function accessTokenURL() { return $this->host.'access_token'; } //返回 OAuth 自動受權 請求地址 function authorizeURL() { return $this->host.'authorize'; } //構造函數 賦值 一些必要參數 function __construct($client_id, $client_secret, $access_token = NULL, $refresh_token = NULL) { $this->client_id = $client_id; $this->client_secret = $client_secret; $this->access_token = $access_token; $this->refresh_token = $refresh_token; // $this->debug = true; } /** * authorize接口 * * 對應API:{@link http://u.mumayi.com/oauth/authorize Oauth2/authorize} * * @param string $url 受權後的回調地址,站外應用需與回調地址一致,站內應用須要填寫canvas page的地址 * @param string $response_type 支持的值包括 code 和token 默認值爲code * @param string $state 用於保持請求和回調的狀態。在回調時,會在Query Parameter中回傳該參數 * @param string $display 受權頁面類型 可選範圍: * - default 默認受權頁面 * - mobile 支持html5的手機 * - popup 彈窗受權頁 * - wap1.2 wap1.2頁面 * - wap2.0 wap2.0頁面 * - js js-sdk 專用 受權頁面是彈窗,返回結果爲js-sdk回掉函數 * - apponweibo 站內應用專用,站內應用不傳display參數,而且response_type爲token時,默認使用改display.受權後不會返回access_token,只是輸出js刷新站內應用父框架 * @return array */ function getAuthorizeURL( $url, $response_type = 'code', $state = NULL, $display = NULL ) { $params = array(); $params['client_id'] = $this->client_id; $params['redirect_uri'] = $url; $params['response_type'] = $response_type; $params['state'] = $state; $params['display'] = $display; return $this->authorizeURL() . "&" . http_build_query($params); } /** * access_token接口 * * 對應API:{@link http://open.weibo.com/wiki/OAuth2/access_token OAuth2/access_token} * * @param string $type 請求的類型,能夠爲:code, password, token * @param array $keys 其餘參數: * - 當$type爲code時: array('code'=>..., 'redirect_uri'=>...) * - 當$type爲password時: array('username'=>..., 'password'=>...) * - 當$type爲token時: array('refresh_token'=>...) * @return array */ function getAccessToken( $type = 'code', $keys ) { $params = array(); $params['client_id'] = $this->client_id; $params['client_secret'] = $this->client_secret; if ( $type === 'token' ) { $params['grant_type'] = 'refresh_token'; $params['refresh_token'] = $keys['refresh_token']; } elseif ( $type === 'code' ) { $params['grant_type'] = 'authorization_code'; $params['code'] = $keys['code']; $params['redirect_uri'] = $keys['redirect_uri']; } elseif ( $type === 'password' ) { $params['grant_type'] = 'password'; $params['username'] = $keys['username']; $params['password'] = $keys['password']; } else { echo json_encode(array('error' => '錯誤的受權類型['.$type.'(token, code)]')); die; } $response = $this->oAuthRequest($this->accessTokenURL(), 'POST', $params); //var_dump($response);die; $token = json_decode($response, true); if ( is_array($token) && !isset($token['error']) ) { $this->access_token = $token['access_token']; //$this->refresh_token = $token['refresh_token']; } else { echo json_encode(array('error' => '獲取訪問令牌失敗。['.$token['error'].']')); die; } return $token; } /** * 解析 signed_request * * @param string $signed_request 應用框架在加載iframe時會經過向Canvas URL post的參數signed_request * * @return array */ function parseSignedRequest($signed_request) { list($encoded_sig, $payload) = explode('.', $signed_request, 2); $sig = self::base64decode($encoded_sig) ; $data = json_decode(self::base64decode($payload), true); if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') return '-1'; $expected_sig = hash_hmac('sha256', $payload, $this->client_secret, true); return ($sig !== $expected_sig)? '-2':$data; } /** * @ignore */ function base64decode($str) { return base64_decode(strtr($str.str_repeat('=', (4 - strlen($str) % 4)), '-_', '+/')); } /** * 讀取jssdk受權信息,用於和jssdk的同步登陸 * * @return array 成功返回array('access_token'=>'value', 'refresh_token'=>'value'); 失敗返回false */ function getTokenFromJSSDK() { $key = "mumayijs_" . $this->client_id; if ( isset($_COOKIE[$key]) && $cookie = $_COOKIE[$key] ) { parse_str($cookie, $token); if ( isset($token['access_token']) && isset($token['refresh_token']) ) { $this->access_token = $token['access_token']; $this->refresh_token = $token['refresh_token']; return $token; } else { return false; } } else { return false; } } /** * 從數組中讀取access_token和refresh_token * 經常使用於從Session或Cookie中讀取token,或經過Session/Cookie中是否存有token判斷登陸狀態。 * * @param array $arr 存有access_token和secret_token的數組 * @return array 成功返回array('access_token'=>'value', 'refresh_token'=>'value'); 失敗返回false */ function getTokenFromArray( $arr ) { if (isset($arr['access_token']) && $arr['access_token']) { $token = array(); $this->access_token = $token['access_token'] = $arr['access_token']; if (isset($arr['refresh_token']) && $arr['refresh_token']) { $this->refresh_token = $token['refresh_token'] = $arr['refresh_token']; } return $token; } else { return false; } } /** * 以 GET 請求方式 請求 OAuth 驗證服務器 * * @return mixed */ function get($url, $parameters = array()) { $response = $this->oAuthRequest($url, 'GET', $parameters); if ($this->format === 'json' && $this->decode_json) { return json_decode($response, true); } return $response; } /** * 以 POST 請求方式 請求 OAuth 驗證服務器 * * @return mixed */ function post($url, $parameters = array(), $multi = false) { $response = $this->oAuthRequest($url, 'POST', $parameters, $multi ); if ($this->format === 'json' && $this->decode_json) { return json_decode($response, true); } return $response; } /** * 請求 OAuth 驗證服務器 進行驗證 * * @return string */ function oAuthRequest($url, $method, $parameters, $multi = false) { if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) { $url = "{$this->host}{$url}.{$this->format}"; } switch ($method) { case 'GET': if(!empty($parameters)) $url = $url . '&' . http_build_query($parameters); return $this->http($url, 'GET'); default: $headers = array(); if (!$multi && (is_array($parameters) || is_object($parameters)) ) { $body = http_build_query($parameters); } else { $body = self::build_http_query_multi($parameters); $headers[] = "Content-Type: multipart/form-data; boundary=" . self::$boundary; } //return $this->http($url, $method, $body, $headers); return $this->http($url, $method, $body, $headers); } } /** * 使用 CURL 模擬一個 HTTP 請求 * * @description (須要開啓 CURL 擴展) * * @return string API results * */ function http($url, $method, $postfields = NULL, $headers = array()) { $this->http_info = array(); $ci = curl_init(); /* Curl settings */ curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent); curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout); curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout); curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ci, CURLOPT_ENCODING, ""); curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer); curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, 1); curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader')); curl_setopt($ci, CURLOPT_HEADER, FALSE); switch ($method) { case 'POST': curl_setopt($ci, CURLOPT_POST, TRUE); if (!empty($postfields)) { curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields); $this->postdata = $postfields; } break; case 'DELETE': curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE'); if (!empty($postfields)) { $url = "{$url}?{$postfields}"; } } if ( isset($this->access_token) && $this->access_token ) $headers[] = "Authorization: OAuth2 ".$this->access_token; if ( !empty($this->remote_ip) ) { if ( defined('SAE_ACCESSKEY') ) { $headers[] = "SaeRemoteIP: " . $this->remote_ip; } else { $headers[] = "API-RemoteIP: " . $this->remote_ip; } } else { if ( !defined('SAE_ACCESSKEY') ) { $headers[] = "API-RemoteIP: " . $_SERVER['REMOTE_ADDR']; } } curl_setopt($ci, CURLOPT_URL, $url ); curl_setopt($ci, CURLOPT_HTTPHEADER, $headers ); curl_setopt($ci, CURLINFO_HEADER_OUT, TRUE ); $response = curl_exec($ci); $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); $this->http_info = array_merge($this->http_info, curl_getinfo($ci)); $this->url = $url; if ($this->debug) { echo "=====post data======\r\n"; var_dump($postfields); echo "=====headers======\r\n"; print_r($headers); echo '=====request info====='."\r\n"; print_r( curl_getinfo($ci) ); echo '=====response====='."\r\n"; print_r( $response ); } curl_close ($ci); return $response; } /** * 從結果中 獲取 header 參數 (CURL 使用) * * @return int * * @ignore (忽略) */ function getHeader($ch, $header) { $i = strpos($header, ':'); if (!empty($i)) { $key = str_replace('-', '_', strtolower(substr($header, 0, $i))); $value = trim(substr($header, $i + 2)); $this->http_header[$key] = $value; } return strlen($header); } /** * 把要傳遞的參數 從新構造一下 (多維數組) * * @ignore (忽略) */ public static function build_http_query_multi($params) { if (!$params) return ''; uksort($params, 'strcmp'); $pairs = array(); self::$boundary = $boundary = uniqid('------------------'); $MPboundary = '--'.$boundary; $endMPboundary = $MPboundary. '--'; $multipartbody = ''; foreach ($params as $parameter => $value) { if( in_array($parameter, array('pic', 'image')) && $value{0} == '@' ) { $url = ltrim( $value, '@' ); $content = file_get_contents( $url ); $array = explode( '?', basename( $url ) ); $filename = $array[0]; $multipartbody .= $MPboundary . "\r\n"; $multipartbody .= 'Content-Disposition: form-data; name="' . $parameter . '"; filename="' . $filename . '"'. "\r\n"; $multipartbody .= "Content-Type: image/unknown\r\n\r\n"; $multipartbody .= $content. "\r\n"; } else { $multipartbody .= $MPboundary . "\r\n"; $multipartbody .= 'content-disposition: form-data; name="' . $parameter . "\"\r\n\r\n"; $multipartbody .= $value."\r\n"; } } $multipartbody .= $endMPboundary; return $multipartbody; } public function get_uid() { $hostUrl = 'http://server.ruanwenwu.cn/index.php/home/resource/userinfo?type=mumayi'; $params = array( 'access_token' => $this->access_token, ); return $this->get( $hostUrl, $params ); } }

注意:這個類的加載跟服務端採用的不一樣的方式。這裏用了命名空間。服務端用的vendor。

三、準備訪問連接。

咱們經過這個url將用戶導向server.ruanwenwu.cn進行受權,發送accesss_code操做。

我在client.ruanwenwu.cn的index控制器中實現的這個url。控制器裏沒有邏輯,我只貼index.html了:

<!doctype html> <html> <head></head> <body> <a href="http://server.ruanwenwu.cn/index.php/home/oauth/authorize?redirect_uri=http://client.ruanwenwu.cn/index.php/home/login&response_type=code&client_id=10009174&type=123456">登錄</a> </body> </html>

注 意:這個url中redirect_uri,response_type爲必選參數。並且這個redirect_uri必須包含咱們在服務端添加 oauth服務時寫的那個redirect_uri。不然不能經過驗證。(也就是說:在咱們這個例子裏,這個redirect_uri必須包含:

http://client.ruanwenwu.cn/index.php/home/login

四、點擊連接,跳轉到服務端對應的url。實際上就是server.ruanwenwu.cn的authorize操做。在這裏會對用戶進行驗證,並判斷是否受權。

//權限驗證 public function authorize() { if (IS_POST) { if(true){ //這個地方驗證成功後要把用戶的uid加進來 $_POST['user_id'] = 1; //這裏是寫死的,其實是從數據庫讀出來 $this -> oauth -> finishClientAuthorization(true, $_POST); //第一個參數很重要,是用戶是否受權 } return; } ///表單準備 $auth_params = $this -> oauth -> getAuthorizeParams(); $this->assign("auth_params",$auth_params); $this->display(); }

注意看我在這個操做裏寫的註釋。

驗證成功後,頁面又跳回到咱們以前寫的redirect_uri,並帶上了code參數,這就是驗證服務器向客戶端發送的access_code。

五、客戶端拿到access_code,向驗證服務器請求access_token。

在個人client.ruanwenwu.cn的loginController.class.php中進行:

<?php namespace Home\Controller; use Think\Controller; use Org\Util\Oauth2; class LoginController extends Controller{ public function _initialize(){ } public function index(){ $this->oauth = new Oauth2('10009174','123456'); $keys = array(); //keys 數組 賦值 code 值 (換取token, 必須參數) $keys['code'] = $_GET['code']; //keys 數組 賦值 回調地址信息 (換取token, 必須參數) $keys['redirect_uri'] = 'http://client.ruanwenwu.cn/index.php/home/login?type=mumayidev'; //根據 code 獲取 token //var_dump( $token = $this->oauth->getAccessToken( 'code', $keys )) ; $token = $this->oauth->getAccessToken( 'code', $keys ) ; //如今已經獲得token,而且將access_token寫到對象裏了。就能夠請求資源了 var_dump( $this->oauth->get_uid()); die; } }
$token = $this->oauth->getAccessToken( 'code', $keys ) ; //這就是請求代碼。code是驗證方式。$keys數組裏要code和redirect_uri。在服務器端須要驗證

服務端的響應是這樣的:

驗證client.ruanwenwu.cn的client_id和secrete是否合法,還要判讀access_code是否過時

public function access_token() { $this -> oauth -> grantAccessToken(); //這個方法裏有全部的驗證及生成access_token的操做。(會有數據庫的寫入) }

服務端生成access_token以後,會返回給客戶端。

客戶端拿到access_token以後,向資源服務器請求用戶資源:

在這裏個人驗證服務器和資源服務器都是server.ruanwenwu.cn。我用resourceController.class.php來作資源控制:

<?php namespace Home\Controller; use Think\Controller; class ResourceController extends Controller{ protected $db; public function _initialize(){ $this->db = M(); } public function userinfo(){ // $arr = array("username"=>"阮文武","sex"=>1,"img"=>"http://weewrerfdf"); // echo json_encode($arr); // die; if(isset($_GET['access_token']) && $_GET['access_token']){ $res = $this->db->table("oauth_token")->where("access_token = '".$_GET['access_token']."'")->find(); if($res && count($res) > 0){ if($res['expires_in'] > time()){ $arr = array("username"=>"阮文武d","sex"=>1,"img"=>"http://weewrerfdf"); echo json_encode($arr); die; } } }; } }

若是access_token有效,就查到用戶id,取出用戶的信息。我這裏只是作測試,因此就直接本身返回了數據。本來寫的很詳細,後來由於內容字段是text,沒保存完整,這會時間比較趕,就寫的簡單一點。

再抽時間補充吧。

歡迎你們與我討論哦。

 

 

http://www.tuicool.com/articles/u6beUju

相關文章
相關標籤/搜索