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/