1. 程式人生 > >JWT——Token認證的兩種實現和安全詳解

JWT——Token認證的兩種實現和安全詳解

前言:
最近因為專案中需要解決跨域取值的問題,所有考慮到用Token認證做技術支撐點,自己看了許多與之相關的文章,從中總結出了以下兩個要點(簽名和token時間)。在說這兩個要點之前先大概簡單說一下與之有關的一些問題。
首先,如果你對token認證的知識一點都不瞭解,那麼我覺得這篇文章還不太適合你,因為我在這裡不會在把相關的基礎知識再說明一遍,因為網上有很多相關的文章,講的都比較好,我會在文章下邊參考文獻中附上鍊接。但是還是說一下重點的幾個點:
1.header(頭部),頭部資訊主要包括(引數的型別--JWT,簽名的演算法--HS256)
2.poyload(負荷),負荷基本就是自己想要存放的資訊(因為資訊會暴露,不應該在載荷裡面加入任何敏感的資料),有兩個形式,下邊會講到
3.sign(簽名),簽名的作用就是為了防止惡意篡改資料,下邊會詳細說明
我的理解,在Java的實現中可以有兩種方式,一種是不借助第三方jar,自己生成token,另一種的藉助第三方jar,傳入自己需要的負荷資訊,生成token。接下來就根據這兩個逐個說明。Token的組成就是header.poyload.sign。
自己生成token:
看過參考資料的朋友應該都知道,header和poyload的組成都是json字串,所以先建立頭部的json,然後用base64編碼(org.apache.axis.encoding.Base64),這裡選擇的base64要對應著編碼和解碼(Base64是一種編碼,也就是說,它是可以被翻譯回原來的樣子來的。它並不是一種加密過程)。然後再建立負荷的json,然後也同樣用base64編碼,這樣就生成了兩個字串,然後用.拼接到一起就形成了現在的形式:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0。在這裡只是給大家一個演示,
實際中根據每個人的負荷引數的不同,編碼後所生成的字串也不同。因為沒有藉助第三方的jar,
所有接下來要對上邊拼接成的字串進行hs256的演算法加密生成sign簽名,這裡需要自己手動去寫一個類,然後提供一
個靜態方法供外界的呼叫。
類的實現程式碼如下:

import javax.crypto.Mac;  
import javax.crypto.spec.SecretKeySpec;  
import org.apache.commons.codec.binary.Base64;  
public class HS256 {  
public static String returnSign(String message) {  
    String hash = "";  
    //別人篡改資料,但是簽名的密匙是在伺服器儲存,密匙不同,生成的sign也不同。  
    //所以根據sign的不同就可以知道是否篡改了資料。  
    String secret = "mystar";//密匙  
    try {  
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");  
        SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(),"HmacSHA256");  
        sha256_HMAC.init(secret_key);  
        hash = Base64.encodeBase64String(sha256_HMAC.doFinal(message.getBytes()));  
        System.out.println(message+"#####"+hash);  
    } catch (Exception e) {  
        System.out.println("Error");  
    }  
        return hash;  
    }  
    }  

這樣token的三部分就生成了,然後當做引數傳到前臺,用cookie儲存就可以在同一客戶端呼叫了。
當從客戶端帶過來token引數的時候,直接對頭部和負荷再次呼叫加密演算法,看生成的新的簽名和之前的簽名是否一致,
判斷資料是否被篡改。
借用第三方的jar(jjwt-0.7.0.jar)生成token:
在這裡自己已經通過程式碼測試,直接先看程式碼:

呼叫這個方法會自動對header和poyload進行base64的編碼,你看過原始碼就知道它用的是哪一種,用的是自己jar包自帶的(io.jsonwebtoken.impl.Base64Codec),跟自己生成token時,用的base64的jar不一樣,自己在此列出來:

public static void main(String[] args) {  
        // String token = createJWT("11","22","222",11);  
        // System.out.println(token);  
        JSONObject json_header = new JSONObject();  
        json_header.put("typ", "JWT");//型別  
        json_header.put("alg", "HS256");//加密演算法  
        // String header =  
        // org.apache.axis.encoding.Base64.encode(json_header.toString().getBytes());  
        String header = Base64Codec.BASE64URL.encode(json_header.toString()  
                .getBytes());  
        String aa = Base64Codec.BASE64URL.decodeToString(header);  
        System.out.println(header + "--" + aa);  
    }  

接著上邊createJWT()方法說,只要把自己定義的負荷json串當做引數傳入就行,並且簽名也會對應的生成,返回一個完整的token。在測試的過程中,發現即使自己不定義token的頭部,也會自動生成header,只是裡邊沒有typ這樣的引數定義,只有HS256,這裡原始碼裡邊,默認了alg的value。在這裡我想說明的是,假如外界會篡改引數,他肯定也知道構成,會把負荷裡邊的引數取出來,也許會修改,然後編碼放回去,但是頭部的資訊對他來說用處不大,所以自己在這個方法裡邊,預設把頭部加上,負荷的值還是自己在呼叫的時候傳入進來。這樣執行完,把生成的token就當做引數傳到前臺,儲存在cookie裡邊。
然後再說一下,前臺帶過來token引數時候,怎麼處理,看程式碼:

這個過程的原理跟自己生成token判斷原理一樣,都是從新生成sign,只是一個是呼叫自己的方法,另外一個是呼叫第三方的方法,自己看了原始碼(public abstract JwtBuilder signWith(SignatureAlgorithm paramSignatureAlgorithm, String paramString);),沒能單獨把第三方生成sign的方法提出來,只是一個介面,但是跟上邊的加密演算法實現原理應該是基本一致的。
至此,token簽名這一塊的問題大致就先說到這了,然後再來說一下token過期時間問題。這個相對來說不是太複雜,可以在負荷裡邊多帶一個引數,把過期時間放進去,其實裡邊有一個exp標籤名就是儲存過期時間欄位的,但是自己在測試過程中,發現每次讀出來的都是最原始的時間,自己當時也再花時間去看,因為我覺得自己帶引數其實一個道理。儲存的是時間戳。
儲存:
long nowMillis = System.currentTimeMillis();
long expMillis = nowMillis + 1000*2;//設定token二秒過期
獲取:
Date aa = new Date(Long.parseLong(claims.get("aa").toString()));
方式就是這樣了,我大概列了出來。到時可以儲存一個生成token時間和token過期時間,然後伺服器接收到的時候,可以根據當前的時間去判斷。當前時間大於token生成時間並且小於token過期時間的情況下,繼續走你接下來的業務操作。
附上參考文獻:
http://blog.csdn.net/u011537073/article/details/52177204
http://www.cnblogs.com/anny0404/p/5318692.html
http://blog.leapoahead.com/2015/09/06/understanding-jwt/