前言
本篇和大家分享jwt(json web token)的使用,她主要用來生成接口訪問的token和驗證,其單獨結合springboot來開發api接口token驗證很是方便,由于jwt的token中存儲有用戶的信息并且有加密,所以適用于分布式,這樣直接吧信息存儲在用戶本地減速了服務端存儲sessiion或token的壓力;
如下快速使用:
1
2
3
4
5
6
7
8
9
10
11
12
|
<!--jwt--> <dependency> <groupid>io.jsonwebtoken</groupid> <artifactid>jjwt</artifactid> <version> 0.9 . 0 </version> </dependency> <!--阿里 fastjson依賴--> <dependency> <groupid>com.alibaba</groupid> <artifactid>fastjson</artifactid> <version> 1.2 . 44 </version> </dependency> |
一般使用jwt來達到3種結果:
- 生成token
- 驗證token是否有效
- 獲取token中jwt信息(主要用戶信息)
生成token
引入了jjwt依賴后,要生成token很方便;對于一個token來說,代表的是唯一并且不可逆的,因此我們在生成時需要增加一些唯一數據進去,比如下面的id:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
long currenttime = system.currenttimemillis(); return jwts.builder() .setid(uuid.randomuuid().tostring()) .setissuedat( new date(currenttime)) //簽發時間 .setsubject( "system" ) //說明 .setissuer( "shenniu003" ) //簽發者信息 .setaudience( "custom" ) //接收用戶 .compresswith(compressioncodecs.gzip) //數據壓縮方式 .signwith(signaturealgorithm.hs256, encrykey) //加密方式 .setexpiration( new date(currenttime + secondtimeout * 1000 )) //過期時間戳 .addclaims(claimmaps) //cla信息 .compact(); |
通過uuid來標記唯一id信息;當然在對token加密時需要用到秘鑰,jwt很是方便她支持了很多中加密方式如:hs256,hs265,md5等復雜及常用的加密方式;
jwt生成的token中內容分為3個部分:head信息,payload信息,sign信息,通常我們要做的是往payload增加一些用戶信息(比如:賬號,昵稱,權限等,但不包含密碼);在對jwt的token有一定了解后,我們來看下真實生成的token值:
1
|
eyjhbgcioijiuzi1niisinppcci6ikdasvaifq.h4siaaaaaaaaafwmtq7cibse7_lwkpdzaesp4qnyinciptx4ine0vbtg4sllfppn7hatgwbwg1bkl4grcbeciwpujzf8iiepjxfapaag2reypuecr2vxyed13nb0pplw3hp1eenblqsquiffy0ohurl3i70evweu_afsejzhd7dlcdv5ntmyhuilhtd3rf_hacchrtv--7yaaaa.i4xwoqtawi0-dwhwn8uz4dbm-vfli5bavyu9lryxu5e |
驗證token是否有效
token生成的時都會伴隨者有一個失效的時間,在這我們可以通過setexpiration函數設置過期時間,記住jwt的有效時間不是滑動的,也就是說不做任何處理時,當到達第一次設置的失效時間時,就基本沒用了,要獲取token是否過期可以使用如下方式:
1
2
3
4
5
6
7
8
9
|
public static boolean isexpiration(string token, string encrykey) { try { return getclaimsbody(token, encrykey) .getexpiration() .before( new date()); } catch (expiredjwtexception ex) { return true ; } } |
這里使用了date的before來用獲取的過期時間和當前時間對比,判斷是否繼續有效,需要注意的是如果在token失效后再通過getclaimsbody(token, encrykey)獲取信息,此時會報expiredjwtexception錯誤,我們即可認為過期。
獲取token中jwt信息(主要用戶信息)
通常我們要把登錄用戶信息存儲在jwt生成的token中,這里可以通過 addclaims(claimmaps) 傳遞map來設置信息,反過來要獲取token中的用戶信息,我們需要這樣做:
1
2
3
4
|
return jwts.parser() .setsigningkey(encrykey) .parseclaimsjws(token) .getbody(); |
此時body獲取出來是claims類型,我們需要從中獲取到用戶信息,需要注意的是在addclaims存儲信息的時候如果存儲的map值沒做過出來,那完整的實體對象存儲進去后會映射成一個linkhasmap類型,如下:
因此通常會在存儲的時候json化,如下代碼:
1
2
3
|
claimmaps.foreach((key, val) -> { claimmaps.put(key, json.tojsonstring(val)); }); |
再來就是通過get方法獲取我們存儲進去的信息,并json反序列化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/** * 獲取body某個值 * * @param token * @param encrykey * @param key * @return */ public static object getval(string token, string encrykey, string key) { return getjws(token, encrykey).getbody().get(key); } /** * 獲取body某個值,json字符轉實體 * * @param token * @param encrykey * @param key * @param tclass * @param <t> * @return */ public static <t> t getvalbyt(string token, string encrykey, string key, class <t> tclass) { try { string strjson = getval(token, encrykey, key).tostring(); return json.parseobject(strjson, tclass); } catch (exception ex) { return null ; } } |
來到這里一個jwt的util代碼基本就完成了,下面給出完整的代碼例子,僅供參考:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
public class jwtutil { /** * 獲取token - json化 map信息 * * @param claimmaps * @param encrykey * @param secondtimeout * @return */ public static string gettokenbyjson(map<string, object> claimmaps, string encrykey, int secondtimeout) { return gettoken(claimmaps, true , encrykey, secondtimeout); } /** * 獲取token * * @param claimmaps * @param isjsonmpas * @param encrykey * @param secondtimeout * @return */ public static string gettoken(map<string, object> claimmaps, boolean isjsonmpas, string encrykey, int secondtimeout) { if (isjsonmpas) { claimmaps.foreach((key, val) -> { claimmaps.put(key, json.tojsonstring(val)); }); } long currenttime = system.currenttimemillis(); return jwts.builder() .setid(uuid.randomuuid().tostring()) .setissuedat( new date(currenttime)) //簽發時間 .setsubject( "system" ) //說明 .setissuer( "shenniu003" ) //簽發者信息 .setaudience( "custom" ) //接收用戶 .compresswith(compressioncodecs.gzip) //數據壓縮方式 .signwith(signaturealgorithm.hs256, encrykey) //加密方式 .setexpiration( new date(currenttime + secondtimeout * 1000 )) //過期時間戳 .addclaims(claimmaps) //cla信息 .compact(); } /** * 獲取token中的claims信息 * * @param token * @param encrykey * @return */ private static jws<claims> getjws(string token, string encrykey) { return jwts.parser() .setsigningkey(encrykey) .parseclaimsjws(token); } public static string getsignature(string token, string encrykey) { try { return getjws(token, encrykey).getsignature(); } catch (exception ex) { return "" ; } } /** * 獲取token中head信息 * * @param token * @param encrykey * @return */ public static jwsheader getheader(string token, string encrykey) { try { return getjws(token, encrykey).getheader(); } catch (exception ex) { return null ; } } /** * 獲取payload body信息 * * @param token * @param encrykey * @return */ public static claims getclaimsbody(string token, string encrykey) { return getjws(token, encrykey).getbody(); } /** * 獲取body某個值 * * @param token * @param encrykey * @param key * @return */ public static object getval(string token, string encrykey, string key) { return getjws(token, encrykey).getbody().get(key); } /** * 獲取body某個值,json字符轉實體 * * @param token * @param encrykey * @param key * @param tclass * @param <t> * @return */ public static <t> t getvalbyt(string token, string encrykey, string key, class <t> tclass) { try { string strjson = getval(token, encrykey, key).tostring(); return json.parseobject(strjson, tclass); } catch (exception ex) { return null ; } } /** * 是否過期 * * @param token * @param encrykey * @return */ public static boolean isexpiration(string token, string encrykey) { try { return getclaimsbody(token, encrykey) .getexpiration() .before( new date()); } catch (expiredjwtexception ex) { return true ; } } public static string getsubject(string token, string encrykey) { try { return getclaimsbody(token, encrykey).getsubject(); } catch (exception ex) { return "" ; } } } |
過濾器驗證token
有了基本的jwtutil工具,我們需要用到springboot項目中,一般來說對于登錄授權token驗證可以通過過濾器來操作,這里創建一個authenfilter,用于對post請求過來的token做驗證:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public class authenfilter implements filter { @override public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception { httpservletrequest rq = (httpservletrequest) servletrequest; httpservletresponse rp = (httpservletresponse) servletresponse; rpbase rpbase = new rpbase(); try { //只接受post if (!rq.getmethod().equalsignorecase( "post" )) { filterchain.dofilter(servletrequest, servletresponse); return ; } string token = rq.getheader( "token" ); if (stringutils.isempty(token)) { rpbase.setmsg( "無token" ); return ; } //jwt驗證 mouser mouser = jwtutil.getvalbyt(token, webconfig.token_encrykey, webconfig.login_user, mouser. class ); if (mouser == null ) { rpbase.setmsg( "token已失效" ); return ; } system.out.println( "token用戶:" + mouser.getnickname()); filterchain.dofilter(servletrequest, servletresponse); } catch (exception ex) { } finally { if (!stringutils.isempty(rpbase.getmsg())) { rp.setcharacterencoding( "utf-8" ); rpbase.setcode(httpstatus.bad_request.value()); rp.getwriter().write(json.tojsonstring(rpbase)); } } } } |
要是自定義過濾器authenfilter生效,還需要把她注冊到容器中,這里通過編碼方式,當然還可以通過@webfilter注解來加入到容器中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@configuration public class webfilterconfig { @bean public filterregistrationbean setfilter() { filterregistrationbean registrationbean = new filterregistrationbean(); registrationbean.setfilter( new authenfilter()); registrationbean.addurlpatterns( "/api/*" ); registrationbean.setorder(filterregistrationbean.lowest_precedence); return registrationbean; } } |
注意addurlpatterns匹配的是過濾器作用的url連接,根據需求而定;為了驗證效果,這里我創建了兩個接口gettoken和t0,分別是獲取token和post查詢接口,代碼如是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@restcontroller public class testcontroller { @postmapping ( "/api/t0" ) public string t0() throws myexception { return uuid.randomuuid().tostring(); } @getmapping ( "/token/{username}" ) public string gettoken( @pathvariable string username) { mouser mouser = new mouser(); mouser.setusername(username); mouser.setnickname(username); map<string, object> map = new hashmap<>(); map.put(webconfig.login_user, mouser); return jwtutil.gettokenbyjson(map, webconfig.token_encrykey, webconfig.token_secondtimeout); } } |
最終要獲通過head傳遞token值來訪問t01接口,得到如下結果:
token在有效時間后訪問直接失敗,從新獲取token并訪問t01接口,得到成功的信息:
git地址: https://github.com/shenniubuxing3
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnblogs.com/wangrudong003/p/10122706.html