本篇文章更新時間:2024/09/04
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知。
一介資男的 LINE 社群開站囉!歡迎入群聊聊~
如果本站內容對你有幫助,歡迎使用 BFX Pay 加密貨幣 或 新台幣 贊助支持。
因工作需求,需要寫到 Oracle ERP NetSuite 的程式。這套的強大,從他也支援的開發串接方式就可以知道。
不過在用 PHP 實作串接 OAuth 1.0a 以及驗證方法更新到 SHA256
的開源工具...
沒有啊~ 於是就是找到一套彈性的其他語言(NodeJS)寫的工具來改寫成 PHP 版本。
class OAuth {
private $consumer;
private $nonce_length;
private $version;
private $parameter_seperator;
private $realm;
private $last_ampersand;
private $signature_method;
private $hash_function;
private $body_hash_function;
public function __construct($opts) {
if (!isset($opts['consumer'])) {
throw new Exception('consumer option is required');
}
$this->consumer = $opts['consumer'];
$this->nonce_length = isset($opts['nonce_length']) ? $opts['nonce_length'] : 32;
$this->version = isset($opts['version']) ? $opts['version'] : '1.0';
$this->parameter_seperator = isset($opts['parameter_seperator']) ? $opts['parameter_seperator'] : ', ';
$this->realm = isset($opts['realm']) ? $opts['realm'] : null;
$this->last_ampersand = isset($opts['last_ampersand']) ? $opts['last_ampersand'] : true;
$this->signature_method = isset($opts['signature_method']) ? $opts['signature_method'] : 'PLAINTEXT';
if ($this->signature_method == 'PLAINTEXT' && !isset($opts['hash_function'])) {
$opts['hash_function'] = function ($base_string, $key) {
return $key;
};
}
if (!isset($opts['hash_function'])) {
throw new Exception('hash_function option is required');
}
$this->hash_function = $opts['hash_function'];
$this->body_hash_function = isset($opts['body_hash_function']) ? $opts['body_hash_function'] : $this->hash_function;
}
public function authorize($request, $token = null) {
$oauth_data = array(
'oauth_consumer_key' => $this->consumer['key'],
'oauth_nonce' => $this->getNonce(),
'oauth_signature_method' => $this->signature_method,
'oauth_timestamp' => $this->getTimeStamp(),
'oauth_version' => $this->version,
);
if ($token !== null && isset($token['key'])) {
$oauth_data['oauth_token'] = $token['key'];
}
if (!isset($request['data'])) {
$request['data'] = array();
}
if (isset($request['includeBodyHash']) && $request['includeBodyHash']) {
$oauth_data['oauth_body_hash'] = $this->getBodyHash($request, isset($token['secret']) ? $token['secret'] : '');
}
$oauth_data['oauth_signature'] = $this->getSignature($request, isset($token['secret']) ? $token['secret'] : '', $oauth_data);
return $oauth_data;
}
private function getSignature($request, $token_secret, $oauth_data) {
return call_user_func($this->hash_function, $this->getBaseString($request, $oauth_data), $this->getSigningKey($token_secret));
}
private function getBodyHash($request, $token_secret) {
$body = is_string($request['data']) ? $request['data'] : json_encode($request['data']);
if (!$this->body_hash_function) {
throw new Exception('body_hash_function option is required');
}
return call_user_func($this->body_hash_function, $body, $this->getSigningKey($token_secret));
}
private function getBaseString($request, $oauth_data) {
return strtoupper($request['method']) . '&' . $this->percentEncode($this->getBaseUrl($request['url'])) . '&' . $this->percentEncode($this->getParameterString($request, $oauth_data));
}
private function getParameterString($request, $oauth_data) {
if (isset($oauth_data['oauth_body_hash'])) {
$base_string_data = $this->sortObject($this->percentEncodeData($this->mergeObject($oauth_data, $this->deParamUrl($request['url']))));
} else {
$base_string_data = $this->sortObject($this->percentEncodeData($this->mergeObject($oauth_data, $this->mergeObject($request['data'], $this->deParamUrl($request['url'])))));
}
$data_str = '';
foreach ($base_string_data as $item) {
$key = $item['key'];
$value = $item['value'];
if (is_array($value)) {
sort($value);
$valString = '';
foreach ($value as $i => $val) {
$valString .= $key . '=' . $val;
if ($i < count($value) - 1) {
$valString .= '&';
}
}
$data_str .= $valString;
} else {
$data_str .= $key . '=' . $value . '&';
}
}
return substr($data_str, 0, -1);
}
private function getSigningKey($token_secret) {
$token_secret = $token_secret ?: '';
if (!$this->last_ampersand && !$token_secret) {
return $this->percentEncode($this->consumer['secret']);
}
return $this->percentEncode($this->consumer['secret']) . '&' . $this->percentEncode($token_secret);
}
private function getBaseUrl($url) {
return explode('?', $url)[0];
}
private function deParam($string) {
$arr = explode('&', $string);
$data = array();
foreach ($arr as $item) {
$parts = explode('=', $item);
$key = $parts[0];
$value = isset($parts[1]) ? $parts[1] : '';
if (isset($data[$key])) {
if (!is_array($data[$key])) {
$data[$key] = array($data[$key]);
}
$data[$key][] = urldecode($value);
} else {
$data[$key] = urldecode($value);
}
}
return $data;
}
private function deParamUrl($url) {
$tmp = explode('?', $url);
if (count($tmp) === 1) {
return array();
}
return $this->deParam($tmp[1]);
}
private function percentEncode($str) {
return rawurlencode($str);
}
private function percentEncodeData($data) {
$result = array();
foreach ($data as $key => $value) {
if (is_array($value)) {
$newValue = array();
foreach ($value as $val) {
$newValue[] = $this->percentEncode($val);
}
$value = $newValue;
} else {
$value = $this->percentEncode($value);
}
$result[$this->percentEncode($key)] = $value;
}
return $result;
}
public function toHeader($oauth_data) {
$sorted = $this->sortObject($oauth_data);
$header_value = 'OAuth ';
if ($this->realm) {
$header_value .= 'realm="' . $this->realm . '"' . $this->parameter_seperator;
}
foreach ($sorted as $item) {
if (strpos($item['key'], 'oauth_') !== 0) {
continue;
}
$header_value .= $this->percentEncode($item['key']) . '="' . $this->percentEncode($item['value']) . '"' . $this->parameter_seperator;
}
return array('Authorization' => substr($header_value, 0, -strlen($this->parameter_seperator)));
}
private function getNonce() {
$word_characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$result = '';
for ($i = 0; $i < $this->nonce_length; $i++) {
$result .= $word_characters[rand(0, strlen($word_characters) - 1)];
}
return $result;
}
private function getTimeStamp() {
return time();
}
private function mergeObject($obj1, $obj2) {
return array_merge($obj1, $obj2);
}
private function sortObject($data) {
$keys = array_keys($data);
sort($keys);
// ksort($keys);
$result = array();
foreach ($keys as $key) {
$result[] = array(
'key' => $key,
'value' => $data[$key],
);
}
return $result;
}
}
Gist: Link
主要就是以 ddo/oauth-1.0a 這套來移植。(感謝 ddo 大大)
在 NetSuite 的案例中測試的寫法如下:
$base_url = 'https://USERID.restlets.api.netsuite.com/app/site/hosting/restlet.nl';
$consumer_key = 'consumer_key_consumer_key_consumer_key';
$consumer_secret = 'consumer_secret_consumer_secret_consumer_secret';
$token = 'token_token_token_token_token_token_token_token_token_';
$token_secret = 'token_secret_token_secret_token_secret_token_secret_';
$realm = 'USERID';
$script = 256;
$deploy=1;
$oauth = new OAuth(array(
'consumer' => array(
'key' => $consumer_key,
'secret' => $consumer_secret,
),
'realm' => $realm,
'signature_method' => 'HMAC-SHA256',
'hash_function' => function ($base_string, $key) {
return base64_encode(hash_hmac('sha256', $base_string, $key, true));
},
));
$request = array(
'method' => 'GET',
'url' => $base_url . '?script=' . $script . '&deploy='.$deploy,
// 'data' => array(
// 'status' => 'Hello World!'
// )
);
$token = array(
'key' => $token,
'secret' => $token_secret,
);
$oauth_data = $oauth->authorize($request, $token);
$header = $oauth->toHeader($oauth_data);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $request['url']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: ' . $header['Authorization'],
'Content-Type: application/json',
]);
$response = curl_exec($ch);
curl_close($ch);
$item = json_decode($response, true);
上面是 GET
的範例。
把 App 與 User 的 Key 還有 Script 的相關資訊帶入後,就可以完成請求。
POST
的部分其實差異就是把註解的 data
欄位打開,然後 curl 方法記得傳送 POST 的資料就可以。設定如下:
$request = array(
'method' => 'POST',
'url' => $base_url . '?script=' . $script . '&deploy='.$deploy,
'includeBodyHash' => true,
'data' => array(
'query' => $sql,
'params' => $params,
),
);