跳至主要內容
客製化API配置
一個月前已更新

功能簡介

SAML 2 是一個標準的 SSO 協議, 並受 Windows AD 支援, 因此在 SSO 領域有著廣泛的應用,但其要求開發者具有一定的背景知識(建議閱讀官方文件),對接成本較高, 因此我們補充實現了自訂API。

自訂API是簡化的 SAML 協議,企業客戶可在現有 SSO API基礎上進行修改,按照Jodoo的要求調用服務和返回參數,並將認證後的使用者資訊返回給Jodoo,完成帳號關聯。

設定步驟

設定入口

1. 登入Jodoo帳號,進入「企業管理 > 管理工具 > 企業設定」頁面中。

2. 在「企業安全 > 單一登入」處,打開單一登入的開關,並點擊「配置」按鈕。

選擇配置方式

進入配置單一登入詳情頁中,選擇單一登入配置方式為「自訂介面」。如下所示:

設定Idp登入API

Idp 登入API,指的是透過開發人員部署所需的基礎內容,允許使用者登入系統的API。通常用於需要驗證使用者身份並且允許使用者造訪系統的場景當中。詳情可參見文件:Idp 配置說明。

在Jodoo的單一登入中,您可以根據企業自身的伺服器配置,設定登入API:

生成認證密鑰

認證密鑰指的是資訊的發送方和接收方,需要透過一個密鑰去加密和解密資料。在自訂API當中,使用者可以自訂設定認證密鑰,也可以點擊「生成密鑰」按鈕,直接生成一串認證密鑰。如下所示:

附註

認證密鑰需要與程式碼中的 SECRET 保持一致。

選擇認證加密算法

Jodoo的單一登入中,支援以下 3 種加密算法,您可以根據自己的需求來選擇合適的加密算法,從而完成單一登入配置:

  • HS256

  • HS384

  • HS512

設定Issuer URL

Issuer URL 用於驗證請求內容與服務後台是否能夠匹配成功,若匹配成功則可以進行解析,否則將請求失敗。您可自訂 Issuer URL 的內容。如設定為: Issuer.test。

附註

若設定了 Issuer URL,則 Issuer URL 中的內容需要與程式碼中的 Issuer 保持一致。

設定登出API

Idp 登出API,是指當企業成員造訪了Jodoo的單點登出地址時,Jodoo不僅會登出當前成員,同時還會將成員重定向至 Idp 並攜帶登出請求參數。

附註

1. IdP 可以銷燬與此成員的會話以實現單點登出的效果。

2. 單點登出請求參數格式與認證請求參數一致, 並額外包含 jti 或 nameId 參數, 但不包含 state 欄位, 在 Token 中的 type 為常量「slo_req」。

程式碼範例

絕大多數編程語言都有較為良好的 JWT 算法實現, 第三方庫列表可在這個頁面中查找,下面給出以 Python 程式碼Golang 程式碼 實現 IdP 配置 的簡單範例:

Golang Demo

 package main   import (    "fmt"    "github.com/dgrijalva/jwt-go"    "log"    "net/http"    "time" )   const (    acs      = "https://www.jodoo.com/sso/custom/5cd91fe50e42834f41b7c6ef/acs"    issuer   = "com.example"    username = "angelmsger"    secret   = "jd" )   func ValidBody(body jwt.MapClaims) bool {    return body["iss"] == "com.jodoo" && body["aud"] == issuer && body["type"] == "sso_req" }   func ValidToken(query string) bool {    token, err := jwt.Parse(query, func(token *jwt.Token) (interface{}, error) {       if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {          return nil, fmt.Errorf("Unexpected Signing Method: %v ", token.Header["alg"])       }       return []byte(secret), nil    })    if err != nil {       return false    }    claims, ok := token.Claims.(jwt.MapClaims)    return ok && token.Valid && ValidBody(claims) }   func GetTokenByUsername(username string) (string, error) {    now := time.Now()    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{       "type":     "sso_res",       "username": username,       "iss":      issuer,       "aud":      "com.jodoo",       "nbf":      now.Unix(),       "iat":      now.Unix(),       "exp":      now.Add(1 * time.Minute).Unix(),    })    return token.SignedString([]byte(secret)) }   func BuildResponseUri(token string, state string) string {    target := acs + "?response=" + token    if state != "" {       target += "&state=" + state    }      return target }   func handler(w http.ResponseWriter, r *http.Request) {    query := r.URL.Query()    reqToken := query.Get("request")    if ok := ValidToken(reqToken); ok {       if resToken, err := GetTokenByUsername(username); err == nil {          target := BuildResponseUri(resToken, query.Get("state"))          http.Redirect(w, r, target, http.StatusSeeOther)       }       w.WriteHeader(404)    }    w.WriteHeader(404) }   func main() {    http.HandleFunc("/sso", handler)    log.Fatal(http.ListenAndServe(":8080", nil)) }  

Python Demo

from datetime import datetime, timedelta from flask import Flask, abort, redirect, request import jwt   from jwt import InvalidTokenError     class Const:     ACS = 'https://www.jodoo.com/sso/custom/5cd91fe50e42834f41b7c6ef/acs'     SECRET = 'jdy'     ISSUER = 'com.example'     USERNAME = 'angelmsger'     app = Flask(__name__)     def valid_token(query):     try:         token = jwt.decode(             query, Const.SECRET,             audience=Const.ISSUER,             issuer='com.jodoo'         )         return token.get('type') == 'sso_req'     except InvalidTokenError:         return False     def get_token_from_username(username):     now = datetime.utcnow()     return jwt.encode({         "type": "sso_res",         'username': username,         'iss': Const.ISSUER,         "aud": "com.jodoo",         "nbf": now,         "iat": now,         "exp": now + timedelta(seconds=60),     }, Const.SECRET, algorithm='HS256').decode('utf-8')     @app.route('/sso', methods=['GET']) def handler():     query = request.args.get('request', default='')     state = request.args.get('state')     if valid_token(query):         token = get_token_from_username(Const.USERNAME)         stateQuery = "" if not state else f"&state={state}"         return redirect(f'{Const.ACS}?response={token}{stateQuery}')     else:         return abort(404)     if __name__ == '__main__':     app.run(port=8080)  

是否回答了您的問題?