基於token的會話保持機制

session簡介

作過Web開發的程序員應該對Session都比較熟悉,Session是一塊保存在服務器端的內存空間,通常用於保存用戶的會話信息。html

用戶經過用戶名和密碼登錄成功以後,服務器端程序會在服務器端開闢一塊Session內存空間並將用戶的信息存入這塊空間,同時服務器會
在cookie中寫入一個Session_id的值,這個值用於標識這個內存空間。前端

下次用戶再來訪問的話會帶着這個cookie中的session_id,服務器拿着這個id去尋找對應的session,若是session中已經有了這個用戶的
登錄信息,則說明用戶已經登錄過了。java

使用Session保持會話信息使用起來很是簡單,技術也很是成熟。可是也存在下面的幾個問題:程序員

  • 服務器壓力大:一般Session是存儲在內存中的,每一個用戶經過認證以後都會將session數據保存在服務器的內存中,而當用戶量增大時,服務器的壓力增大。
  • Session共享:如今不少應用都是分佈式集羣,須要咱們作額外的操做進行Session共享;
  • CSRF跨站僞造請求攻擊:Session機制是基於瀏覽器端的cookie的,cookie若是被截獲,用戶就會很容易受到跨站請求僞造的攻擊。

基於token的認證

基於token的認證機制將認證信息返回給客戶端並存儲。下次訪問其餘頁面,須要從客戶端傳遞認證信息回服務端。簡單的流程以下:算法

  • 客戶端使用用戶名跟密碼請求登陸;
  • 服務端收到請求,去驗證用戶名與密碼;
  • 驗證成功後,服務端會簽發一個 Token,再把這個 Token 發送給客戶端;
  • 客戶端收到 Token 之後能夠把它存儲起來,好比放在 Cookie 裏或者 Local Storage 裏;
  • 客戶端每次向服務端請求資源的時候須要帶着服務端簽發的 Token;
  • 服務端收到請求,而後去驗證客戶端請求裏面帶着的 Token,若是驗證成功,就向客戶端返回請求的數據;

基於token的驗證機制,有如下的優勢:sql

  • 支持跨域訪問,將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的依賴跨域

<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>3.8.2</version>
</dependency>

生成token和驗證token的工具類以下:

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<String,Object> 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);
    }
}

執行結果以下:

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 <token>

另外一種作法是,跨域的時候,JWT 就放在 POST 請求的數據體裏面。

JWT 自己包含了認證信息,一旦泄露,任何人均可以得到該令牌的全部權限。爲了減小盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。

爲了減小盜用,JWT 不該該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。(或者是對JWT在先後端之間進行加密以後在傳輸)

關於JWT的一個問題

上面生成JWT token的過程關鍵點就是密鑰,假如這個密鑰泄露了,那是否是就能夠僞造token了。

還有就是生產環境的密鑰值,開發的程序員大機率是知道的,怎麼防止程序要監守自盜,僞造token值呢?但願有經驗的大佬指教下。

//token祕鑰
private static final String TOKEN_SECRET = "ZCfasfhuaUUHufguGuwu2020BQWE";

關於上面的問題,@仙湖碼農 給出了一個簡單易懂的方案~

jwt 來生成token,還有一個玩法,用戶登陸時,生成token的 SecretKey 是一個隨機數,也就是說每一個用戶,每次登陸時jwt SecretKey 是隨機數,並保存到緩存,key是登陸帳戶,(固然了,分佈式緩存的話,就用Redis,sqlserver緩存等等),總之,客戶端訪問接口是,header 要帶登陸帳戶,和token,服務端拿到登陸帳號,到緩存去撈相應的SecretKey ,而後再進行token校驗。能夠防僞造token了(這個方案在必定程度上能防止僞造,可是不能防止token泄露被劫持)。

參考

相關文章
相關標籤/搜索