日拱一卒系列(聊一聊jwt)

1.引子

我們知道,http是一種無狀態協議,即對於服務端應用來說,兩次http請求之間相互獨立,你不知道我,我不知道你。

那麼問題來了,比如說一個電商網站,購物的時候,需要瀏覽商品,將商品添加到購物車,需要下單結算。這就提出了要求:我們需要知道是誰選擇了商品a,是誰將商品a添加到了購物車,購物車又是誰的......等一系列問題,都指向了“誰”,即將多次不同http請求關聯起來。

如何關聯呢?

你應該已經反應過來了,登錄認證啊!用戶訪問購物網站,首先要做的是提供用戶名稱、密碼登錄到購物網站,然後在網站導航的地方,通常會提示:歡迎xxx!對吧,這樣以來,購物網站自然就知道了上面我們提到的一系列“誰”,到底是誰

這是從一個普通用戶的角度來描述的,我們是程序員,需要從技術的角度來進行描述。你說這也不難!不就是

  • 當用戶訪問登錄接口的時候,根據用戶提供的用戶名稱、密碼查詢數據庫

  • 如果用戶存在,購物網站應用服務器,比如說tomcat,生成一個session,並且存儲到購物網站應用服務器內存

  • 將session的唯一標識sessionId,返回給客戶端(瀏覽器),瀏覽器將sessionId存儲到cookie

  • 瀏覽器再登錄成功後,每次發起http請求,比如選擇商品、加入購物車、下訂單、結算都帶上sessionId,依據sessionId告訴服務器,一系列的誰,就是誰

  • 購物網站應用服務端,根據sessionId,自然就知道了是誰在選擇商品,誰在將商品加入購物車,誰在下訂單.......

通過session、cookie將多次不同的http請求關聯了起來,解決了一系列誰是誰的問題,完美!不能再完美了!

這也就是我們熟悉的傳統session會話的解決方案,但是這種方案,其實是有瑕疵的,我們來看一下

  • 會話session存儲在服務端,需要消耗應用服務器內存,如果網站生意比較好,動不動就有幾千萬、上億用戶,是不是單存儲會話session就需要消耗不少內存?

  • 網站太受歡迎,高併發、大流量,一臺應用服務器扛不住,部署集羣方案吧,比如說增加網站應用服務器a、b、c。會話session都是應用服務器內部生成的,用戶在服務節點a登錄了,下次請求如果打到服務節點b,節點b如何知道用戶已經登錄過?

  • 會話sessionId存儲在瀏覽器cookie中的對吧,CSRF(跨站請求僞造)怎麼辦?

  • 移動互聯網時代,大家都用智能手機,玩着各種app,沒有瀏覽器不支持cookie怎麼辦?

  • 分佈式、微服務時代,不同應用之間相互調用,服務a,不認識服務b的session啊,怎麼辦?

綜合上述總結一下,傳統會話session方案最大的不足在於消耗應用服務器內存、難以支撐應用彈性擴容縮容

這纔有了業界當前流行的token方案,接下來讓我們一起來看token方案,在這裏關於token方案,我將結合jwt給你分享。

2.案例

2.1.token登錄認證方案

token登錄認證方案,實現思路上事實上與傳統session方案差不多,畢竟token與sessionId一樣,都是字符串嘛,我們從普通用戶操作流程進行分析

  • 用戶打開購物網站,提供用戶名稱、密碼進行登錄請求

  • 網站服務器,接收到用戶登錄請求,根據用戶名稱、密碼查詢數據庫,檢查用戶是否存在

  • 如果用戶存在,根據某種規則,生成一個token(一個字符串)

  • 將token響應給瀏覽器,用戶在之後的選擇商品、加入購物車、下訂單等請求中,都帶上token

  • 網站服務器,校驗處理token即知道誰是誰了

你看,這就是token方案,對比傳統session方案,它不再需要

  • token不需要在服務器存儲,即不消耗服務器內存資源

  • 服務繼續無狀態,支持按需隨意擴容縮容

2.2.什麼是jwt

jwt的全稱是(json web token)。 是爲了在網絡應用環境間傳遞聲明的一種基於json的開放標準,通過jwt實現的token被設計爲緊湊且安全,適用於分佈式站點單點登錄(sso)場景。

jwt實現的token主要由三部分組成,以.符號進行分割

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2NjYiLCJpYXQiOjE2MTg2NDYyNjUsImV4cCI6MTYxOTI1MTA2NX0.uVeXiEhqfhpWAnkiX8glIBE4nOG6o2zaQfRBOC-EiuY
  • 頭header:標記token令牌類型,加密算法

  • 載荷payload:token數據,放置一些用戶非敏感信息,比如說用戶id,用戶名稱(切記:jwt是可以解密,密碼、手機號碼等用戶敏感信息,千萬不要放在其中)

  • 簽名sign:用於驗證token是否有效,放置篡改

2.3.jwt實現案例

2.3.1.導入依賴

<!--jjwt依賴-->
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-api</artifactId>
	<version>0.10.7</version>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-impl</artifactId>
	<version>0.10.7</version>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-jackson</artifactId>
	<version>0.10.7</version>
	<scope>runtime</scope>
</dependency>

2.3.2.jwt工具類

/**
 * token工具
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/4/17 14:31
 */
@Slf4j
@Data
public class TokenJwtUtil {

    /**
     * 祕鑰,默認:aaabbbcccdddeeefffggghhhiiijjjkkklllmmm
     */
    private String secret = "aaabbbcccdddeeefffggghhhiiijjjkkklllmmm";
    /**
     * 有效時間,默認一週,單位秒
     */
    private Long expirationTime = 604800L;

    /**
     * 生成token
     * @param claims  token 數據
     * @return
     */
    public String generateToken(Map<String, Object> claims){
        // 生成時間,過期時間
        Date createdTime = new Date();
        Date expirationTime = new Date(System.currentTimeMillis() + this.expirationTime * 1000);

        // 祕鑰
        byte[] keyBytes = this.secret.getBytes();
        SecretKey key = Keys.hmacShaKeyFor(keyBytes);

        // 生成token
       return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(createdTime)
                .setExpiration(expirationTime)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();

    }

    /**
     * 校驗token是否有效
     * @param token
     * @return 有效返回true,無效返回false
     */
    public Boolean validateToken(String token){
        Date expirationTime = getExpirationDateFromToken(token);
        return !expirationTime.before(new Date());
    }

    /**
     * 獲取token過期時間
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimsFromToken(token).getExpiration();
    }

    /**
     * 解析獲取token數據
     * @param token
     * @return
     */
    public Claims getClaimsFromToken(String token) {

        try{
            return  parser()
                    .setSigningKey(this.secret.getBytes())
                    .parseClaimsJws(token)
                    .getBody();
        }catch (ExpiredJwtException
                | UnsupportedJwtException
                | MalformedJwtException
                | IllegalArgumentException e){
            log.error("解析token發生異常", e);
            throw new IllegalArgumentException("Token invalided.");
        }

    }

}

2.3.3.測試使用

public static void main(String[] args) {
        TokenJwtUtil tokenJwtUtil = new TokenJwtUtil();

        // 1.生成token
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId","666");
        String token = tokenJwtUtil.generateToken(claims);
        log.info("準備token數據:{}", claims);
        log.info("生成token={}",token);

        // 2.解析token頭
        String[] split = token.split("\\.");
        byte[] headBytes = Base64.decodeBase64(split[0]);
        log.info("解析token頭:{}", new String(headBytes));

        // 3.解析token數據
        byte[] bodyBytes = Base64.decodeBase64(split[1]);
        log.info("解析token數據:{}", new String(bodyBytes));

        // 4.解析token簽名
        byte[] signatureBytes = Base64.decodeBase64(split[2]);
        log.info("解析token簽名:{}", new String(signatureBytes));

        // 5.解析token過期時間
        Date expirationDate = tokenJwtUtil.getExpirationDateFromToken(token);
        log.info("解析token過期時間:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(expirationDate));

        // 6.校驗token是否有效
        Boolean isValidate = tokenJwtUtil.validateToken(token);
        log.info("校驗token是否有效:{}",isValidate);
}

[com.anan.edu.common.util.Test] - 準備token數據:{userId=666}
[com.anan.edu.common.util.Test] - 生成token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI2NjYiLCJpYXQiOjE2MTg2NDYyNjUsImV4cCI6MTYxOTI1MTA2NX0.uVeXiEhqfhpWAnkiX8glIBE4nOG6o2zaQfRBOC-EiuY
[com.anan.edu.common.util.Test] - 解析token頭:{"alg":"HS256"}
[com.anan.edu.common.util.Test] - 解析token數據:{"userId":"666","iat":1618646265,"exp":1619251065}
[com.anan.edu.common.util.Test] - 解析token簽名:�W��Hj~Vy"_�% 8�ảl�A�A8/���
[com.anan.edu.common.util.Test] - 解析token過期時間:2021-04-24 15:57:45
[com.anan.edu.common.util.Test] - 校驗token是否有效:true

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章