系統鑑權流程及簽名生成規則

背景

QBS將來會接入更多第三方應用,當第三方應用訪問QBS系統資源時,須要通過QBS認證系統認證,認證經過以後可對QBS資源進行訪問。安全

基於公網的HTTP請求存在被攔截,篡改,重發的可能,須要對用戶請求參數信息進行有效保護。服務器

認證流程採用OAuth受權碼方式,經過受權碼獲取access_token,經過access_token結合sign方式對請求入參進行加密。app

設計原則:

  • 輕量級;
  • 適合於異構系統(跨操做系統,多語言簡易實現);
  • 易於開發;
  • 易於測試;
  • 易於部署;
  • 知足接口安全要求,無過分設計;

名詞解釋

OAuth測試

開放協議標準,容許第三方應用訪問服務提供者的資源,而不須要將用戶名,密碼提供給第三方應用加密

QBS認證系統url

獨立的QBS資源系統的認證系統,對第三方應用進行備案及受權訪問及限流/防刷操作系統

QBS資源服務器設計

題庫系統,包含試題,試卷等信息code

client_keyorm

每一個準備訪問QBS資源系統的第三方應用,須要在QBS認證系統進行備案,client_key能夠爲應用名稱

client_secret

第三方應用進行備案時生成的密鑰,用於接口請求sign的生成

redirect_url

第三方應用進行備案時配置的應用重定向地址,爲安全起見,認證經過重定向到此地址,不依賴請求中的redirect_url

code(受權碼)

第三方應用經過獲取此受權碼進而獲取access_token

access_token

第三方應用經過access_token進行QBS的資源訪問

refresh_token

當access_token過時以後,可經過refresh_token進行新的access_token獲取

sign

簽名,用於對請求入參加密,具體生成方法見後面

簡要方案

輸入圖片說明

方案詳情

應用備案

第三方受權管理配置界面,配置參數: client_key: xxx// 第三方平臺名稱 client_secret:xxx // 爲第三方平臺分配的密鑰 redirect_url: xxx.com/authxxx // QBS認證系統下發受權碼的重定向地址

受權

獲取受權碼

入參: response_type: code // 固定值 client_key: xxx// 配置約定 state: xxx // 第三方應用當前狀態,QBS認證系統不關心,原樣返回

響應參數: grant_type: authorization_code // 固定值 code: xxx // 認證系統下發的受權碼

獲取access_token

入參: grant_type: authorization_code // 固定值 code: xxx // 受權碼 client_key: xxx// 配置約定 client_secret: xxx // 配置約定

響應參數: access_token: aaa // 發放的access_token expire_in: 3600 // 過時時間 refresh_token: bbb // access_token過時後,經過refresh_token更新access_token

sign生成規則:

根據參數名=參數值(參數值首尾不能包含空格)的格式,按首字符字典順序 (ASCII值大小)升序排序。若遇到相同首字符,則判斷第二個字符。以此類推,待簽名字符串須要。

以參數名1=參數值1&參數名2=參數值2...&參數名N=參數值N的規則進行拼接。 參數首尾加上備案生成的client_secret, 最後得出的字符串進行md5Hex得出sign。

例子:

  • 有c=3,b=2,a=1 三個參
  • 把參數名和參數值連接組成字符串, a1b2c3timestamp12345678
  • 用申請獲得的client_secret連接到拼接字符串的頭部和尾部,而後進行md5Hex,最後獲得加密摘要轉換成大寫,

示例:md5(client_secreta1b2c3timestamp12345678client_secret),取得md5Hex摘要值 C5F3EB5D7DC2748AED89E90AF00081E6 。

接口入參參考:

例子:

獲取試題詳情,HTTP請求入參:

{
client_key:xxx,
access_token: xxx //生成的資源訪問access_token
sign:xxx // 加密校驗值,生成方式見下面

questionId:1 //請求核心參數
}

實體類

# ClientKey

    private int id;
    private String company;
    private String clientKey;
    private String clientSecret;
    private String redirectUrl;
    private Date updateAt;
# 受權碼

    private int id;
    private String clientKey;
    private String redirectUrl;
    private String code;
    private Date createdAt;
    private Date expiresAt;
# accessToken

    private int id;
    private String clientKey;
    private String redirectUrl;
    private String accessToken;
    private String refreshToken;
    private Date createdAt;
    private Date expiresAt;
    private Date refreshTokenExpiresAt;
# refreshToken

    private int id;
    private String clientKey;
    private String refreshToken;
    private Date refreshTokenExpiresAt;

獲取受權碼

/**
     * 獲取受權碼
     *
     * @param client_key
     * @param redirect_url
     * @param response_type
     * @param state
     * @throws IOException
     */
    @GetMapping("code")
    public RetDTO code(
            @RequestParam("response_type") String response_type, // 固定值 code
            @RequestParam("client_key") String client_key, // 配置項
            @RequestParam("redirect_url") String redirect_url, // 配置項
            @RequestParam("state") String state // 客戶端狀態,原樣返回
    ) {
        if (!StringUtils.isEmpty(response_type.trim())
                && ("code").equals(response_type.trim())
                && !StringUtils.isEmpty(client_key.trim())
                && !StringUtils.isEmpty(redirect_url.trim())) {
            try {
                AuthClientCode code = clientService.createCode(client_key, redirect_url);
                if (null != code) { // 20 分鐘
                    String redirectUrl = String.format(REDIRECT_URL, code.getRedirectUrl(), state, code.getCode());
                    logger.info("response_type: {}, client_key: {}, redirect_url: {}, code: {}", response_type, client_key, redirect_url, code);
                    return RetDTO.getSuccess(redirectUrl);
                }
            } catch (Exception e) {
                logger.error("response_type: {}, client_key: {}, redirect_url: {}, e: {}", response_type, client_key, redirect_url, e.getMessage());
                throw new BizException(e.getMessage());
            }
        }
        logger.info("response_type: {}, client_key: {}, redirect_url: {}, 獲取受權碼失敗", response_type, client_key, redirect_url);
        return new RetDTO(HttpStatus.OK.value(), "獲取受權碼失敗");
    }

獲取access_token

/**
     * 獲取access_token
     *
     * @param grant_type
     * @param code
     * @param client_key
     * @param redirect_url
     * @return
     */
    @PostMapping("access-token")
    public RetDTO accessToken(
            @RequestParam("grant_type") String grant_type, // 固定值 authorization_code
            @RequestParam("code") String code, // 受權碼
            @RequestParam("client_key") String client_key, // 配置項
            @RequestParam("redirect_url") String redirect_url // 配置項
    ) {
        // code,client_key,redirect_uri,確認無誤後,發放令牌access_token
        if (!StringUtils.isEmpty(grant_type.trim())
                && ("authorization_code").equals(grant_type.trim())
                && !StringUtils.isEmpty(code.trim())
                && !StringUtils.isEmpty(client_key.trim())
                && !StringUtils.isEmpty(redirect_url.trim())) {

            try {
                String clientCode = clientService.getCode(client_key, redirect_url);
                if("".equals(clientCode)){
                    return new RetDTO(HttpStatus.OK.value(), "備案信息無效");
                }
                if (code.equals(clientCode)) { // 受權碼有效,生成access_token
                    AuthClientAccessToken authClientAccessToken = clientService.createAccessToken(client_key, redirect_url, code);
                    logger.info("grant_type: {}, client_key: {}, redirect_url: {}, code: {}, access_token: {}", grant_type, client_key, redirect_url, code, authClientAccessToken.getAccessToken());
                    return RetDTO.getSuccess(authClientAccessToken);
                }
            } catch (Exception e) {
                logger.error("grant_type: {}, client_key: {}, redirect_url: {}, code: {}, e: {}", grant_type, client_key, redirect_url, code, e.getMessage());
                throw new BizException(e.getMessage());
            }
        }
        logger.info("grant_type: {}, client_key: {}, redirect_url: {}, code: {}, 受權碼無效", grant_type, client_key, redirect_url, code);
        return new RetDTO(HttpStatus.OK.value(), "受權碼無效");
    }

更新access_token

/**
     * 更新token
     *
     * @param grant_type
     * @param refresh_token
     * @param client_key
     * @param redirect_url
     * @return
     */
    @PostMapping("refresh-token")
    public RetDTO refresh_token(
            @RequestParam("grant_type") String grant_type, // 固定值 authorization_code
            @RequestParam("refresh_token") String refresh_token, // refresh_token
            @RequestParam("client_key") String client_key, // 配置項
            @RequestParam("redirect_url") String redirect_url // 配置項
    ) {
        // refresh_token,client_key,redirect_uri,確認無誤後,發放令牌access_token
        if (!StringUtils.isEmpty(grant_type.trim())
                && ("authorization_code").equals(grant_type.trim())
                && !StringUtils.isEmpty(refresh_token.trim())
                && !StringUtils.isEmpty(client_key.trim())
                && !StringUtils.isEmpty(redirect_url.trim())) {
            try {
                String refreshToken = clientService.getRefreshToken(client_key, redirect_url);
                if (!("").equals(refreshToken) && refresh_token.equals(refreshToken)) { // 受權碼有效,生成access_token
                    AuthClientAccessToken authClientAccessToken = clientService.createAccessToken(client_key, redirect_url, refreshToken);
                    logger.info("grant_type: {}, client_key: {}, redirect_url: {}, refresh_token: {}, 新access_token: {}", grant_type, client_key, redirect_url, refresh_token, authClientAccessToken.getAccessToken());
                    return RetDTO.getSuccess(authClientAccessToken);
                } else {
                    return new RetDTO(HttpStatus.OK.value(), "refresh_token無效");
                }
            } catch (Exception e) {
                logger.info("grant_type: {}, client_key: {}, redirect_url: {}, refresh_token: {}, e: {}", grant_type, client_key, redirect_url, refresh_token, e.getMessage());
                throw new BizException(e.getMessage());
            }
        }
        logger.info("grant_type: {}, client_key: {}, redirect_url: {}, refresh_token: {}, access_token更新失敗", grant_type, client_key, redirect_url, refresh_token);
        return new RetDTO(HttpStatus.OK.value(), "refresh_token更新失敗");
    }
相關文章
相關標籤/搜索