大家好,我是李俊輝蹬昌!我會把精彩文章在深入看懂的基礎(chǔ)上混驰,過濾出精華轉(zhuǎn)載過來,推薦給大家皂贩,分享給更多的人參考和學(xué)習(xí)栖榨。如果您覺得文章有用,請幫忙點個贊或關(guān)注明刷,也為我鼓勵一下婴栽,堅持寫下去!
對于 Token辈末,在很多大型網(wǎng)站中都有所應(yīng)用愚争,比如 Facebook,Twitter挤聘,Google轰枝,Github 等等,比起傳統(tǒng)的身份驗證方法组去,Token 的擴展性更強鞍陨,也更安全點,非常適合用在 Web 應(yīng)用或者移動應(yīng)用上从隆。Token 的中文有人翻譯成 “令牌”诚撵,我覺得挺好,意思就是键闺,你拿著這個令牌寿烟,才能過一些關(guān)卡。
1.基于Token 的身份驗證方法
使用基于 Token 的身份驗證方法辛燥,在服務(wù)端不需要存儲用戶的登錄記錄韧衣。大概的流程是這樣的:
客戶端使用用戶名跟密碼請求登錄盅藻;
服務(wù)端收到請求,去驗證用戶名與密碼畅铭;
驗證成功后氏淑,服務(wù)端會簽發(fā)一個 Token,再把這個 Token 發(fā)送給客戶端硕噩;
客戶端收到 Token 以后可以把它存儲起來假残,比如放在 Cookie 里或者 Local Storage 里;
客戶端每次向服務(wù)端請求資源的時候需要帶著服務(wù)端簽發(fā)的 Token炉擅;
服務(wù)端收到請求辉懒,然后去驗證客戶端請求里面帶著的 Token,如果驗證成功谍失,就向客戶端返回請求的數(shù)據(jù)眶俩。
2.JWT
實施 Token 驗證的方法挺多的,還有一些標準方法快鱼,比如 JWT颠印,讀作:jot ,表示:JSON Web Tokens 抹竹。JWT 標準的 Token 有三個部分:
1.header(頭部)线罕,頭部信息主要包括(參數(shù)的類型--JWT,簽名的算法--HS256)
2.poyload(負荷),負荷基本就是自己想要存放的信息(因為信息會暴露窃判,不應(yīng)該在載荷里面加入任何敏感的數(shù)據(jù))
3.sign(簽名)钞楼,簽名的作用就是為了防止惡意篡改數(shù)據(jù),下邊會詳細說明
中間用點分隔開袄琳,并且都會使用 Base64 編碼询件,所以真正的 Token 看起來像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
2.1 Header
Header 部分主要是兩部分內(nèi)容,一個是 Token 的類型唆樊,另一個是使用的算法宛琅,比如下面類型就是 JWT,使用的算法是 HS256窗轩。
{
"typ" : "JWT",
"alg" : "HS256"
}
上面的內(nèi)容要用 Base64 的形式編碼一下,所以就變成這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2.2 Payload
Payload 里面是 Token 的具體內(nèi)容座咆,這些內(nèi)容里面有一些是標準字段痢艺,你也可以添加其它需要的內(nèi)容。下面是標準字段:
iss:Issuer介陶,發(fā)行者
sub:Subject堤舒,主題
aud:Audience,觀眾
exp:Expiration time哺呜,過期時間
nbf:Not before
iat:Issued at舌缤,發(fā)行時間
jti:JWT ID
比如下面這個 Payload,用到了 iss 發(fā)行人,exp 過期時間国撵,另外還有兩個自定義的字段陵吸,一個是 name ,還有一個是 admin 介牙。
{
"iss" : "csdn.net",
"exp" : "201511205211314",
"name" : "維C果糖",
"admin" : true
}
使用 Base64 編碼以后就變成了這個樣子:
eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ
2.3 Signature
JWT 的最后一部分是 Signature 壮虫,這部分內(nèi)容有三個部分,先是用 Base64 編碼的 header 和 payload 环础,再用加密算法加密一下囚似,加密的時候要放進去一個 Secret ,這個相當于是一個密碼线得,這個密碼秘密地存儲在服務(wù)端饶唤。
header
payload
secret
var encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
HMACSHA256(encodedString, 'secret');
處理完成以后看起來像這樣:
SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
最后這個在服務(wù)端生成并且要發(fā)送給客戶端的 Token 看起來像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
客戶端收到這個 Token 以后把它存儲下來,下回向服務(wù)端發(fā)送請求的時候就帶著這個 Token 贯钩。服務(wù)端收到這個 Token 募狂,然后進行驗證,通過以后就會返回給客戶端想要的資源魏保。
3.Web安全
Token熬尺,我們稱之為“令牌”,其最大的特點就是隨機性谓罗,不可預(yù)測粱哼。一般黑客或軟件無法猜測出來。那么檩咱,Token 有什么作用揭措?又是什么原理呢?
Token 一般用在兩個地方:
`防止表單重復(fù)提交刻蚯;
Anti CSRF 攻擊(跨站點請求偽造)绊含。`
兩者在原理上都是通過 session token 來實現(xiàn)的。當客戶端請求頁面時炊汹,服務(wù)器會生成一個隨機數(shù) Token躬充,并且將 Token 放置到 session 當中,然后將 Token 發(fā)給客戶端(一般通過構(gòu)造 hidden 表單)讨便。下次客戶端提交請求時充甚,Token 會隨著表單一起提交到服務(wù)器端。
然后霸褒,如果應(yīng)用于“Anti CSRF攻擊”伴找,則服務(wù)器端會對 Token 值進行驗證,判斷是否和session中的Token值相等废菱,若相等技矮,則可以證明請求有效抖誉,不是偽造的。不過衰倦,如果應(yīng)用于“防止表單重復(fù)提交”袒炉,服務(wù)器端第一次驗證相同過后,會將 session 中的 Token 值更新下耿币,若用戶重復(fù)提交梳杏,第二次的驗證判斷將失敗,因為用戶提交的表單中的 Token 沒變淹接,但服務(wù)器端 session 中 Token 已經(jīng)改變了十性。
上面的 session 應(yīng)用相對安全,但也叫繁瑣塑悼,同時當多頁面多請求時劲适,必須采用多 Token 同時生成的方法,這樣占用更多資源厢蒜,執(zhí)行效率會降低霞势。因此,也可用 cookie 存儲驗證信息的方法來代替 session Token斑鸦。比如愕贡,應(yīng)對“重復(fù)提交”時,當?shù)谝淮翁峤缓蟊惆岩呀?jīng)提交的信息寫到 cookie 中巷屿,當?shù)诙翁峤粫r固以,由于 cookie 已經(jīng)有提交記錄,因此第二次提交會失敗嘱巾。不過憨琳,cookie 存儲有個致命弱點,如果 cookie 被劫持(XSS 攻擊很容易得到用戶 cookie)旬昭,那么又一次 game over篙螟,黑客將直接實現(xiàn) CSRF 攻擊。所以问拘,安全和高效相對的遍略,具體問題具體分析吧!
此外骤坐,要避免“加 token 但不進行校驗”的情況绪杏,在 session 中增加了 token,但服務(wù)端沒有對 token 進行驗證或油,這樣根本起不到防范的作用寞忿。還需注意的是驰唬,對數(shù)據(jù)庫有改動的增顶岸、刪腔彰、改操作,需要加 token 驗證辖佣,對于查詢操作霹抛,一定不要加 token,防止攻擊者通過查詢操作獲取 token 進行 CSRF攻擊卷谈。但并不是這樣攻擊者就無法獲得 token杯拐,只是增大攻擊成本而已。
4.結(jié)合后端開發(fā)再次理解token生成過程
在Java的實現(xiàn)中可以有兩種方式世蔗,一種是不借助第三方j(luò)ar端逼,自己生成token,另一種的借助第三方j(luò)ar污淋,傳入自己需要的負荷信息顶滩,生成token。接下來就根據(jù)這兩個逐個說明寸爆。Token的組成就是header.poyload.sign礁鲁。
4.1自己生成token:
header
和poyload
的組成都是json字符串,所以先創(chuàng)建頭部的json赁豆,然后用base64
編碼(org.apache.axis.encoding.Base64)仅醇,
這里選擇的base64
要對應(yīng)著編碼和解碼(Base64
是一種編碼,也就是說,它是可以被翻譯回原來的樣子來的屎蜓。它并不是一種加密過程)咱揍。然后再創(chuàng)建負荷的json,然后也同樣用base64
編碼甲抖,這樣就生成了兩個字符串,然后用.拼接到一起就形成了現(xiàn)在的形式:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0心铃。
在這里只是給大家一個演示准谚,
實際中根據(jù)每個人的負荷參數(shù)的不同,編碼后所生成的字符串也不同去扣。因為沒有借助第三方的jar柱衔,
所有接下來要對上邊拼接成的字符串進行hs256的算法加密生成sign簽名,這里需要自己手動去寫一個類愉棱,然后提供一
個靜態(tài)方法供外界的調(diào)用唆铐。
類的實現(xiàn)代碼如下:
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 = "";
//別人篡改數(shù)據(jù),但是簽名的密匙是在服務(wù)器存儲奔滑,密匙不同艾岂,生成的sign也不同。
//所以根據(jù)sign的不同就可以知道是否篡改了數(shù)據(jù)朋其。
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的三部分就生成了王浴,然后當做參數(shù)傳到前臺脆炎,用cookie
或者localstore(推薦)
存儲就可以在同一客戶端調(diào)用了。
當從客戶端帶過來token參數(shù)的時候氓辣,直接對頭部和負荷再次調(diào)用加密算法秒裕,看生成的新的簽名和之前的簽名是否一致,判斷數(shù)據(jù)是否被篡改钞啸。
4.2借用第三方的jar(jjwt-0.7.0.jar)生成token:
在這里自己已經(jīng)通過代碼測試几蜻,直接先看代碼:
調(diào)用這個方法會自動對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串當做參數(shù)傳入就行,并且簽名也會對應(yīng)的生成源武,返回一個完整的token扼褪。在測試的過程中,發(fā)現(xiàn)即使自己不定義token的頭部粱栖,也會自動生成header话浇,只是里邊沒有typ這樣的參數(shù)定義,只有HS256闹究,這里源碼里邊幔崖,默認了alg的value。在這里我想說明的是渣淤,假如外界會篡改參數(shù)赏寇,他肯定也知道構(gòu)成,會把負荷里邊的參數(shù)取出來价认,也許會修改嗅定,然后編碼放回去,但是頭部的信息對他來說用處不大用踩,所以自己在這個方法里邊渠退,默認把頭部加上,負荷的值還是自己在調(diào)用的時候傳入進來脐彩。
這樣執(zhí)行完碎乃,把生成的token就當做參數(shù)傳到前臺,存儲在cookie里邊惠奸。
然后再說一下梅誓,前臺帶過來token參數(shù)時候,怎么處理,看代碼:
public abstract JwtBuilder signWith(SignatureAlgorithm paramSignatureAlgorithm, String paramString);
),沒能單獨把第三方生成sign的方法提出來妈拌,只是一個接口拥坛,但是跟上邊的加密算法實現(xiàn)原理應(yīng)該是基本一致的。
至此尘分,token簽名這一塊的問題大致就先說到這了猜惋,然后再來說一下token過期時間問題。這個相對來說不是太復(fù)雜培愁,可以在負荷里邊多帶一個參數(shù)著摔,把過期時間放進去,其實里邊有一個exp標簽名就是存儲過期時間字段的定续,但是自己在測試過程中谍咆,發(fā)現(xiàn)每次讀出來的都是最原始的時間,自己當時也再花時間去看私股,因為我覺得自己帶參數(shù)其實一個道理摹察。存儲的是時間戳。
存儲:
long nowMillis = System.currentTimeMillis();
long expMillis = nowMillis + 1000*2;//設(shè)置token二秒過期
獲瘸ā:
Date aa = new Date(Long.parseLong(claims.get("aa").toString()));
方式就是這樣了供嚎,我大概列了出來。到時可以存儲一個生成token時間和token過期時間峭状,然后服務(wù)器接收到的時候克滴,可以根據(jù)當前的時間去判斷。當前時間大于token生成時間并且小于token過期時間的情況下优床,繼續(xù)走你接下來的業(yè)務(wù)操作劝赔。
5.token被劫持了,怎樣解決這個安全問題
a胆敞、在存儲的時候把 token 進行對稱加密存儲望忆,用時解開。
b竿秆、將請求 URL启摄、時間戳、token 三者進行合并加鹽簽名幽钢,服務(wù)端校驗有效性歉备。
c、HTTPS 對 URL 進行判斷匪燕。
HTTP 協(xié)議是無狀態(tài)的蕾羊,在web中使用cookie+session的技術(shù)來保持用戶登陸的狀態(tài)
移動端使用token來保持用戶登陸狀態(tài)由于token在網(wǎng)絡(luò)中傳輸喧笔,很容易被中間人獲取,進而模擬用戶進行其他相關(guān)操作
解決辦法:
服務(wù)器端
響應(yīng)頭增加隨機字符串 CSRF_TOKEN=xxxxxxxxxxx(每次請求都不同)
客戶端
客戶端和服務(wù)端 保留密鑰 secret = yyyyyyyyy
客戶端獲取響應(yīng)頭CSRF_TOKEN下次請求必須攜帶
客戶端 (secret+提交內(nèi)容) 進行簽名
當用戶提交信息到服務(wù)器端龟再,首先驗證簽名數(shù)據(jù)是否被篡改书闸,隨后通過token+隨機字符串比對,正確的話執(zhí)行操作利凑,刷新隨機字符串浆劲,即使token被中間人獲取到了,沒有隨機字符串依舊執(zhí)行不了任何操作哀澈,再糟糕點中間人通過攔截響應(yīng)頭獲取到了隨機字符串牌借,但是密鑰還沒泄露,沒有辦法進行簽名依舊執(zhí)行不了操作
缺點:
以上解決辦法只適用于APP端割按,瀏覽器端不適用膨报,因為沒地方保存密鑰
總結(jié):
所以能上 HTTPS 就用 HTTPS 吧!
名稱解釋:
[1] XSS 攻擊:跨站腳本攻擊(Cross Site Scripting)适荣,惡意攻擊者往 Web 頁面里插入惡意 Script 代碼现柠,當用戶瀏覽該頁之時,嵌入其中 Web 里面的 Script 代碼會被執(zhí)行弛矛,從而達到惡意攻擊用戶的目的晒旅。
[2] CSRF 攻擊:CSRF(Cross-site request forgery)跨站請求偽造,也被稱為“One Click Attack”或者 Session Riding汪诉,通撤狭担縮寫為 CSRF 或者 XSRF,是一種對網(wǎng)站的惡意利用扒寄。盡管聽起來像跨站腳本(XSS)鱼鼓,但它與 XSS 非常不同,XSS 利用站點內(nèi)的信任用戶该编,而 CSRF 則通過偽裝來自受信任用戶的請求來利用受信任的網(wǎng)站迄本。與 XSS 攻擊相比,CSRF 攻擊往往不大流行(因此對其進行防范的資源也相當稀少)和難以防范课竣,所以被認為比 XSS 更具危險性嘉赎。
————————————————
如果你覺李俊輝的文章對您有用,請幫忙點個贊或關(guān)注于樟,也為我鼓勵一下公条,堅持寫下去,在此感謝??迂曲!
轉(zhuǎn)載請一定注明出處靶橱!
原文地址:http://www.reibang.com/p/1422374a404b
本文參考鏈接:
https://blog.csdn.net/qq_35246620/article/details/55049812
https://blog.csdn.net/buyaoshuohua1/article/details/73739419?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
https://blog.csdn.net/weixin_30677617/article/details/101518468?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
https://blog.csdn.net/weixin_43644324/article/details/87895729