開源運維CMDB實現:API認證

本文是在 基礎上, 講功能模塊:API認證的具體實現,方便大家可以按需擴展或者實現自己的認證方法。

因為CMDB實現是前後端完全分離的,客戶端向API服務發起請求時,服務需要驗證客戶端的身份是否合法。HTTP API常見的認證方法有以下幾種:

  • API Key + Secret
  • Cookie-Session
  • JWT(JSON Web Token)
  • OAuth

本系統實現了前3種認證方式,下面講講實現過程,並不會分析這幾種認證方式的優缺點,網上有很多文章做了深入的對比分析。

API Key + Secret

服務端會每個用戶自動生成一個密鑰對(Key和Secret),Key相當於標識用戶的身份,Secret則是用來加密的秘鑰。實際上這種認證方式的簽名算法並沒有統一的標準,因此實現各異。本系統的實現方法如下圖,當然大家可以按需實現自己的簽名算法。

https://github.com/pycook/cmdb/blob/master/cmdb-api/api/lib/perm/auth.py

<code>def _auth_with_key():
key = request.values.get('_key')

secret = request.values.get('_secret')
path = request.path
keys = sorted(request.values.keys())
req_args = [request.values[k] for k in keys if k not in ("_key", "_secret")]

user, authenticated = User.query.authenticate_with_key(key, secret, req_args, path)
if user and authenticated:
login_user(user)
return True

return False/<code>
<code>def authenticate_with_key(self, key, secret, args, path):
user = self.filter(User.key == key).filter(User.deleted.is_(False)).filter(User.block == 0).first()
if not user:
return None, False
if user and hashlib.sha1('{0}{1}{2}'.format(
path, user.secret, "".join(args)).encode("utf-8")).hexdigest() == secret:
authenticated = True
else:
authenticated = False

return user, authenticated/<code>

簽名過程如下:

  1. 客戶端首先拿到自己的Key,Secret
  2. 請求的參數,按照參數名的字典序排列,並連接到url path + Secret之後
  3. SHA1(步驟2的字符串)
  4. HTTP請求的參數里加上_key和_secret, 其中_key就是用戶的API Key, _secret就是步驟3生成的簽名

客戶端請求籤名可以實現如下:

<code>def build_api_key(path, params):
key = "your key"
secret = "your secret"
values = "".join([str(params[k]) for k in sorted(params.keys())

if params[k] is not None]) if params.keys() else ""
_secret = "".join([path, secret, values]).encode("utf-8")
params["_secret"] = hashlib.sha1(_secret).hexdigest()
params["_key"] = key

return params/<code>

這種方法需要每次HTTP請求的時候都要進行簽名!

Cookie-Session

這種認證方法適用於web客戶端即瀏覽器請求API的場景。其認證的過程大致如下:

  1. 用戶在登錄頁面輸入用戶名、密碼後,密碼取md5值,點登錄POST請求給API認證服務
  2. 服務收到認證請求後,驗證用戶是否合法,如果不合法返回401錯誤,否則使用HMAC-SHA1對用戶信息進行簽名
  3. 服務響應登錄請求,會把步驟2生成的帶有過期時間的session寫進瀏覽器的cookie中
  4. 後面的的HTTP請求都會帶上這個加密的cookie,服務便可識別用戶的身份

步驟2的簽名,是Flask使用itsdangerous來對用戶認證信息進行簽名和序列化。

簽名流程:

  1. 使用itsdangeroussession_json_serializer對session dict進行序列化生成pyload
  2. 使用 HMAC-SHA1(SECRET_KEY, salt = 'cookie-session')生成key
  3. 用第二步生成的key對第一步的序列化的session進行簽名, payload + '.' + base64_encode(HAMC-SHA1(key, payload))

JWT

JWT(JSON Web Token)是一種用以產生訪問令牌的開源標準, 簡單來說就是對JSON數據進行簽名生成Token。JWT生成的數據如下圖:


開源運維CMDB實現:API認證

JWT數據

JWT由3部分組成:

  1. base64_encode(header)
  2. base64_encode(payload)
  3. HMAC-HS256(base64_encode(header) + "." + base64_encode(payload), SECRET_KEY)

header和payload長下面這樣,都是JSON格式的數據


開源運維CMDB實現:API認證

JWT的header和payload

header一般用來存放元數據,payload會存放用戶的信息,過期時間等,但是敏感信息不能存放在payload裡,因為畢竟是base64編碼,算是明文。第三部分用HMAC-HS256進行簽名,無非是防止前面2部分數據被篡改。

CMDB裡在用戶通過認證後會生成一個JWT的Token,其代碼如下:

https://github.com/pycook/cmdb/blob/master/cmdb-api/api/views/account.py

<code>token = jwt.encode({
'sub': user.email,
'iat': datetime.datetime.now(),
'exp': datetime.datetime.now() + datetime.timedelta(minutes=24 * 60 * 7)},
current_app.config['SECRET_KEY'])/<code>

用戶認證通過後會拿到這個token,後面請求在HTTP Headers裡設置Access-Token為該值,便可以通過認證,其基於JWT的認證代碼如下:

https://github.com/pycook/cmdb/blob/master/cmdb-api/api/views/account.py

<code>def _auth_with_token():
auth_headers = request.headers.get('Access-Token', '').strip()
if not auth_headers:
return False

try:
token = auth_headers
data = jwt.decode(token, current_app.config['SECRET_KEY'])
user = User.query.filter_by(email=data['sub']).first()
if not user:
return False

login_user(user)
return True
except jwt.ExpiredSignatureError:
return False
except (jwt.InvalidTokenError, Exception):

return False/<code>


分享到:


相關文章: