JWT使用

JWT公司的主流Json Web Token 令牌 如何使用感憾,取代session竿屹,還可以運用分布式認證

JWT簡介:

JWT(JSON WEB TOKEN):JSON網(wǎng)絡(luò)令牌,JWT是一個輕便的安全跨平臺傳輸格式缸榛,定義了一個緊湊的自包含的方式在不同實體之間安全傳輸信息(JSON格式)蚊俺。它是在Web環(huán)境下兩個實體之間傳輸數(shù)據(jù)的一項標(biāo)準(zhǔn)尿背。實際上傳輸?shù)木褪且粋€字符串。廣義上講JWT是一個標(biāo)準(zhǔn)的名稱橡伞;狹義上JWT指的就是用來傳遞的那個token字符串

jwt特點:

  • JWT無需存儲在服務(wù)器(不使用Session/Cookie)盒揉,不占用服務(wù)器資源(也就是Stateless無狀態(tài)的),也就不存在多服務(wù)器共享Session的問題
  • 使用簡單兑徘,用戶在登錄成功拿到 Token后刚盈,一般訪問需要權(quán)限的請求時,在Header附上Token即可挂脑。

**JWT公司官網(wǎng): **

https://jwt.io/

jwt的token結(jié)構(gòu):

JWT的數(shù)據(jù)結(jié)構(gòu)以及簽發(fā)的過程
JWT由三部分構(gòu)成:header(頭部)藕漱、payload(載荷)和signature(簽名)。
xxxxx.yyyyy.zzzzz

1崭闲、Header 頭部信息:指定類型和算法

{
  "alg": "HS256",
  "typ": "JWT"
}

typ:用來標(biāo)識整個token字符串是一個JWT字符串
alg:用來說明這個JWT簽發(fā)的時候所使用的簽名和摘要算法

常用的值以及對應(yīng)的算法如下:

加密算法

一般簽發(fā)JWT的時候肋联,header對應(yīng)的json結(jié)構(gòu)只需要typ和alg屬性就夠了。JWT的header部分是把前面的json結(jié)構(gòu)镀脂,經(jīng)過Base64Url編碼之后生成出來的

2牺蹄、Payload 荷載信息:存放Claims聲明信息既主體信息組成。用來存儲JWT基本信息薄翅,或者是我們的信息沙兰。

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true 
}

payload用來承載要傳遞的數(shù)據(jù),它的json結(jié)構(gòu)實際上是對JWT要傳遞的數(shù)據(jù)的一組聲明翘魄,這些聲明被JWT標(biāo)準(zhǔn)稱為claims鼎天。當(dāng)后面對JWT進行驗證的時候,這些claim都能發(fā)揮特定的作用暑竟。

sub代表這個token的所有人斋射,存儲的是所有人的ID育勺;
name表示這個所有人的名字;
admin表示所有人是否管理員的角色罗岖。

根據(jù)JWT的標(biāo)準(zhǔn)涧至,這些claims可以分為以下三種類型:

  • Reserved claims
    保留的claims都是可選的,JWT標(biāo)準(zhǔn)里面針對它自己規(guī)定的claim都提供了有詳細的驗證規(guī)則描述桑包,每個實現(xiàn)庫都會參照這個描述來提供JWT的驗證實現(xiàn)南蓬。

JWT標(biāo)準(zhǔn)的claim:

屬性 全稱 描述 認證
iss Issuser 代表簽發(fā)主體 完全匹配
sub Subject 代表的主體递鹉,它的所有人 完全匹配
aud Audience 代表JWT的接收對象 包含即可
exp Expiration time 代表這個JWT的過期時間 過期失敗
nbf Not Before 生效時間 早于該時間失敗
iat Issued at 簽發(fā)時間盼忌,maxAge之類的驗證 大于指定值失敗
jti JWT ID JWT的唯一標(biāo)識 完全匹配
  • Public claims
    不重要

  • Private claims
    自定義的claim不會驗證,除非明確告訴接收方要對這些claim進行驗證以及規(guī)則才行刀荒;標(biāo)準(zhǔn)的claim知道如何進行驗證弱左。

把這個json結(jié)構(gòu)做base64url編碼之后窄陡,就能生成payload部分的串:

image

3、Signature

把前兩者對應(yīng)的Json結(jié)構(gòu)進行base64url編碼之后的字符串拼接起來和密鑰放一起加密后的簽名拆火,驗證是否是我們服務(wù)器發(fā)起的Token跳夭,secret是我們的密鑰。使用base64拼接很容易破解榜掌,所以建議在傳輸過程中采用ssl加密是最穩(wěn)妥的优妙。

組成方式為: header.payload.signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiLlvKDlvLoiLCJuYW1lIjoiNzg5IiwiZXhwIjoxNTI4MzY0MjU5LCJpYXQiOjE1MjgzNjMwNTl9.574koY-c9SqMNNzfvAWQuKEnimWeZAcoFQ5XudNWF3o

下面例子使用HMAC SHA256算法進行簽名

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

拿前面的header和payload串來測試,并把“secret”這個字符串就當(dāng)成密鑰來測試:

image

最后的結(jié)果B其實就是JWT需要的signature憎账。不過對比我在介紹JWT的開始部分給出的JWT的舉例:

image

在線工具生成的signature有細微區(qū)別套硼,在于最后是否有“=”字符。這些實現(xiàn)庫最后編碼用base64url編碼胞皱,在線工具都是bas64編碼邪意,導(dǎo)致編碼結(jié)果有區(qū)別。

JWT的驗證過程

WT的驗證規(guī)則主要包括簽名驗證和payload里面各個標(biāo)準(zhǔn)claim的驗證反砌。驗證成功的JWT雾鬼,才能當(dāng)做有效的憑證來使用。

  • 簽名驗證:對JWT的完整性進行驗證就是簽名認證宴树。
  • 驗證方法:把header做base64url解碼策菜,獲取JWT簽名算法,用該算法再次用同樣的邏輯對header和payload做一次簽名酒贬。獲得的串與JWT本身的第三個部分的串比較又憨。不同認為這個JWT被篡改過,驗證失敗了锭吨。
  • 生成簽名:接收方生成簽名須使用跟JWT發(fā)送方相同的密鑰蠢莺,做好密鑰的安全傳遞或共享。

簽名認證是每個實現(xiàn)庫都會自動做的零如,但是payload的認證是由使用者來決定的躏将。因為JWT里面可能不會包含任何一個標(biāo)準(zhǔn)的claim锄弱,所以它不會自動去驗證這些claim。

登錄認證在簽發(fā)JWT時祸憋,可以用sub存儲用戶的id会宪,用exp存儲它本次登錄之后的過期時間,然后在驗證的時候僅驗證exp這個claim蚯窥,以實現(xiàn)會話的有效期管理狈谊。

JWT 令牌注銷或者銷毀

痛點:當(dāng)用戶點擊了“注銷”按鈕,用戶的令牌在客戶端會從授權(quán)認證服務(wù)移除沟沙,但此令牌仍舊是有效,可以被攻擊者竊取到用于API調(diào)用壁榕,直至jsonwebtoken的有效時間結(jié)束矛紫。

解決方案:利用Redis撤銷JSON Web Token產(chǎn)生的令牌,當(dāng)用戶點擊注銷按鈕時牌里。且令牌在Redis存儲的時間與令牌在jsonwebtoken中定義的有效時間相同颊咬。當(dāng)有效時間到了后,令牌會自動被Redis刪除牡辽。應(yīng)用檢查各終端上傳的令牌在Redis中是否存在喳篇。

客戶端發(fā)起請求

image

服務(wù)器端獲取Token

image

你該如何去理解JWT幫你做些什么?

首先前端登錄后端通過用戶名與密碼驗證成功后,使用jwt生成一個具備有效期的token令牌, 這個token里面存儲著 token本身的結(jié)構(gòu)态辛,加上token的過期時間麸澜,和一個自己定義的字符串類似于加密簽名所用的鹽,這個鹽只有你自己知道奏黑,還可以包含一些自定義的用戶信息放在荷載信息里炊邦。

所涉及的jar包如下:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<!-- 阿里巴巴json -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.4</version>
</dependency>

生成Token

public class JwtUtil { 
    private static Logger logger = Logger.getLogger(JwtUtil.class); 
    token的key 也是名 不要寫成token這樣,要按照規(guī)范來
    public static final String TOKEN_HEADER = "Authorization";
    token值的前綴熟史,這是一種規(guī)范
    public static final String TOKEN_PREFIX = "Bearer ";
    加密時候用  是對稱的秘鑰鹽
    private static final String SECRET = "mrLang";
    獲取用戶的功能使用的key
    public static final String FUNCTS = "FUNCTS";
    獲取用戶使用的key
    public static final String USERINFO = "USER";
    token的生命周期30分
    private static final long EXPIRATION = 1800L;
    /**
     * 創(chuàng)建token令牌 以下為參數(shù)都是自定義信息
     * @param loginName  一般我們放用戶的唯一標(biāo)識登錄名
     * @param functs 當(dāng)前用戶的功能集合馁害, 
     * 本人的rbac權(quán)限比較個性化且很負責(zé),一般你們放role角色就可以了
     * @param user 當(dāng)前用戶
     * @return 
      */
    public static String createToken(String loginName, 
      List<Object> functs, Users user) {
        Map<String, Object> map = new HashMap<>(); //當(dāng)前用戶擁有的功能
        map.put(FUNCTS, JsonUtil.set(functs)); //當(dāng)前用戶信息
        map.put(USERINFO, JsonUtil.set(user));

        設(shè)置HS256加密蹂匹,并且把你的鹽 放里碘菜,這里推薦使用SH256證書加密

        String token = Jwts.builder()
                .setSubject(loginName)//主題  主角是誰?    賦值登錄名
                .setClaims(map)
                .setIssuedAt(new Date())//設(shè)置發(fā)布時間限寞,也是生成時間
                .setExpiration(new Date(System.currentTimeMillis() 
                  + EXPIRATION * 1000))//設(shè)置過期時間
                .signWith(SignatureAlgorithm.HS256, SECRET)
                .compact();//創(chuàng)建完成
        return token;
}

公共獲取自定義數(shù)據(jù)

public static Claims getTokenBody(String token) { 
  return Jwts.parser().setSigningKey(SECRET)
            .parseClaimsJws(token).getBody();
}

驗證Token是否過期

public static boolean isExpiration(String token) {
  try { 
      return getTokenBody(token).getExpiration().before(new Date());
    } catch (Exception e) { 
      return true;
    }
}

獲取自定義的信息

// 獲取主角忍啸,登錄名
public static String getUserName(String token) { 
  return getTokenBody(token).getSubject();
} 

// 獲取token中存儲的功能
public static List<Object> getUserFuncts(String token) {
    String str = getTokenBody(token).get(FUNCTS).toString();
    List<Object> list = JsonUtil.getArray(str); 
    return list;
} 

// 獲取token存儲的用戶
public static Object getUser(String token) {
    String str = getTokenBody(token).get(USERINFO).toString(); 
    return JsonUtil.getObj(str);
} 

刷新Token

public static String refreshToken(String token) { 
  if (isExpiration(token)) {
      logger.info("token刷新失敗@ニ浮吊骤! 過期了!静尼!"); return null;
  } 

  // 獲取用戶 權(quán)限信息
  String functs = getTokenBody(token).get(FUNCTS).toString();
  String user = getTokenBody(token).get(USERINFO).toString();
  String username = getTokenBody(token).getSubject();
  Map<String, Object> map = new HashMap<>();
  map.put(FUNCTS, JsonUtil.set(functs));
  map.put(USERINFO, JsonUtil.set(user));
  token = Jwts.builder().signWith(SignatureAlgorithm.HS256, SECRET)
     .setClaims(map).setSubject(username)
     .setIssuedAt(new Date())
     .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
     .compact(); 

return token;
}

token發(fā)送給前端

傳入當(dāng)前用戶的功能與用戶信息白粉,登錄名生成token传泊,寫入response的返回頭中,前端獲取后保存在前端的本地緩存中鸭巴,后續(xù)前端請求要把token放在頭header里眷细。

//登錄成功之后
List<Object> functs=(List<Object>) authResult.getAuthorities();
//當(dāng)前功能列表
String loginName=authResult.getName();//登錄名
Users obj=(Users)authResult.getPrincipal();//用戶信息
String token=JwtUtil.createToken(loginName,functs,obj);

//生成token  TOKEN_HEADER= Authorization TOKEN_PREFIX=Bearer token值
response.setHeader(JwtUtil.TOKEN_HEADER,JwtUtil.TOKEN_PREFIX+token);
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK); //個人編寫的視圖對象
DTO dto=new DTO<>();
dto.setCode("000000");
dto.setMessage("認證通過");

PrintWriter pw=response.getWriter();
pw.write(JsonUtil.set(dto));//寫入json
pw.flush();//強制刷新
pw.close();//關(guān)閉流

驗證用戶請求攜帶token

你可以自定義一個過濾器,或者使用某某框架鹃祖,自定義攔截器或者aop溪椎。

String header = request.getHeader(JwtUtil.TOKEN_HEADER);
 if (null == header || !header.toLowerCase().startsWith(JwtUtil.TOKEN_PREFIX)) { 
  // 如果頭部 Authorization 未設(shè)置或者不是 basic 認證頭部,則當(dāng)前 
  // 請求不是該過濾器關(guān)注的對象恬口,直接放行校读,繼續(xù)filter chain 的執(zhí)行
   chain.doFilter(request, response);
   return;
} 

try {
  String token = header.replace(JwtUtil.TOKEN_PREFIX, ""); 
  // 驗證token是否過期
  if (JwtUtil.isExpiration(token)) { 
      throw new javax.security.sasl.AuthenticationException("token 驗證不通過");
} 

//檢查token是否能解析
Users user = (Users) JwtUtil.getUser(token); 
if (null == user) { 
    throw new javax.security.sasl.AuthenticationException("token 驗證不通過");
} 

//驗證成功

JWT簽名算法中HS256和RS256有什么區(qū)別?

JWT簽名算法:HS256 和 RS256祖能。
簽名實際上是一個加密的過程歉秫,生成一段標(biāo)識(也是JWT的一部分)作為接收方驗證信息是否被篡改的依據(jù)。

RS256 (采用SHA-256 的 RSA 簽名) 是一種非對稱算法, 它使用公共/私鑰對: 標(biāo)識提供方采用私鑰生成簽名, JWT 的使用方獲取公鑰以驗證簽名养铸。由于公鑰 (與私鑰相比) 不需要保護雁芙,因此大多數(shù)標(biāo)識提供方使其易于使用方獲取和使用 (通常通過一個元數(shù)據(jù)URL)。

HS256 (帶有 SHA-256 的 HMAC對稱算法, 雙方之間僅共享一個密鑰钞螟。由于使用相同的密鑰生成簽名和驗證簽名兔甘,因此必須注意確保密鑰不被泄密。

在開發(fā)應(yīng)用的時候啟用JWT鳞滨,使用RS256更加安全洞焙,你可以控制誰能使用什么類型的密鑰。另外拯啦,如果你無法控制客戶端闽晦,無法做到密鑰的完全保密,RS256會是個更佳的選擇提岔,JWT的使用方只需要知道公鑰仙蛉。

建議:

為了雙重保險,建議您使用RS256碱蒙,最好是生成一個證書讀取使用證書里面的公鑰加密荠瘪,私鑰留著以后每次前端請求驗證token的合法性就可以了
jwt提供了方法可以驗證token的合法性,過期時間赛惩,還可以根據(jù)token讀取用戶信息哀墓,這樣你把token存儲前端的localstorage中,
后端的緩存都省了

這樣做安全么喷兼?
你要知道 RS256非對稱加密都是一套體系的篮绰,公鑰私鑰是一組, 都存在的后端季惯,別人不進你服務(wù)器盜取你就是安全的吠各,要是真能登錄你服務(wù)器那就直接刪你庫了臀突,同樣本文用大的對稱加密也是 你自定定義的秘鑰 鹽 本文的mrlang都是存在你自己的服務(wù)器中,別人是不知道的贾漏,

分布式中如何驗證Token候学?

你可以把token工具類copy到不同的機器上,讓每臺機器自己去做驗證纵散,只要你每臺機器token工具類里存儲的 鹽或者一對秘鑰 都一致 那么驗證就會得到一致通過 梳码,這樣做的好處就是 你不必遠調(diào)用遠程驗證服務(wù)器了,所以真的很好用伍掀,換個思維想想 你去調(diào)用驗證服務(wù)器還需要花費很多時間掰茶,這對訪問量特別大的項目來說,壓力真的不小蜜笤,所以本文的JWT Token 不管作為單機符匾,集群,分布式 你都值得擁有

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瘩例,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子甸各,更是在濱河造成了極大的恐慌垛贤,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趣倾,死亡現(xiàn)場離奇詭異聘惦,居然都是意外死亡,警方通過查閱死者的電腦和手機儒恋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門善绎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诫尽,你說我怎么就攤上這事禀酱。” “怎么了牧嫉?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵剂跟,是天一觀的道長。 經(jīng)常有香客問我酣藻,道長曹洽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任辽剧,我火速辦了婚禮送淆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怕轿。我一直安慰自己偷崩,他們只是感情好辟拷,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著环凿,像睡著了一般梧兼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上智听,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天羽杰,我揣著相機與錄音,去河邊找鬼到推。 笑死考赛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莉测。 我是一名探鬼主播颜骤,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼捣卤!你這毒婦竟也來了忍抽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤董朝,失蹤者是張志新(化名)和其女友劉穎鸠项,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體子姜,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡祟绊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了哥捕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牧抽。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖遥赚,靈堂內(nèi)的尸體忽然破棺而出扬舒,到底是詐尸還是另有隱情,我是刑警寧澤凫佛,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布呼巴,位于F島的核電站,受9級特大地震影響御蒲,放射性物質(zhì)發(fā)生泄漏衣赶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一厚满、第九天 我趴在偏房一處隱蔽的房頂上張望府瞄。 院中可真熱鬧,春花似錦、人聲如沸遵馆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽货邓。三九已至秆撮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間换况,已是汗流浹背职辨。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留戈二,地道東北人舒裤。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像觉吭,于是被迫代替她去往敵國和親腾供。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353