- jwt
jwt(json web token), 是為了在網絡應用環境間傳遞聲明而執行的一種基于json的開放標準((rfc 7519).該token被設計為緊湊且安全的,特別適用于分布式站點的單點登錄(sso)場景。jwt的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便于從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用于認證,也可被加密。
- jwt與其它的區別
通常情況下,把api直接暴露出去是風險很大的,不說別的,直接被機器攻擊就喝一壺的。那么一般來說,對api要劃分出一定的權限級別,然后做一個用戶的鑒權,依據鑒權結果給予用戶開放對應的api。目前,比較主流的方案有幾種:
oauth
oauth(開放授權)是一個開放的授權標準,允許用戶讓第三方應用訪問該用戶在某一服務上存儲的私密的資源(如照片,視頻),而無需將用戶名和密碼提供給第三方應用。
oauth 允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據。每一個令牌授權一個特定的第三方系統(例如,視頻編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,oauth讓用戶可以授權第三方網站訪問他們存儲在另外服務提供者的某些特定信息,而非所有內容
cookie/session auth
cookie認證機制就是為一次請求認證在服務端創建一個session對象,同時在客戶端的瀏覽器端創建了一個cookie對象;通過客戶端帶上來cookie對象來與服務器端的session對象匹配來實現狀態管理的。默認的,當我們關閉瀏覽器的時候,cookie會被刪除。但可以通過修改cookie 的expire time使cookie在一定時間內有效,基于session方式認證勢必會對服務器造成一定的壓力(內存存儲),不易于擴展(需要處理分布式session),跨站請求偽造的攻擊(csrf)
- jwt的優點
1.相比于session,它無需保存在服務器,不占用服務器內存開銷。
2.無狀態、可拓展性強:比如有3臺機器(a、b、c)組成服務器集群,若session存在機器a上,session只能保存在其中一臺服務器,此時你便不能訪問機器b、c,因為b、c上沒有存放該session,而使用token就能夠驗證用戶請求合法性,并且我再加幾臺機器也沒事,所以可拓展性好就是這個意思。
3.前后端分離,支持跨域訪問。
- jwt的組成
1
2
3
4
5
6
7
8
9
10
|
{ "iss" : "jwt builder" , "iat" : 1416797419 , "exp" : 1448333419 , "aud" : "www.battcn.com" , "givenname" : "levin" , "surname" : "levin" , "role" : [ "admin" , "member" ] } |
- iss: 該jwt的簽發者,是否使用是可選的;
- sub: 該jwt所面向的用戶,是否使用是可選的;
- aud: 接收該jwt的一方,是否使用是可選的;
- exp(expires): 什么時候過期,這里是一個unix時間戳,是否使用是可選的;
- iat(issued at): 在什么時候簽發的(unix時間),是否使用是可選的;
- nbf (not before):如果當前時間在nbf里的時間之前,則token不被接受;一般都會留一些余地,比如幾分鐘;,是否使用是可選的;
一個jwt實際上就是一個字符串,它由三部分組成,頭部、載荷、簽名(上圖依次排序)
jwt token生成器:
- 認證
- 登陸認證
- 客戶端發送 post 請求到服務器,提交登錄處理的controller層
- 調用認證服務進行用戶名密碼認證,如果認證通過,返回完整的用戶信息及對應權限信息
- 利用 jjwt 對用戶、權限信息、秘鑰構建token
- 返回構建好的token
- 請求認證
- 客戶端向服務器請求,服務端讀取請求頭信息(request.header)獲取token
- 如果找到token信息,則根據配置文件中的簽名加密秘鑰,調用jjwt lib對token信息進行解密和解碼;
- 完成解碼并驗證簽名通過后,對token中的exp、nbf、aud等信息進行驗證;
- 全部通過后,根據獲取的用戶的角色權限信息,進行對請求的資源的權限邏輯判斷;
- 如果權限邏輯判斷通過則通過response對象返回;否則則返回http 401;
無效token
有效token
- jwt的缺點
有優點就會有缺點,是否適用應該考慮清楚,而不是技術跟風
- token過大容易占用更多的空間
- token中不應該存儲敏感信息
- jwt不是 session ,勿將token當session
- 無法作廢已頒布的令牌,因為所有的認證信息都在jwt中,由于在服務端沒有狀態,即使你知道了某個jwt被盜取了,你也沒有辦法將其作廢。在jwt過期之前(你絕對應該設置過期時間),你無能為力。
- 類似緩存,由于無法作廢已頒布的令牌,在其過期前,你只能忍受”過期”的數據(自己放出去的token,含著淚也要用到底)。
- 代碼(片段)
tokenproperties 與 application.yml資源的key映射,方便使用
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
|
@configuration @configurationproperties (prefix = "battcn.security.token" ) public class tokenproperties { /** * {@link com.battcn.security.model.token.token} token的過期時間 */ private integer expirationtime; /** * 發行人 */ private string issuer; /** * 使用的簽名key {@link com.battcn.security.model.token.token}. */ private string signingkey; /** * {@link com.battcn.security.model.token.token} 刷新過期時間 */ private integer refreshexptime; // get set ... } |
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
@component public class tokenfactory { private final tokenproperties properties; @autowired public tokenfactory(tokenproperties properties) { this .properties = properties; } /** * 利用jjwt 生成 token * @param context * @return */ public accesstoken createaccesstoken(usercontext context) { optional.ofnullable(context.getusername()).orelsethrow(()-> new illegalargumentexception( "cannot create token without username" )); optional.ofnullable(context.getauthorities()).orelsethrow(()-> new illegalargumentexception( "user doesn't have any privileges" )); claims claims = jwts.claims().setsubject(context.getusername()); claims.put( "scopes" , context.getauthorities().stream().map(object::tostring).collect(tolist())); localdatetime currenttime = localdatetime.now(); string token = jwts.builder() .setclaims(claims) .setissuer(properties.getissuer()) .setissuedat(date.from(currenttime.atzone(zoneid.systemdefault()).toinstant())) .setexpiration(date.from(currenttime .plusminutes(properties.getexpirationtime()) .atzone(zoneid.systemdefault()).toinstant())) .signwith(signaturealgorithm.hs512, properties.getsigningkey()) .compact(); return new accesstoken(token, claims); } /** * 生成 刷新 refreshtoken * @param usercontext * @return */ public token createrefreshtoken(usercontext usercontext) { if (stringutils.isblank(usercontext.getusername())) { throw new illegalargumentexception( "cannot create token without username" ); } localdatetime currenttime = localdatetime.now(); claims claims = jwts.claims().setsubject(usercontext.getusername()); claims.put( "scopes" , arrays.aslist(scopes.refresh_token.authority())); string token = jwts.builder() .setclaims(claims) .setissuer(properties.getissuer()) .setid(uuid.randomuuid().tostring()) .setissuedat(date.from(currenttime.atzone(zoneid.systemdefault()).toinstant())) .setexpiration(date.from(currenttime .plusminutes(properties.getrefreshexptime()) .atzone(zoneid.systemdefault()).toinstant())) .signwith(signaturealgorithm.hs512, properties.getsigningkey()) .compact(); return new accesstoken(token, claims); } } |
配置文件,含token過期時間,秘鑰,可自行擴展
1
2
3
4
5
6
7
|
battcn: security: token: expiration-time: 10 # 分鐘 1440 refresh-exp-time: 30 # 分鐘 2880 issuer: http: //blog.battcn.com signing-key: battcn |
websecurityconfig 是 spring security 關鍵配置,在securrty中基本上可以通過定義過濾器去實現我們想要的功能.
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
|
@configuration @enablewebsecurity public class websecurityconfig extends websecurityconfigureradapter { public static final string token_header_param = "x-authorization" ; public static final string form_based_login_entry_point = "/api/auth/login" ; public static final string token_based_auth_entry_point = "/api/**" ; public static final string manage_token_based_auth_entry_point = "/manage/**" ; public static final string token_refresh_entry_point = "/api/auth/token" ; @autowired private restauthenticationentrypoint authenticationentrypoint; @autowired private authenticationsuccesshandler successhandler; @autowired private authenticationfailurehandler failurehandler; @autowired private loginauthenticationprovider loginauthenticationprovider; @autowired private tokenauthenticationprovider tokenauthenticationprovider; @autowired private tokenextractor tokenextractor; @autowired private authenticationmanager authenticationmanager; protected loginprocessingfilter buildloginprocessingfilter() throws exception { loginprocessingfilter filter = new loginprocessingfilter(form_based_login_entry_point, successhandler, failurehandler); filter.setauthenticationmanager( this .authenticationmanager); return filter; } protected tokenauthenticationprocessingfilter buildtokenauthenticationprocessingfilter() throws exception { list<string> list = lists.newarraylist(token_based_auth_entry_point,manage_token_based_auth_entry_point); skippathrequestmatcher matcher = new skippathrequestmatcher(list); tokenauthenticationprocessingfilter filter = new tokenauthenticationprocessingfilter(failurehandler, tokenextractor, matcher); filter.setauthenticationmanager( this .authenticationmanager); return filter; } @bean @override public authenticationmanager authenticationmanagerbean() throws exception { return super .authenticationmanagerbean(); } @override protected void configure(authenticationmanagerbuilder auth) { auth.authenticationprovider(loginauthenticationprovider); auth.authenticationprovider(tokenauthenticationprovider); } @override protected void configure(httpsecurity http) throws exception { http .csrf().disable() // 因為使用的是jwt,因此這里可以關閉csrf了 .exceptionhandling() .authenticationentrypoint( this .authenticationentrypoint) .and() .sessionmanagement() .sessioncreationpolicy(sessioncreationpolicy.stateless) .and() .authorizerequests() .antmatchers(form_based_login_entry_point).permitall() // login end-point .antmatchers(token_refresh_entry_point).permitall() // token refresh end-point .and() .authorizerequests() .antmatchers(token_based_auth_entry_point).authenticated() // protected api end-points .antmatchers(manage_token_based_auth_entry_point).hasanyrole(roleenum.admin.name()) .and() .addfilterbefore(buildloginprocessingfilter(), usernamepasswordauthenticationfilter. class ) .addfilterbefore(buildtokenauthenticationprocessingfilter(), usernamepasswordauthenticationfilter. class ); } } |
- 說點什么
由于jwt代碼做了簡單封裝,包含內容較多,所以文章里只貼主要片段,需要完整代碼可以直接從下面git獲取
本章代碼(battcn-jwt-service):battcn-cloud.rar
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:http://blog.csdn.net/memmsc/article/details/78122931