什么是jwt
jwt(json web token)是一個(gè)開(kāi)放標(biāo)準(zhǔn)(rfc 7519),它定義了一種緊湊且獨(dú)立的方式,可以在各個(gè)系統(tǒng)之間用json作為對(duì)象安全地傳輸信息,并且可以保證所傳輸?shù)男畔⒉粫?huì)被篡改。
「jwt」由三部分構(gòu)成
- 信息頭:指定了使用的簽名算法
- 聲明部分:其中也可以包含超時(shí)時(shí)間
- 基于指定的算法生成的簽名
通過(guò)這三部分信息,api 服務(wù)端可以根據(jù)「jwt」信息頭和聲明部分的信息重新生成簽名。之所以可以這樣做,是因?yàn)樯珊灻枰拿罔€存放在服務(wù)器端。
1
|
jwtauth. new ( "hs256" , [] byte ( "k8uemdpyb9awfkzs" ), nil) |
如果這個(gè)簽名秘鑰比較簡(jiǎn)單,建議立刻換一個(gè)復(fù)雜一些的,更改以后會(huì)使所有已經(jīng)產(chǎn)生的「jwt」 失效,強(qiáng)制客戶(hù)端重新從服務(wù)器獲取授權(quán)。
jwt通常有兩種應(yīng)用場(chǎng)景:
- 授權(quán)。這是最常見(jiàn)的jwt使用場(chǎng)景。一旦用戶(hù)登錄,每個(gè)后續(xù)請(qǐng)求將包含一個(gè)jwt,作為該用戶(hù)訪問(wèn)資源的令牌。
- 信息交換。可以利用jwt在各個(gè)系統(tǒng)之間安全地傳輸信息,jwt的特性使得接收方可以驗(yàn)證收到的內(nèi)容是否被篡改。
本文討論第一點(diǎn),如何利用jwt來(lái)實(shí)現(xiàn)對(duì)api的授權(quán)訪問(wèn)。這樣就只有經(jīng)過(guò)授權(quán)的用戶(hù)才可以調(diào)用api。
jwt的結(jié)構(gòu)
jwt由三部分組成,用.分割開(kāi)。
header
第一部分為header,通常由兩部分組成:令牌的類(lèi)型,即jwt,以及所使用的加密算法。
1
2
3
4
|
{ "alg" : "hs256" , "typ" : "jwt" } |
base64加密后,就變成了:
eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9
payload
第二部分為payload,里面可以放置自定義的信息,以及過(guò)期時(shí)間、發(fā)行人等。
1
2
3
4
5
|
{ "sub" : "1234567890" , "name" : "john doe" , "iat" : 1516239022 } |
base64加密后,就變成了:
eyjzdwiioiixmjm0nty3odkwiiwibmftzsi6ikpvag4grg9liiwiawf0ijoxnte2mjm5mdiyfq
signature
第三部分為signature,計(jì)算此簽名需要四部分信息:
- header里的算法信息
- header
- payload
- 一個(gè)自定義的秘鑰
接受到j(luò)wt后,利用相同的信息再計(jì)算一次簽名,然年與jwt中的簽名對(duì)比,如果不相同則說(shuō)明jwt中的內(nèi)容被篡改。
解碼后的jwt
將上面三部分都編碼后再合在一起就得到了jwt。
需要注意的是,jwt的內(nèi)容并不是加密的,只是簡(jiǎn)單的base64編碼。也就是說(shuō),jwt一旦泄露,里面的信息可以被輕松獲取,因此不應(yīng)該用jwt保存任何敏感信息。
jwt是怎樣工作的
- 應(yīng)用程序或客戶(hù)端向授權(quán)服務(wù)器請(qǐng)求授權(quán)。這里的授權(quán)服務(wù)器可以是單獨(dú)的一個(gè)應(yīng)用,也可以和api集成在同一個(gè)應(yīng)用里。
- 授權(quán)服務(wù)器向應(yīng)用程序返回一個(gè)jwt。
- 應(yīng)用程序?qū)wt放入到請(qǐng)求里(通常放在http的authorization頭里)
- 服務(wù)端接收到請(qǐng)求后,驗(yàn)證jwt并執(zhí)行對(duì)應(yīng)邏輯。
在java里使用jwt
引入依賴(lài)
1
2
3
4
|
<dependency> <groupid>io.jsonwebtoken</groupid> <artifactid>jjwt</artifactid> </dependency> |
這里使用了一個(gè)叫jjwt(java jwt)的庫(kù)。
jwt service
生成jwt
1
2
3
4
5
6
7
|
public string generatetoken(string payload) { return jwts.builder() .setsubject(payload) .setexpiration( new date(system.currenttimemillis() + 10000 )) .signwith(signaturealgorithm.hs256, secret_key) .compact(); } |
這里設(shè)置過(guò)期時(shí)間為10秒,因此生成的jwt只在10秒內(nèi)能通過(guò)驗(yàn)證。
需要提供一個(gè)自定義的秘鑰。
解碼jwt
1
2
3
4
5
6
7
|
public string parsetoken(string jwt) { return jwts.parser() .setsigningkey(secret_key) .parseclaimsjws(jwt) .getbody() .getsubject(); } |
解碼時(shí)會(huì)檢查jwt的簽名,因此需要提供秘鑰。
驗(yàn)證jwt
1
2
3
4
5
6
7
8
|
public boolean istokenvalid(string jwt) { try { parsetoken(jwt); } catch (throwable e) { return false ; } return true ; } |
jjwt并沒(méi)有提供判斷jwt是否合法的方法,但是在解碼非法jwt時(shí)會(huì)拋出異常,因此可以通過(guò)捕獲異常的方式來(lái)判斷是否合法。
注冊(cè)/登錄
1
2
3
4
5
6
7
|
@getmapping ( "/registration" ) public string register( @requestparam string username, httpservletresponse response) { string jwt = jwtservice.generatetoken(username); response.setheader(jwt_header_name, jwt); return string.format( "jwt for %s :\n%s" , username, jwt); } |
需要為還沒(méi)有獲取到j(luò)wt的用戶(hù)提供一個(gè)這樣的注冊(cè)或者登錄入口,來(lái)獲取jwt。
獲取到響應(yīng)里的jwt后,要在后續(xù)的請(qǐng)求里包含jwt,這里放在請(qǐng)求的authorization頭里。
驗(yàn)證jwt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@override public void dofilter(servletrequest request, servletresponse response, filterchain chain) throws ioexception, servletexception { httpservletrequest httpservletrequest = (httpservletrequest) request; httpservletresponse httpservletresponse = (httpservletresponse) response; string jwt = httpservletrequest.getheader(jwt_header_name); if (white_list.contains(httpservletrequest.getrequesturi())) { chain.dofilter(request, response); } else if (istokenvalid(jwt)) { updatetoken(httpservletresponse, jwt); chain.dofilter(request, response); } else { httpservletresponse.senderror(httpservletresponse.sc_unauthorized); } } private void updatetoken(httpservletresponse httpservletresponse, string jwt) { string payload = jwtservice.parsetoken(jwt); string newtoken = jwtservice.generatetoken(payload); httpservletresponse.setheader(jwt_header_name, newtoken); } |
將驗(yàn)證操作放在filter里,這樣除了登錄入口,其它的業(yè)務(wù)代碼將感覺(jué)不到j(luò)wt的存在。
將登錄入口放在white_list里,跳過(guò)對(duì)這些入口的驗(yàn)證。
需要刷新jwt。如果jwt是合法的,那么應(yīng)該用同樣的payload來(lái)生成一個(gè)新的jwt,這樣新的jwt就會(huì)有新的過(guò)期時(shí)間,用此操作來(lái)刷新jwt,以防過(guò)期。
如果使用filter,那么刷新的操作要在調(diào)用dofilter()之前,因?yàn)檎{(diào)用之后就無(wú)法再修改response了。
api
1
2
3
4
5
6
7
8
9
|
private final static string jwt_header_name = "authorization" ; @getmapping ( "/api" ) public string testapi(httpservletrequest request, httpservletresponse response) { string oldjwt = request.getheader(jwt_header_name); string newjwt = response.getheader(jwt_header_name); return string.format( "your old jwt is:\n%s \nyour new jwt is:\n%s\n" , oldjwt, newjwt); } |
這時(shí)候api就處于jwt的保護(hù)下了。api可以完全不用感知到j(luò)wt的存在,同時(shí)也可以主動(dòng)獲取jwt并解碼,以得到j(luò)wt里的信息。如上所示。
尾注
完整的demo可以在這里找到:https://github.com/beginner258/jwt-demo
參考資料:https://jwt.io/
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)服務(wù)器之家的支持。
原文鏈接:https://www.cnblogs.com/xz816111/p/9620139.html