日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

自建三方SSO協議及配置說明

本文為您介紹自建標準的三方SSO對接協議及配置說明。

說明

獨立部署版本支持對接自建三方SSO協議,可自行完成對接。若需要緊急支持專家指導,請聯系Quick BI運營負責人

背景信息

自建標準的三方SSO對接,主要針對于Quick BI與客戶的自有登錄系統的賬戶對接認證。

很多客戶,賬戶登錄系統并沒有提供標準的行業登錄規范(例如:OAuth2/SAML/LDAP等協議),還是采用自有的協議。為了降低業務系統與客戶的SSO server 的對接成本,因此設計了自有的簡單登錄規范協議,如下:

  • 降低業務側與客戶SSO server的對接成本(一個協議,支持眾多客戶)。

  • 降低企業的對接成本(不用開發支持標準的行業登錄認證規范協議、提供jar支持)。

應用場景

三方系統根據Quick BI的協議說明提供相關接口或配置,實現以下場景:

  • 支持同根域跨域的登錄場景對接。

  • 同域支持同步登錄登出。

  • 跨域支持同步登出。

快速開始

1. 接入前準備

1.1 前置依賴

租戶側按照協議對接說明完成自有賬號登錄系統的開發。

1.2 網絡要求

對接前,請確保您的鑒權服務器地址和Quick BI服務地址的網絡連通性!

2. 在Quick BI進行對接配置

2.1 開啟三方SSO登錄

步驟一:使用登錄認證超管賬號進入超級管理員后臺。

image..png

若賬號無權限登錄時,會出現以下界面,提示“無權限禁止訪問”。請聯系Quick BI運維人員添加權限。image.png

步驟二: 選擇登錄系統管理->登錄全局開關,開啟/關閉指定的三方登錄方式,保存后立即生效。

image.png

2.2 標準SSO登錄對接配置

步驟一:打開登錄認證配置頁面

在運維中心->登錄策略配置或開放平臺->登錄認證位置打開。兩個位置打開的配置效果是一樣的,下邊以在運維中心打開對應頁面為例,頁面如下:

image.png

步驟二:添加登錄策略

若您已配置過登錄策略,則跳過步驟二。

每個登錄策略都允許配置一套域名/IP攔截策略,用來指定訪問指定域名或IP時Quick BI支持的登錄方式。

登錄策略配置請參見自定義企業登錄門戶

步驟三:選中需要開啟標準SSO登錄的策略,并點擊編輯。

image.png

步驟四:在「編輯策略」頁面,選擇基礎設置。

image.png

步驟五:配置標準SSO三方登錄

在您進行標準SSO三方登錄配置前,若您未開啟Quick BI賬號,建議開啟,原因為:1)防止三方登錄設置錯誤后,無法登錄Quick BI;2)自定義賬號配置成功且能正常登錄后,可根據需要關閉Quick BI賬號。

  • Quick BI賬號配置頁面如下。

    相關配置項請參見內置賬號配置說明

    image.png

  • 開啟標準SSO登錄

    image.png

  • 在彈出窗口填寫標準SSO的配置信息,

image.png

其中,標準SSO協議相關的配置項及說明如下:

具體的接口參數請參見接口參數規范

配置項

是否必填

說明

參考值

系統名稱

對接的系統名稱,用于在登錄項的按鈕組標題顯示。

三方系統SSO

系統圖標

對接的系統圖標,用于在登錄項中顯示圖標,最大32KB。

登錄態名稱

跨域場景下,為登錄態參數的名稱,其他場景下為 Cookie 名稱。

user_ticket

是否跨域

可設置為跨域或不跨域。

跨域或不跨域

登錄地址

三方系統登錄頁全稱,必需攜帶回跳參數,并以 = 結尾。

http://dev.sso.aliyun.test:XXXX/login.htm?redirectUrl=

登出地址

分兩種場景:

1:不跨域,為三方登出頁的地址,必需攜帶回跳參數,并以 = 結尾

2:跨域,登出接口的地址,參考跨域登出接口規范

http://dev.sso.aliyun.test:XXXX/logout.do?redirectUrl=

登錄態校驗接口

(請參見接口參數規范

三方提供的開放接口,

用于校驗登錄態的有效性。GET 方法。

http://dev.sso.aliyun.test:XXXX/valid

用戶信息接口

(請參見接口參數規范

三方提供的開放接口,

用于獲取登錄態用戶信息。GET 方法。

http://dev.sso.aliyun.test:XXXX/queryUser

簽名密鑰-公鑰ak

(請參見簽名校驗

接口的簽名公鑰;如不填寫,則接口不攜帶加簽參數。

123xxxxxx

簽名密鑰-私鑰sk

(請參見簽名校驗

接口的簽名密鑰;如不填寫,則接口不攜帶加簽參數。

abcxxxxhijklmn

Session失效時間

指定登錄態Session的失效時間,失效后,重新發起登錄校驗。單位秒,默認86400。

86400

步驟六: 保存發布

重要
  • 保存發布后立即生效,請慎重操作。

  • 推薦新建無痕模式窗口或者打開其他瀏覽器測試登錄配置是否成功,以防退出登錄后由于登錄配置錯誤導致無法登錄。

image.png

3.登錄驗證

在您完成標準SSO登錄對接配置后,請訪問Quick BI服務地址,并點擊標準SSO登錄,進行登錄驗證。

image..png

為了防止其他因素干擾,推薦新建無痕窗口用來測試登錄。

需要注意的是所有的無痕窗口共用cookies,通過關閉窗口清空登錄態時需要保證所有無痕窗口被關閉。

登錄對接成功效果:當您登錄后,出現以下頁面,則說明已對接成功。 拋錯原因是您當前登錄的三方賬號還未同步到Quick BI組織中,鑒權不通過,此時,您需要進行三方賬號同步,請參見獨立部署:三方賬號同步方案

image..png

4.登錄常見問題

4.1 AE0580800018 權限不足禁止訪問,請聯系組織管理員添加到具體組織

無組織用戶默認無法訪問Quick BI,需要先將三方賬號添加到Quick BI,請參見獨立部署:三方賬號同步方案

image..png

4.2. Quick BI沒有調用登錄態校驗接口

  1. 跨域場景下,回跳到Quick BI的鏈接中沒有ticket或者ticket參數名/格式錯誤。

  2. 同域場景下

    1. cookies的域名和Qucik BI的域名沒有滿足同源。

    2. cookies的名稱和策略中配置的[登錄態名稱]不一致。

    3. cookies的屬性設置問題:

      1. HttpOnly開啟后http無法獲取https的cookie。

      2. SameSite屬性設置不為None。

4.3. Quick BI請求三方接口失敗,例如獲取登錄態或者用戶信息失敗

請聯系運維同學查看日志報錯。

協議對接說明

1.前提條件

  • 三方在登錄時返回的自身的userId,accountName和nick必須保持唯一性

  • Quick BI調用三方接口的默認配置,三方可以按以下方式:

  • Content-Type:application/json

  • 傳參方式:url+body同時傳參

2.流程圖

根據以上說明,提供同域和跨域方案,以供不同場景選擇。

2.1同根域方案

業務側系統與單點登錄系統(SSO server) 保持同樣的根域名,SSO server將登錄態Cookie的域名設置為根域名下,通過瀏覽器帶入到業務側系統,以實現登錄態的共享。

以公共云Quick BI為例,Quick BI域名為bi.aliyun.com,阿里云登錄域名為account.aliyun.com;阿里云的登錄態Cookie:login_aliyun_ticket的域名為.aliyun.com,子域名都可以獲取這個Cookie。流程如下:

1.登錄態Cookie的注入和注銷清除,統一由三方SSO負責,業務側不負責,只負責拿到登錄態Cookie進行有效性校驗和身份校驗。

2.登錄態Cookie名稱自定義,由SSO server自定義,請參見配置標準SSO三方登錄

3.因為是同根域,故支持同步登入和登出。

  • 登錄流程

    未登錄用戶訪問Quick BI頁面,會重定向到三方登錄頁面,登錄并寫入Cookie后跳轉回Quick BI自動登錄。

    1. 三方訪問Quick BI頁面:http(s)//:xxx.qbi.com/home

  1. 未在cookies中拿到ticket,跳轉三方登錄鏈接

image.png

登出流程

業務側會調用SSO server的登出地址,SSO server負責清理掉登錄態Cookie實現登出。

image.png

2.2 跨域方案

存在部分場景,業務側系統與SSO server根域名不一致,例如,使用IP的方式訪問服務(常見于部分國企或者政府機構)。因此,無法通過Cookie的方式做到登錄態的共享。跨域方案有如下流程:

  1. 登錄態業務側系統自行維護。身份校驗成功之后,登錄層維護了自有的登錄態Cookie:x_login_ck。x_login_ck的過期時間,以及對應的Session過期時間,支持配置。

  2. 跨域特性,不支持同步登入登出。但預留了接口,可以在登出的時候調用。支持雙向的登出接口(業務側調SSO server/SSO server 掉業務側登出)。提供登出的安全校驗機制,需要SSO server按照規范實現。

登錄流程

未登錄用戶訪問Quick BI頁面,會重定向到三方登錄頁面,登錄后攜帶登錄態參數重定向回Quick BI自動登錄。

image.png

登出流程

因為跨域,登出的方式只能通過接口調用的方式支持,分為以下兩種情況

  • 三方系統登出后,調用Qucik BI接口實現同步登出

  • Qucik BI登出后調用三方系統接口進行同步登出

接口的安全校驗機制,請參見下圖:

image.png

3.接口參數規范

3.1登錄態校驗接口(必須)

接口描述:通過ticket,校驗登錄態的有效性,如果有效,返回對應的userId信息。

接口API:接口路徑自定義 (GET)

接口參數

參數名

類型

是否必選

說明

ticket

string

登錄態ticket的參數。

accessKey

string

/

簽名的ak,用于安全校驗。

若配置了accessKey, 請求API時自動帶上,客戶系統決定是否消費。

timestamp

string

/

請求的時間戳,用于安全校驗。

若配置了accessKey, 請求API時自動帶上,客戶系統決定是否消費。

nonce

string

/

隨機字符串,用于重放攻擊的安全校驗。

若配置了accessKey, 請求API時自動帶上,客戶系統決定是否消費

signature

string

/

簽名字符串,用于安全校驗。

若配置了accessKey, 請求API時自動帶上,客戶系統決定是否消費。簽名算法參考以下說明

請求示例:

GET
http://bi.douson.com/ticket/valid?ticket=c5f5628-21db-446b-8226-e76291e99380&timestamp=1610703757345&nonce=e76291e99380&signature=LAtufg1ssx-1Addkddl

返回參數

參數名

類型

是否必選

說明

code

string

接口狀態碼,不實際消費,僅用于日志提示,例如出錯時錯誤碼。

message

string

接口狀態信息,不實際消費,僅用于日志提示,例如出錯時錯誤碼。

success

boolean

標記接口請求狀態。

  • true:標識接口請求成功(非登錄態校驗)

  • false:接口請求失敗

data

json

登錄態校驗信息。

|__isLogin

boolean

登錄態是否有效。

  • true:有效。

  • false:無。

|__userId

string

isLogin=true時必需

用戶唯一ID。isLogin=true時必須返回。

success=true && isLogin=true時Quick BI消費,用于獲取用戶信息。

|__redirectUrl

string

重定向地址。isLogin=false有效,用于自定義重定向。

success=true && isLogin=false時,登錄層消費(如果有)重定向跳轉。

# 登錄態校驗成功結果返回案例
{
    "code":"200",       
    "message":"獲取成功",  
    "success":true,    // 請求成功,
    "data":{             
        "isLogin":true,   // 登錄態校驗有效
        "userId":"1089987878",   // 登錄態校驗有效,返回登錄態對應的用戶ID
        "redirectUrl":""
    }
}

# 登錄態校驗失敗結果返回案例
{
    "code":"400",       
    "message":"校驗失敗",  
    "success":true,    // 請求成功,
    "data":{             
        "isLogin":false,   // 登錄態校驗失敗
        "userId":"",  
        "redirectUrl":"http://aliyun.com/xxxxx"  // 按照該重定向地址跳轉
    }
}
重要

  • isLogin不要序列化成login。

  • Quick BI存量三方SSO對接客戶。配置的API存在兩種形式,Quick BI直接把參數拼接在配置的URL后。對于這類歷史存量客戶,參考配置:standard.sso.is.old.version = true

  • 攜帶URL參數。例如http://aaa.com/valid?userToken=。如果切換,接口新增一個ticket參數即可。

  • 路徑參數。例如http://aaa.com/valid/ 。 如果切換,需要新增一個接口,并且從參數ticket中解析。

3.2獲取賬戶登錄信息接口(必須)

接口描述:通過userId,獲取具體的用戶信息。

接口API:接口路徑自定義 (GET)

接口參數

參數名

類型

是否必選

說明

userId

string

用戶唯一ID。

accessKey

string

/

簽名的ak,用于安全校驗。

若配置了accessKey,請求API時自動帶上,客戶系統決定是否消費。

timestamp

string

/

請求的時間戳,用于安全校驗。

若配置了accessKey,請求API時自動帶上,客戶系統決定是否消費。

nonce

string

/

隨機字符串,用于重放攻擊的安全校驗。

若配置了accessKey,請求API時自動帶上,客戶系統決定是否消費。

signature

string

/

簽名字符串,用于安全校驗。

若配置了accessKey,請求API時自動帶上,客戶系統決定是否消費。

示例1:

GET
http://bi.douson.com/query/userinfo?userId=c5f5628-21db-446b-8226-e76291e99380&timestamp=1610703757345&nonce=e76291e99380&signature=LAtufg1ssx-1Addkddl

返回參數

參數名

類型

是否必選

說明

code

string

接口狀態碼,不實際消費,僅用于日志提示,例如出錯時錯誤碼。

message

string

接口狀態信息,不實際消費,僅用于日志提示,例如出錯時錯誤碼。

success

boolean

標記接口請求狀態。

  • true:標識接口請求成功。

  • false:接口請求失敗。

data

json

用戶賬戶信息字段。

|__userId

string

用戶唯一ID,必需且唯一。

|__userName

string

用戶賬戶名,必需且唯一。

|__nick

string

用戶賬戶的顯示名稱。必需且唯一。

|__userEmail

string

用戶賬戶郵箱。

|__userPhone

string

用戶賬戶電話。

|__extraInfo

Map<String,String>

其他擴展字段,自定義。

透傳到業務系統,登錄層不做消費。

{
    "code":"200",
    "message":"獲取成功",
    "success":true,
    "data":{
        "userId":"1089987878",
        "userName":"alibaba",
        "nick":"阿里巴巴測試賬號",
        "extraInfo":{
            "tag":"自定義擴展字段"
        }
    }
}
重要

  • Quick BI存量三方SSO對接客戶。配置的API存在兩種形式,Quick BI直接把參數拼接在配置的URL后。

  • 攜帶URL參數。例如http://aaa.com/getUserInfo?userId=。如果切換,接口新增一個userId參數即可。

  • 路徑參數的。例如http://aaa.com/getUserInfo/ 。如果切換,需要新增一個接口或者參數,并且從參數ticket中解析。

3.3 SSO server登出,通知業務同步登出接口(可選)

接口調用方:SSO server

接口描述:用于跨域場景下,業務側(例如Quick BI)被動接收登出的通知。SSO server在收到登出的通知的時候調用該接口,通知業務側系統做同步登出。?

接口API: /auth_sso/login/crossDomain/logout.do(POST) Content-Type:application/x-www-form-urlencoded;charset-utf8

接口參數

參數名

類型

是否必選

說明

accountId

string

需要登出的賬戶ID。

accessKey

string

簽名的ak,用于安全校驗。

timestamp

string

請求的時間戳,用于安全校驗。

nonce

string

隨機字符串,用于重放攻擊的安全校驗。

signature

string

簽名字符串,用于安全校驗。

返回參數

{
    "code":"200",
    "message":"獲取成功",
    "success":true,
    "data": true,   // 登出清理是否成功
    "traceId": "xxxxxxxxxxxx" // 請求唯一ID
}

3.4 業務側登出,通知SSO server 側同步登出接口規范(可選)

接口調用方:業務側(例如Quick BI), 接口可選。

接口描述:用于跨域場景。業務側登出,業務清理掉自身登錄態Cookie后,調用配置的SSO server 該接口。SSO server收到通知后,做同步登出操作。該接口需要SSO server 按照標準協議規范開發并提供配置過來。

接口API:接口路徑自定義 (POSTContent-Type:application/x-www-form-urlencoded;charset-utf8

接口參數

表單參數

類型

是否必選

說明

userId

string

用戶賬戶的唯一ID。

accessKey

string

/

簽名的ak,用于安全校驗。

請求API時自動帶上,客戶系統決定是否消費。

timestamp

string

/

請求的時間戳,用于安全校驗。

請求API時自動帶上,SSO server決定是否消費。

nonce

string

/

隨機字符串,用于重放攻擊的安全校驗。

請求API時自動帶上,SSO server決定是否消費

signature

string

/

簽名字符串,用于安全校驗。

請求API時自動帶上,SSO server決定是否消費。

返回參數

{
    "code":"200",
    "message":"獲取成功",
    "success":true,  // 用于判斷接口是否調用成功
    "data": true,    // 用于判斷登出是否執行成功
}

4.簽名校驗

4.1 接口安全加簽規則 & SDK算法

簽名采用行業通用的簽名算法,簽名算法如下:

image.png

主體簽名分成兩個步驟:

  1. 構建待簽名字符串string_to_sign

  2. 使用密鑰(secret_key)對拼接的string_to_sign進行簽名操作,加密算法使用HMAC-SHA256base64

4.2 構建待簽名字符串(string_to_sign)

拼接待簽名字符串string_to_sign的拼接規則如下:

string_to_sign = 
    Request_Method\n
    Request_Uri\n
    Request_QueryString\n

上述各部分,以換行符\n分割連接(非字符串"\n"),各部分說明如下:

表1-1 待簽名字符串拼接說明

拼接部分

說明

示例

Request_Method

http方法名:GET | POST | PUT | DELETE

格式為大寫。

GET

Request_Uri

原始請求的相對路徑,不包含host和URL請求參數。

重要

如果uri中攜帶符號+,則需要先將+替換成空格。

/

Request_QueryString

主要由請求中所有Query參數、所有表單參數拼接而成。

拼接規則:

  • 對請求參數名按照字典順序從小到大排序,然后拼接。拼接時,每組鍵值對用"="鏈接,鍵值對之間,用"&"連接。

說明

  • 對于參數鍵值對,如果參數名或者參數值為空時,不參與拼接。

  • 對于表單參數,可能存在同一個參數有多個值的情況。此時先要將value進行字母排序,中間以英文逗號分割開,拼接成一個value。當做一組參數鍵值對。

  • signature不參與字符串拼接。

對于請求:http://abc.test/openapi/v2/user?status=3&pageNo=1&pageSize=10&key=

其中,Query參數鍵值對:

  • status=3

  • pageNo=1

  • pageSize=10

  • key=

按照參數名排序,順序為:

key->pageNo->pageSize->status

由于key的參數為null,因此不加入拼接,故拼接的Request_QueryString如下:

pageNo=1&pageSize=10&status=3

4.3 生成簽名字符串(signature)

生成了待簽名字符串后,使用secret_key私鑰對待簽名字符串進行加密,生成最終的簽名(signature)。具體的簽名規則如下:

signature = HMAC-SHA256-BASE64(sk, percentURLEncode(string_to_sign))

其中,需要說明的是:

  • percentURLEncode:表示待簽名字符串的編碼以及特殊字符的處理。編碼規則如下:

    • 對于字符 A~Z、a~z、0~9 以及字符、短劃線(-)、下劃線(_)、半角句號(.)、波浪線(~)不編碼。

    • 對于其它字符編碼成 %XY 的格式,其中 XY 是字符對應 ASCII 碼的 16 進制表示。例如英文的雙引號(”)對應的編碼為 %22。

    • 對于擴展的 UTF-8 字符,編碼成 %XY%ZA… 的格式。

    • 英文空格( )要編碼成 %20,而不是加號(+)。

該編碼方式和一般采用的application/x-www-form-urlencoded MIME 格式編碼算法(比如 Java 標準庫中的 java.net.URLEncoder 的實現)相似,但又有所不同。實現時,可以先用標準庫的方式進行編碼,再把編碼后的字符串中加號(+)替換成 %20、星號(*)替換成 %2A%7E替換回波浪線(~),即可得到上述規則描述的編碼字符串。這個算法可以用下面的percentEncode方法來實現。

   private  static String percentEncode(String value) throws UnsupportedEncodingException {
        return value != null ? URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
    }

  • HMAC-SHA256-BASE64:表示先進行SHA256編碼,而后采用BASE64對生成的結果進行加密。

4.4 JAVA方式實現簽名校驗

目前,統一登錄層提供了開發jar包,可以直接使用。jar包下載地址:sso-signature-1.1.0-SNAPSHOT.jar

如果是集團域內環境下,可以直接引用maven:

        <dependency>
            <groupId>com.alibaba.quickbi</groupId>
            <artifactId>sso-signature</artifactId>
            <version>1.1.0-SNAPSHOT</version>
        </dependency>

jar包中主要包含了三個類:

  • NonceUtil:隨機字符串生成工具類。

  • SignatureUtil:簽名生成工具類。主要用于生成簽名方式。例如,3.2.3需要調取業務側的跨域登出接口,涉及簽名校驗。

  • RequestSignatureUtil:http請求的簽名校驗工具類。

主要用于解析待簽名的請求,例如:3.2.1/3.2.2/3.2.4接口,調用SSO server提供的接口,SSO server通過RequestSignatureUtil.validRequestSignature() 校驗請求的合法性。

SignatureUtil類如下:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * 簽名生成組件
 *
 * @author gengnan.wy
 * @date 2021-01-18
 */
public class SignatureUtil {

    /**
     * 構建待簽名的字符串
     *
     * @param uri        請求的URI
     * @param method     方法,GET、POST
     * @param parameters 參與簽名的請求參數。
     * @return
     */
    public static String buildStringToSign(String uri, String method,
                                           Map<String, String> parameters) {
        if (null == uri || "".equalsIgnoreCase(uri.trim())
            || null == method || "".equalsIgnoreCase(method.trim())) {
            throw new IllegalArgumentException("input parameter error, uri or method can not be null");
        }
        // URL中。按照 原始符號 --> 瀏覽器URL編碼 --> spring web解析接收,對于 加號(+)和空格, 有如下問題:
        // +   -> %2B --> 空格;
        // 空格 -> %20 --> 空格;因此,對于spring web接收到的請求,并不清楚空格的原始對應,是 + 還是空格
        // 因此,此處對于源頭所有的+,按照空格處理
        uri = uri.replace("+", " ");

        // method
        StringBuilder sb = new StringBuilder();
        sb.append(method.toUpperCase());
        sb.append("\n");

        // uri
        sb.append(uri);
        sb.append("\n");

        // paramters
        if (null != parameters && parameters.size() > 0) {
            String queryString = buildSortedString(parameters, "=", "&");
            if (null != queryString) {
                sb.append(queryString);
                sb.append("\n");
            }
        }
        return sb.toString();
    }

    /**
     * 簽名函數
     *
     * @param stringToSign :待簽名的字符串
     * @param secretKey    簽名加密的密鑰
     * @return
     */
    public static String sign(String stringToSign, String secretKey) {
        if (null == stringToSign || null == secretKey) {
            throw new IllegalArgumentException("input parameter error");
        }
        try {
            String encodeString = percentEncode(stringToSign);
            return sha256(encodeString, secretKey);
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalStateException("error in encode string");
        }

    }

    private static String percentEncode(String value) throws UnsupportedEncodingException {
        return value != null ? URLEncoder.encode(value, "utf-8")
            .replace("+", "%20")
            .replace("*", "%2A")
            .replace("%7E", "~") : null;
    }

    /**
     * 將map中的元素,按照key的字母順序,進行排序
     *
     * @param maps
     * @return
     */
    private static String buildSortedString(Map<String, String> maps, String symbol1, String symbol2) {
        StringBuilder sb = new StringBuilder();
        List<String> keys = new LinkedList<String>();
        for (String key : maps.keySet()) {
            keys.add(key);
        }
        Collections.sort(keys);

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = maps.get(key);
            // key或者value為空或null,不加入字符串的拼接
            if (null == key || key.trim().length() == 0
                || null == value || value.trim().length() == 0) {
                continue;
            }
            sb.append(key);
            sb.append(symbol1);
            sb.append(value);
            if (i != keys.size() - 1) {
                sb.append(symbol2);
            }
        }
        return sb.toString();
    }

    /**
     * sha256加密處理
     *
     * @param content 待加密字符串
     * @param secret  密鑰
     * @return
     */
    public static String sha256(String content, String secret) throws NoSuchAlgorithmException,
        UnsupportedEncodingException, InvalidKeyException {
        Mac hamcSha256 = Mac.getInstance("HmacSHA256");
        byte[] keyBytes = secret.getBytes("UTF-8");
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256");
        hamcSha256.init(secretKey);
        byte[] result = hamcSha256.doFinal(content.getBytes("UTF-8"));
        return new String(Base64.getEncoder().encode(result));
    }
}

RequestSignatureUtil如下:

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Http請求的簽名校驗工具類
 *
 * @author gengnan.wy
 * @date 2021-01-18
 */
public class RequestSignatureUtil {

    private static final String SIGNATURE_PARAM_NAME = "signature";
    private static final int NONCE_SIZE = 16;

    /**
     * 校驗request請求的合法性。
     *
     * @param request
     * @return
     */
    public static boolean validRequestSignature(HttpServletRequest request, String sk) {
        if (null == request || null == sk) {
            return false;
        }

        String uri  = request.getRequestURI();
        String method = request.getMethod();
        Map<String, String> parameters = extractQueryParameters(request);
        String requestSign = "";
        if (!parameters.containsKey(SIGNATURE_PARAM_NAME)) {
            return false;
        } else {
            requestSign = parameters.get(SIGNATURE_PARAM_NAME);
            parameters.remove(SIGNATURE_PARAM_NAME);
        }
        String stringToSign = SignatureUtil.buildStringToSign(uri, method, parameters);
        String sign = SignatureUtil.sign(stringToSign, sk);
        if (sign.equalsIgnoreCase(requestSign)) {
            return true;
        }
        return false;
    }

    /**
     * 提取請求參數信息;如果一個參數具有多個參數值,則將多個參數值按照字母順序,從小到大排序,以英文逗號連接;
     *
     * @param request
     * @return
     */
    private static Map<String, String> extractQueryParameters(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        Map<String, String> result = new HashMap<String, String>();
        if (null == parameterMap || parameterMap.size() == 0) {
            return result;
        }
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String key = entry.getKey();
            String[] values = entry.getValue();
            // 參數為空的,不加入簽名計算
            if (null == values || values.length == 0) {
                continue;
            }
            if (values.length == 1) {
                result.put(key, values[0]);
            } else {
                String value = sortArraysToString(values, ",");
                result.put(key, value);
            }
        }
        return result;
    }

    /**
     * 按照字典順序排序
     *
     * @param arrays
     * @return
     */
    private static String sortArraysToString(String[] arrays, String sep) {
        List<String> list = Arrays.asList(arrays);
        Collections.sort(list);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i));
            if (i != list.size() - 1) {
                sb.append(sep);
            }
        }
        return sb.toString();
    }
}