# RESTful登入(基於token鑑權)的設計例項

思無邪-machengyu發表於2017-04-06

使用場景

現在很多基於restful的api介面都有個登入的設計,也就是在發起正式的請求之前先通過一個登入的請求介面,申請一個叫做token的東西。申請成功後,後面其他的支付請求都要帶上這個token,服務端通過這個token驗證請求的合法性。這個token通常都有一個有效期,一般就是幾個小時。

比如我之前接入過一個支付寶和微信支付的通道,他們提供的api就要求先登入獲取token然後才能使用支付的api介面。

在比如微信的公眾平臺介面,關鍵的介面在使用之前都要帶access token。access_token是公眾號的全域性唯一票據,有效期為7200秒,重複獲取將導致上次獲取的access_token失效。

介面呼叫請求說明

http請求方式: GET

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

引數說明

引數 是否必須 說明
grant_type 是 獲取access_token填寫client_credential
appid 是 第三方使用者唯一憑證
secret 是 第三方使用者唯一憑證金鑰,既appsecret
返回說明

正常情況下,微信會返回下述JSON資料包給公眾號:

{"access_token":"ACCESS_TOKEN","expires_in":7200}

引數 說明
access_token 獲取到的憑證
expires_in 憑證有效時間,單位:秒
錯誤時微信會返回錯誤碼等資訊,JSON資料包示例如下(該示例為AppID無效錯誤):

{"errcode":40013,"errmsg":"invalid appid"}

什麼是JWT(json web token)

首先說它是一種規範。目的是在客戶端和服務端之間定義一種鑑權行為從而保證資料傳遞的安全性。

JWT 標準的 Token 有三個部分:

  • header
  • payload
  • signature

  • 中間用點分隔開,並且都會使用 Base64 編碼,所以真正的 Token 看起來像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

關於三個部分具體都是些什麼東西,大家自行搜尋即可,不是我這篇文章的重點。

我們說restful API中使用的token鑑權機制,大部分都是遵循JWT規範的,也就是底層都是對JWT的具體實現。

一個java實現例項

說了這麼多,該上例項了。這個例項會用到redis,spring等技術。

這個例項原出處:

RESTful登入設計(基於Spring及Redis的Token鑑權)

原專案是基於maven的。我這裡為了除錯方便把專案遷移到myeclipse中,關於如何匯入到myeclipse中,請參考:

如何在myeclipse中配置,匯入maven專案

上面的連結中對程式碼解釋的也比較清楚了。我這裡只說下如何執行測試效果。

根據本地實際情況修改mysql和redis配置

這個工程其實是個基於spring boot的專案,Spring boot 的預設配置檔案是 resources 下的 application.properties。所以我們主要是修改它。

這裡寫圖片描述

其中spring.datasource.*的配置項是mysql相關的,這裡解釋下。有人可能有疑問為什麼程式裡並沒有看到引用這些配置變數。這是因為spring boot是一種約定優於配置的開發框架。比如,

spring.datasource.username就是表示資料庫使用者名稱,你不能隨便改,框架裡就用它作為查抄依據。

同理spring.redis.*也是redis相關的配置。

上面幾個地方要根據你本地實際情況修改。修改完之後,在你的mysql中新建一個名為demo的資料庫(如果原來沒有的話)。然後執行工程中init.sql中的語句,這是為了初始化表。

執行

首先執行右鍵專案目錄,run as -> maven install,會看到類似下面的輸出:

這裡寫圖片描述

然後再執行 run as -> java application, 然後選擇程式的入口:

這裡寫圖片描述

同樣看下console有沒有錯誤,如果報錯通常是上面的配置不對,仔細檢查下。

開啟瀏覽器,輸入http://localhost:8080,顯示如下:

這裡寫圖片描述

表明執行成功。

測試下登入,

這裡寫圖片描述

登入成功,並且也成功的建立了token。

退出登入,在authorization中填寫用userId和token以”_”拼接得到的字串。

這裡寫圖片描述

有人會問為什麼authorization需要這樣的格式才能退出登入成功。下面的程式碼可以說明問題,

...
  @RequestMapping(method = RequestMethod.DELETE)
    @Authorization
    public ResponseEntity logout(@CurrentUser User user) {
        tokenManager.deleteToken(user.getId());
        return new ResponseEntity<>(ResultModel.ok(), HttpStatus.OK);
    }

@Authorization用於表示該操作需要登入後才能進行,否則會返回401錯誤。如下:

...
//驗證token
        TokenModel model = manager.getToken(authorization);
        if (manager.checkToken(model)) {
            //如果token驗證成功,將token對應的使用者id存在request中,便於之後注入
            request.setAttribute(Constants.CURRENT_USER_ID, model.getUserId());
            return true;
        }
        //如果驗證token失敗,並且方法註明了Authorization,返回401錯誤
        if (method.getAnnotation(Authorization.class) != null) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

為了驗證redis的快取有效期,我把程式碼做了一點修改,過期時間設定成1分鐘便於測試。

/**
     * token有效期(分鐘)
     */
    public static final int TOKEN_EXPIRES_MINS = 1;

public TokenModel createToken(long userId) {
        //使用uuid作為源token
        String token = UUID.randomUUID().toString().replace("-", "");
        TokenModel model = new TokenModel(userId, token);
        //儲存到redis並設定過期時間
//        redis.boundValueOps(userId).set(token, Constants.TOKEN_EXPIRES_HOUR, TimeUnit.HOURS);

        redis.boundValueOps(userId).set(token, Constants.TOKEN_EXPIRES_MINS, TimeUnit.MINUTES);
        return model;
    }

然後我先登入,等超過一分鐘再退出登入,確認會返回401錯誤。

相關文章