1. 程式人生 > >基於token的會話保持機制

基於token的會話保持機制

## session簡介 做過Web開發的程式設計師應該對Session都比較熟悉,Session是一塊儲存在伺服器端的記憶體空間,一般用於儲存使用者的會話資訊。 ![](https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=260083543,4284155072&fm=26&gp=0.jpg) 使用者通過使用者名稱和密碼登陸成功之後,伺服器端程式會在伺服器端開闢一塊Session記憶體空間並將使用者的資訊存入這塊空間,同時伺服器會 在cookie中寫入一個Session_id的值,這個值用於標識這個記憶體空間。 下次使用者再來訪問的話會帶著這個cookie中的session_id,伺服器拿著這個id去尋找對應的session,如果session中已經有了這個使用者的 登陸資訊,則說明使用者已經登陸過了。 使用Session保持會話資訊**使用起來非常簡單,技術也非常成熟**。但是也存在下面的幾個問題: - 伺服器壓力大:通常Session是儲存在記憶體中的,每個使用者通過認證之後都會將session資料儲存在伺服器的記憶體中,而當用戶量增大時,伺服器的壓力增大。 - Session共享:現在很多應用都是分散式叢集,需要我們做額外的操作進行Session共享; - CSRF跨站偽造請求攻擊:Session機制是基於瀏覽器端的cookie的,cookie如果被截獲,使用者就會很容易受到跨站請求偽造的攻擊。 ## 基於token的認證 基於token的認證機制將認證資訊返回給客戶端並存儲。下次訪問其他頁面,需要從客戶端傳遞認證資訊回服務端。簡單的流程如下: - 客戶端使用使用者名稱跟密碼請求登入; - 服務端收到請求,去驗證使用者名稱與密碼; - 驗證成功後,服務端會簽發一個 Token,再把這個 Token 傳送給客戶端; - 客戶端收到 Token 以後可以把它儲存起來,比如放在 Cookie 裡或者 Local Storage 裡; - 客戶端每次向服務端請求資源的時候需要帶著服務端簽發的 Token; - 服務端收到請求,然後去驗證客戶端請求裡面帶著的 Token,如果驗證成功,就向客戶端返回請求的資料; 基於token的驗證機制,有以下的優點: - 支援跨域訪問,將token置於請求頭中,而cookie是不支援跨域訪問的; - 無狀態化,服務端無需儲存token,只需要驗證token資訊是否正確即可,而session需要在服務端儲存,一般是通過cookie中的sessionID在服務端查詢對應的session; - 無需繫結到一個特殊的身份驗證方案(傳統的使用者名稱密碼登陸),只需要生成的token是符合我們預期設定的即可; - 更適用於移動端(Android,iOS,小程式等等),像這種原生平臺不支援cookie,比如說微信小程式,每一次請求都是一次會話,當然我們可以每次去手動為他新增cookie,詳情請檢視博主另一篇部落格; - 避免CSRF跨站偽造攻擊,還是因為不依賴cookie; 缺點的話一個就是相比較於傳統的session登陸機制實現起來略微複雜一點,另外一個比較大的缺點是由於伺服器不儲存 token,因此無法在使用過程中廢止某個 token,或者更改 token 的許可權。也就是說,一旦 token 簽發了,在到期之前就會始終有效,除非伺服器部署額外的邏輯。 退出登陸的話,只要前端清除token資訊即可。 ## 基於JWT的token認證實現 JWT(JSON Web Token)就是基於token認證的代表,這邊就用JWT為列來介紹基於token的認證機制。 需要引入JWT的依賴 ```xml com.auth0 java-jwt 3.8.2 ``` 生成token和驗證token的工具類如下: ```java public class JWTTokenUtil { //設定過期時間 private static final long EXPIRE_DATE=30*60*100000; //token祕鑰 private static final String TOKEN_SECRET = "ZCfasfhuaUUHufguGuwu2020BQWE"; public static String token (String username,String password){ String token = ""; try { //過期時間 Date date = new Date(System.currentTimeMillis()+EXPIRE_DATE); //祕鑰及加密演算法 Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); //設定頭部資訊 Map header = new HashMap<>(); header.put("typ","JWT"); header.put("alg","HS256"); //攜帶username,password資訊,生成簽名 token = JWT.create() .withHeader(header) .withClaim("username",username) .withClaim("password",password).withExpiresAt(date) .sign(algorithm); }catch (Exception e){ e.printStackTrace(); return null; } return token; } public static boolean verify(String token){ /** * @desc 驗證token,通過返回true * @params [token]需要校驗的串 **/ try { Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(token); return true; }catch (Exception e){ e.printStackTrace(); return false; } } public static void main(String[] args) { String username ="name1"; String password = "pw1"; //注意,一般不會把密碼等私密資訊放在payload中,這邊只是舉個列子 String token = token(username,password); System.out.println(token); boolean b = verify(token); System.out.println(b); } } ``` 執行結果如下: ```text Connected to the target VM, address: '127.0.0.1:11838', transport: 'socket' eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXNzd29yZCI6IjEyMyIsImV4cCI6MTU5NzM5Nzc0OCwidXNlcm5hbWUiOiJ6aGFuZ3NhbiJ9.LI5S_nX-YcqtExI9UtKiP8FPqpQW_ccaws2coLzyOS0 true ``` 關於`DecodedJWT`這個類,大家可以重點看下,裡面包含了解碼後的使用者資訊。 ### JWT的使用說明 客戶端收到伺服器返回的 JWT,可以儲存在 Cookie 裡面,也可以儲存在 localStorage。 此後,客戶端每次與伺服器通訊,都要帶上這個 JWT。你可以把它放在 Cookie 裡面自動傳送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請求的頭資訊`Authorization`欄位裡面。 > ``` > Authorization: Bearer > ``` 另一種做法是,跨域的時候,JWT 就放在 POST 請求的資料體裡面。 JWT 本身包含了認證資訊,一旦洩露,任何人都可以獲得該令牌的所有許可權。為了減少盜用,JWT 的有效期應該設定得比較短。對於一些比較重要的許可權,使用時應該再次對使用者進行認證。 為了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。(或者是對JWT在前後端之間進行加密之後在傳輸) ### 關於JWT的一個問題 上面生成JWT token的過程關鍵點就是金鑰,假如這個金鑰洩露了,那是不是就可以偽造token了。 還有就是生產環境的金鑰值,開發的程式設計師大概率是知道的,怎麼防止程式要監守自盜,偽造token值呢?希望有經驗的大佬指教下。 ```java //token祕鑰 private static final String TOKEN_SECRET = "ZCfasfhuaUUHufguGuwu2020BQWE"; ``` 關於上面的問題,@仙湖碼農 給出了一個簡單易懂的方案~ > jwt 來生成token,還有一個玩法,使用者登入時,生成token的 SecretKey 是一個隨機數,也就是說每個使用者,每次登入時jwt SecretKey 是隨機數,並儲存到快取,key是登入賬戶,(當然了,分散式快取的話,就用Redis,sqlserver快取等等),總之,客戶端訪問介面是,header 要帶登入賬戶,和token,服務端拿到登入賬號,到快取去撈相應的SecretKey ,然後再進行token校驗。可以防偽造token了(這個方案在一定程度上能防止偽造,但是不能防止token洩露被劫持)。 ## 參考 - https://www.cnblogs.com/achengmu/p/12693260.html - https://www.cnblogs.com/ruoruchujian/p/11271285.html - [JWT使用詳解](https://www.cnblogs.com/dwlovelife/p/11321541.html) - [JWT官網](https://jwt.io)