什么是jwt
jwt(json web token)是一個開放標準(rfc 7519),它定義了一種緊湊且獨立的方式,可以在各個系統之間用json作為對象安全地傳輸信息,并且可以保證所傳輸的信息不會被篡改。
「jwt」由三部分構成
- 信息頭:指定了使用的簽名算法
- 聲明部分:其中也可以包含超時時間
- 基于指定的算法生成的簽名
通過這三部分信息,api 服務端可以根據「jwt」信息頭和聲明部分的信息重新生成簽名。之所以可以這樣做,是因為生成簽名需要的秘鑰存放在服務器端。
1
|
jwtauth. new ( "hs256" , [] byte ( "k8uemdpyb9awfkzs" ), nil) |
如果這個簽名秘鑰比較簡單,建議立刻換一個復雜一些的,更改以后會使所有已經產生的「jwt」 失效,強制客戶端重新從服務器獲取授權。
jwt通常有兩種應用場景:
- 授權。這是最常見的jwt使用場景。一旦用戶登錄,每個后續請求將包含一個jwt,作為該用戶訪問資源的令牌。
- 信息交換??梢岳胘wt在各個系統之間安全地傳輸信息,jwt的特性使得接收方可以驗證收到的內容是否被篡改。
本文討論第一點,如何利用jwt來實現對api的授權訪問。這樣就只有經過授權的用戶才可以調用api。
jwt的結構
jwt由三部分組成,用.分割開。
header
第一部分為header,通常由兩部分組成:令牌的類型,即jwt,以及所使用的加密算法。
1
2
3
4
|
{ "alg" : "hs256" , "typ" : "jwt" } |
base64加密后,就變成了:
eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9
payload
第二部分為payload,里面可以放置自定義的信息,以及過期時間、發行人等。
1
2
3
4
5
|
{ "sub" : "1234567890" , "name" : "john doe" , "iat" : 1516239022 } |
base64加密后,就變成了:
eyjzdwiioiixmjm0nty3odkwiiwibmftzsi6ikpvag4grg9liiwiawf0ijoxnte2mjm5mdiyfq
signature
第三部分為signature,計算此簽名需要四部分信息:
- header里的算法信息
- header
- payload
- 一個自定義的秘鑰
接受到jwt后,利用相同的信息再計算一次簽名,然年與jwt中的簽名對比,如果不相同則說明jwt中的內容被篡改。
解碼后的jwt
將上面三部分都編碼后再合在一起就得到了jwt。
需要注意的是,jwt的內容并不是加密的,只是簡單的base64編碼。也就是說,jwt一旦泄露,里面的信息可以被輕松獲取,因此不應該用jwt保存任何敏感信息。
jwt是怎樣工作的
- 應用程序或客戶端向授權服務器請求授權。這里的授權服務器可以是單獨的一個應用,也可以和api集成在同一個應用里。
- 授權服務器向應用程序返回一個jwt。
- 應用程序將jwt放入到請求里(通常放在http的authorization頭里)
- 服務端接收到請求后,驗證jwt并執行對應邏輯。
在java里使用jwt
引入依賴
1
2
3
4
|
<dependency> <groupid>io.jsonwebtoken</groupid> <artifactid>jjwt</artifactid> </dependency> |
這里使用了一個叫jjwt(java jwt)的庫。
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(); } |
這里設置過期時間為10秒,因此生成的jwt只在10秒內能通過驗證。
需要提供一個自定義的秘鑰。
解碼jwt
1
2
3
4
5
6
7
|
public string parsetoken(string jwt) { return jwts.parser() .setsigningkey(secret_key) .parseclaimsjws(jwt) .getbody() .getsubject(); } |
解碼時會檢查jwt的簽名,因此需要提供秘鑰。
驗證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并沒有提供判斷jwt是否合法的方法,但是在解碼非法jwt時會拋出異常,因此可以通過捕獲異常的方式來判斷是否合法。
注冊/登錄
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); } |
需要為還沒有獲取到jwt的用戶提供一個這樣的注冊或者登錄入口,來獲取jwt。
獲取到響應里的jwt后,要在后續的請求里包含jwt,這里放在請求的authorization頭里。
驗證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); } |
將驗證操作放在filter里,這樣除了登錄入口,其它的業務代碼將感覺不到jwt的存在。
將登錄入口放在white_list里,跳過對這些入口的驗證。
需要刷新jwt。如果jwt是合法的,那么應該用同樣的payload來生成一個新的jwt,這樣新的jwt就會有新的過期時間,用此操作來刷新jwt,以防過期。
如果使用filter,那么刷新的操作要在調用dofilter()之前,因為調用之后就無法再修改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); } |
這時候api就處于jwt的保護下了。api可以完全不用感知到jwt的存在,同時也可以主動獲取jwt并解碼,以得到jwt里的信息。如上所示。
尾注
完整的demo可以在這里找到:https://github.com/beginner258/jwt-demo
參考資料:https://jwt.io/
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnblogs.com/xz816111/p/9620139.html