参考OAuth2.0协议,当我们开发了一下服务例如oss,文件转码服务等等,这里我们称之为实际功能模块,我们想让第三方快速方便地接入,同时还要确保安全,这时候我们就可以考虑开发一个包我们先称之为中间包,这个中间包有以下特点:
1、将接口简单化,并且做成可调用方法。
2、方法不做实际功能业务,通过http调用实际功能业务接口。
3、我们不可能要求第三方在使用我们的服务的时候还要和写一套签名算法和我们对接,所以验签模块肯定是写在中间包里面写好,第三方在自己平台上获取或申请到access_key和access_secret(自己平台需要保存在数据库),在初始化类的带入。
以php写一个例子:
我现在开发了一个转码功能,在中间包里我先要写好网络层,负责与实际功能接口通讯:
请求类:
class HttpHelper
{
public static $connectTimeout = 20000;//20 second
public static $readTimeout = 80000;//80 second
public static function curl($url, $httpMethod = "GET", $postFields = null, $headers = null)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $httpMethod);
if (defined('ENABLE_HTTP_PROXY')) { //如果有设置http代理
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_PROXY, HTTP_PROXY_IP);
curl_setopt($ch, CURLOPT_PROXYPORT, HTTP_PROXY_PORT);
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($httpMethod == 'POST'){
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
}
if (self::$readTimeout) {
curl_setopt($ch, CURLOPT_TIMEOUT, self::$readTimeout);
}
if (self::$connectTimeout) {
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$connectTimeout);
}
//https request
if (strlen($url) > 5 && strtolower(substr($url, 0, 5)) == "https") {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
if (is_array($headers) && 0 < count($headers)) {
$httpHeaders = self::getHttpHearders($headers);
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
}
$httpResponse = new HttpResponse();
$httpResponse->setBody(curl_exec($ch));
$httpResponse->setStatus(curl_getinfo($ch, CURLINFO_HTTP_CODE));
if (curl_errno($ch)) {
throw new Exception("Speicified endpoint or uri is not valid.", "SDK.ServerUnreachable");
}
curl_close($ch);
return $httpResponse;
}
public static function getHttpHearders($headers)
{
$httpHeader = array();
foreach ($headers as $key => $value) {
array_push($httpHeader, $key.":".$value);
}
return $httpHeader;
}
}
响应类:
class HttpResponse
{
private $body;
private $status;
public function getBody()
{
return $this->body;
}
public function setBody($body)
{
$this->body = $body;
}
public function getStatus()
{
return $this->status;
}
public function setStatus($status)
{
$this->status = $status;
}
public function isSuccess()
{
if (200 <= $this->status && 300 > $this->status) {
return true;
}
return false;
}
public function getMessage(){
$response = $this->body;
$message = json_decode($response,true);
return is_array($message)?$message['data']['msg']:'未知异常';
}
}
然后中间包的客户端可以这样写:
class Transcode
{
protected $access_key;
protected $access_secret;
public function __construct($access_key,$access_secret)
{
$this->access_key = $access_key;
$this->access_secret = $access_secret;
}
public function VideoTranscode($objectKeyInput, $objectKeyTarget, $type, $preset_id, $pipeline_id){
$params = array(
'input_file'=>$objectKeyInput,
'output_file'=>$objectKeyTarget,
'file_type'=>$type,
'preset_id'=>$preset_id,
'pipeline_id'=>$pipeline_id,
'access_key'=>$this->access_key,
);
$header = array(
'signature'=>\Signature::doSignMd5($params,$this->access_secret)
);
$response = \HttpHelper::curl('10.8.8.99/api/upload_complete','POST',$params,$header);
//10.8.8.99/api/upload_complete是实际功能接口
return $response->getBody();
}
public function LiveTranscode(){
$params = array(
'access_key'=>$this->access_key,
);
$header = array(
'signature'=>\Signature::doSignMd5($params,$this->access_secret)
);
$response =\HttpHelper::curl('10.8.8.99/api/livejob','POST',$params,$header);
return $response->getBody();
}
}
access_key可以带在参数里,因为这个就像用户名,但是access_secret是调用服务的密码,只有发送方和接收方知道,必须加密传输,同时为了在网络传输的过程中参数被篡改,我们可以把参数和该access_secret使用签名算法加密成签名字符串signature,带在http头部,实际功能模块接收到post过来的参数,获取里面的access_key参数查询数据库是否存在该用户,如果存在则获取该用户的access_secret,和post过来的参数进行相同的签名算法生成signature1,如果signature1=signature则证明签名成功,数据没有被篡改,并且该用户是又权限操作该接口的。
写一个简单的签名类:
class Signature
{
public static function doSignMd5($data, $key = '') {
//签名步骤一:按字典序排序参数
ksort($data);
$string = self::ToUrlParams($data);
//签名步骤二:在string后加入KEY
$string = $string . "&key=" . $key;
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
protected static function ToUrlParams($data) {
$buff = "";
foreach ($data as $k => $v)
{
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff;
}
}
这样一个简单的中间包就实现了,在实际功能模块我们这样验证签名:
$params = $_POST;
$access_key = $params['access_key'];
$type = $params['type'];
$signature = $request->header('signature');
$data = PartnerSdkModel::where('is_on',1)->where('sdk_id',$type)
->where('access_key',$access_key)->first();
if (empty($data)){
return ApiResponse(['message'=>'access_key不存在','status'=>401,'error_id'=>'ACCESS_KEY_NOT_EXIST']);
}
$sign_result = \App\Library\ParentSign::doSignMd5($params, $data->access_secret);
if ($sign_result != $signature) {
return ApiResponse(['message'=>'签名失败','status'=>401,'error_id'=>'SIGNATURE_ERROR']);
}
return;
然后第三方这样调用我们的中间包就可以实现调用我们实际功能接口:
$client= new Transcode('your_access_key','your_access_secret');
$liveData = $client->VideoTranscode();