使用場景
現在很多基於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
- 1
參數說明
參數 是否必須 說明
grant_type 是 獲取access_token填寫client_credential
appid 是 第三方用戶唯一憑證
secret 是 第三方用戶唯一憑證密鑰,既appsecret
返回說明
正常情況下,微信會返回下述JSON數據包給公眾號:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
- 1
參數 說明
access_token 獲取到的憑證
expires_in 憑證有效時間,單位:秒
錯誤時微信會返回錯誤碼等信息,JSON數據包示例如下(該示例為AppID無效錯誤):
{"errcode":40013,"errmsg":"invalid appid"}
- 1
什么是JWT(json web token)
首先說它是一種規范。目的是在客戶端和服務端之間定義一種鑒權行為從而保證數據傳遞的安全性。
JWT 標准的 Token 有三個部分:
- header
- payload
- signature
-
中間用點分隔開,並且都會使用 Base64 編碼,所以真正的 Token 看起來像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
- 1
關於三個部分具體都是些什么東西,大家自行搜索即可,不是我這篇文章的重點。
我們說restful API中使用的token鑒權機制,大部分都是遵循JWT規范的,也就是底層都是對JWT的具體實現。
一個java實現實例
說了這么多,該上實例了。這個實例會用到redis,spring等技術。
這個實例原出處:
RESTful登錄設計(基於Spring及Redis的Token鑒權)
原項目是基於maven的。我這里為了調試方便把項目遷移到myeclipse中,關於如何導入到myeclipse中,請參考:
上面的鏈接中對代碼解釋的也比較清楚了。我這里只說下如何運行測試效果。
根據本地實際情況修改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); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
@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; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
為了驗證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; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
然后我先登錄,等超過一分鍾再退出登錄,確認會返回401錯誤。