Golang 10行代碼,搞定對外提供 OpenAPI

open api

open api 是什麼? Open API即開放API,也稱開放接口 所謂的開放API(OpenAPI)是服務型網站常見的一種應用,網站的服務商將本身的網站服務封裝成一系列API(Application Programming Interface,應用編程接口)開放出去,供第三方開發者使用,這種行爲就叫作開放網站的API,所開放的API就被稱做OpenAPI(開放API)。 (摘自百科,主要爲了說一下概念)mysql

open api 設計常見問題

服務提供

通訊方式設計

常見的設計是: 必備的有:app key 與 app secret 簽名 可選的有: 企業ID, 業務ID。 這些可選的東西通常用來區分業務領域或者權限而存在,確保不一樣調用者領域與權限的隔離。git

app key 與app secret 是我見過最多見,也是必然會出現的一個設計, 在我對接過的十多種API(阿里、騰訊、小米)中, 這這些都是必備的。github

app key 的做用

主要是爲了防止請求被篡改以及用戶識別。通常一個 app key 是用於作用戶識別的, 服務提供方通常會存放調用方的一些信息, 並能夠經過這個App key 檢索到。golang

app secret 的做用

app secret 主要是用來簽名請求的, 簽名過的請求若是被修改以後, 則簽名就會發生變化,攻擊者是不可能知道這個變化的結果的,這樣能夠有效防止攻擊者的攻擊。web

通常來說,會對以下領域進行簽名:sql

  1. parameter (url 參數)
  2. header (請求頭)

通常的流程以下:編程

  1. 用戶在請求參數信息中加入時間信息
  2. 用戶使用本身的 app secret 對請求進行簽名
  3. 用戶把簽名的結果, app key 與其餘參數一塊兒放到請求裏面傳到服務提供方
  4. 服務方根據 app key找到本身存放的 app secret, 並對請求進行簽名
  5. 比對簽名信息是否一致, 不一致,則認認爲請求受到了非法修改,直接拒絕服務。
  6. 校驗時間信息, 確保時間信息是在容許的範圍內, 不然拒絕提供服務。

如何頒發? 通常是由服務提供方生成這樣的一個鍵值對, 並把鍵值對安全的遞給調用方。 之後 app secret 不會出如今網絡傳輸中,只會用於雙方的簽名校驗。api

數據格式與文檔

  1. 服務地址, 須要準確無誤的告訴服務提供方的調用地址
  2. 環境設置 (通常API提供方會提供線上與線下兩種途徑一個用來線上使用,一個用來調試)
  3. 參數規定 (對於一個Open API通常來說參數都是固定的, 不能隨意變更,不然會引發雙方不小的爭執)
  4. 結果格式 (結果格式,要寫清楚全部出現的結果格式的可能性, 讓調用者有辦法能夠提早對任何可能的結果進行處理)
  5. 示例代碼 (一段示例代碼, 對於開發者是很友好的,大部分開發者喜歡看到這個)

注意事項

  1. 安全防範
  2. 限流

祕鑰對設計

安全防範

1. 存放攻擊

這個是最多見的一種, 它不須要知道服務提供方與三方的祕鑰, 只須要知道,雙方的通訊方式, 經過分析這種通訊方式,來達到竊取信息,或者形成攻擊的目的。 這種行爲的防範方法也是有一些的: 在請求裏面加上時間信息,對時間信息進行加密簽名, 對時間設置可用時間段, 好比1分鐘, 過了一分鐘,攻擊者獲取到的信息就不能再用了。安全

2. 祕鑰泄露

祕鑰泄露對於不少企業來講並不陌生, 一旦泄露對於雙方的危害都很大, 並且若是不能及時發現會帶來不小的損失, 這種人爲泄漏, 不少時候並無太好的辦法。 能夠作的事情比較現實的就是更換祕鑰。服務器

3. 穿透攻擊

調用服務方提供的Open API的三方合做者, 有可能對服務提供方形成壓力過大的攻擊, 這種攻擊主要來源於兩種途徑:

  1. 三方合做者應用程序問題致使請求放大, 致使的調用次數太高
  2. 三方合做者把相關接口直接暴露到外部, 這樣會帶來潛在的問題, 在攻擊者識別這樣的接口以後,瘋狂發起攻擊, 服務提供方的服務器會承受巨大壓力,甚至crash

對於穿透的攻擊: 服務提供方能作的最多見的方案就是限流, 另外就是流量識別, 在流量異常的時候對API接口自己作一些限制

固然安全防範確實還有不少須要考慮的點,通常都會結合攻擊的特色, 對於攻擊類型進行定製防範, 對於業界常見的防範措施,通常都是要默認加入的。

我的的開源項目

openapi 它主要解決的問題是: 簡化服務端提供API的流程

  1. 封裝服務端API簽名流程
  2. 封裝服務端頒發鍵值對的流程
  3. 封裝服務端簽名校驗機制
  4. 提供簡單的安全防範

這個工具是用golang設計的, 所以只適用於golang的項目。 因爲這個工具是基於 http.Request 進行設計的, 所以理論上講兼容全部的web框架, 如 gin, iris, beego等, 我在readme裏面提供了iris的示例代碼

若是使用mysql來存放app key 和secret信息, 能夠建以下的一個表

CREATE TABLE `app` (
  `app_key` varchar(32) NOT NULL,
  `app_secret` varchar(128) NOT NULL,
  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`app_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
複製代碼

若是已經存在這樣一個相似的表也能夠不用創建,在代碼中指定便可

r, err := openapi.CheckValid(req,
	// default implementation is via sql, to fetch the secrect
	    openapi.SqlSecretKeeper{
            Db:        store.GetDb(),   // 可使用的 mysql 鏈接
            TableName: "app",       // 存放app key 和secrets的表名
            KeyCol:    "app_key",   // app key 的列名
            SecretCol: "app_secret", // app secret的列名
            AppKey:    k,           // 用戶使用的 app key
	})
複製代碼

固然若是您已經封裝好了app key 與secret的邏輯, 也能夠本身實現以下接口

// the interface to get the secret
type SecretKeeper interface {
	GetSecret() (string, error)
}

複製代碼

對於使用了web 框架的,只須要寫一個middleware, 並啓用就好了, 示例代碼以下:

建立middleware

// create a middle ware for iris
func OpenApiHandler(ctx iris.Context) {

    //sign header? to prevent header being modified by others
    // openapi.SignHeader(true)

	req := ctx.Request()
	// you can put the key somewhere in the header or url params
	k := ctx.URLParam("app_key")
	r, err := openapi.CheckValid(req,
	// default implementation is via sql, to fetch the secrect
	    openapi.SqlSecretKeeper{
            Db:        store.GetDb(),
            TableName: "app",       // the name of table where you store all your app keys and secretcs
            KeyCol:    "app_key",   // the column name of the app keys
            SecretCol: "app_secret", // the column name of the app secrets
            AppKey:    k,           // the app key that the client used
	})
	logError(err)
	if r {
	    // verfy success, continue the request
		ctx.Next()
	} else {
	    // verify fail, stop the request and return
		ctx.Text(err.Error())
		ctx.StopExecution()
		return
	}
}

複製代碼

啓用middleware

// use the middle ware somewhere
// so all the apis under this group should be
// called with signed result and app key
	openApiGroup := app.Party("/open")
	openApiGroup.Use(OpenApiHandler)
	{
		openApiGroup.Get("/app", func(ctx iris.Context) {
			ctx.Text("success")
		})
	}

複製代碼

是否是很簡單,若是文中有誤,或者缺失的內容歡迎各類批評教育。

若是您能讀到這裏, 我會感受到十分榮幸,謝謝您的關注。

謝謝支持

相關文章
相關標籤/搜索