JWT學(xué)習(xí)(二):JWT在分布式SSO中的應(yīng)用實例
上一篇文章講解了JWT的基本簡介,這一篇文章我就來實戰(zhàn)一下。介紹一下在分布式單點登錄中的使用方法:
首先來看一下Token實體類,
public?class?Token?implements?Serializable{ private?static?final?long?serialVersionUID?=?-5391652691006115018L; /**?認(rèn)證頭?**/ private?Head?head; /**?認(rèn)證信息有效載荷?**/ private?Playload?playload; /**?第一部分:base64head頭?**/ private?String?base64Head; /**?第二部分:base64playload荷載?**/ private?String?base64PlayLoad; /**?第三部分:簽證信息?**/ private?String?signature; /**?最終token字符串?**/ private?String?tokenStr; /** ?*?Token頭 ?* ?* ?*/ public?static?class?Head?implements?Serializable{ private?static?final?long?serialVersionUID?=?-6516084948347601103L; /**?token類型?**/ private?String?typ?=?"JWT"; /**?token算法?默認(rèn):HMAC?SHA256**/ private?String?alg?=?"HS256"; public?String?getTyp()?{ return?typ; } public?void?setTyp(String?typ)?{ this.typ?=?typ; } public?String?getAlg()?{ return?alg; } public?void?setAlg(String?alg)?{ this.alg?=?alg; } } /** ?*?Token有效載荷 ?*? ?* ?*/ public?static?class?Playload?implements?Serializable?{ private?static?final?long?serialVersionUID?=?3981890375700111920L; /**?該token簽發(fā)者?**/ private?String?iss; /**?該token的所有人,可以存放用戶名?**/ private?String?sub; /**?接收token的一方?**/ private?String?aud; /**?token的過期時間(時間戳),必須要大于簽發(fā)時間;大于等于該時間需要刷新token?**/ private?long?exp; /**?token生效的開始時間(時間戳),意味著在這個時間之前驗證token是會失敗的,默認(rèn)生成token后立即生效?**/ private?long?nbf; /**?token的簽發(fā)時間?時間戳**/ private?long?iat; /**?token的唯一身份標(biāo)識,主要用來作為一次性token,從而回避重放攻擊?**/ private?String?jti; /**?token驗證寬限時間(時間戳)?超過寬限時間需要重新登錄,?? ?*?即該token的真正存活時間,寬限時間的加入是為了解決并發(fā)token刷新后新token失效問題 ?*?**/ private?long?gra; /**?token類型:??后臺登錄用戶,互聯(lián)網(wǎng)用戶,第三方機構(gòu)?**/ private?String?typ; public?String?getIss()?{ return?iss; } public?void?setIss(String?iss)?{ this.iss?=?iss; } public?String?getSub()?{ return?sub; } public?void?setSub(String?sub)?{ this.sub?=?sub; } public?String?getAud()?{ return?aud; } public?void?setAud(String?aud)?{ this.aud?=?aud; } public?long?getExp()?{ return?exp; } public?void?setExp(long?exp)?{ this.exp?=?exp; } public?long?getNbf()?{ return?nbf; } public?void?setNbf(long?nbf)?{ this.nbf?=?nbf; } public?long?getIat()?{ return?iat; } public?void?setIat(long?iat)?{ this.iat?=?iat; } public?String?getJti()?{ return?jti; } public?void?setJti(String?jti)?{ this.jti?=?jti; } public?long?getGra()?{ return?gra; } public?void?setGra(long?gra)?{ this.gra?=?gra; } public?String?getTyp()?{ return?typ; } public?void?setTyp(String?typ)?{ this.typ?=?typ; } } public?Head?getHead()?{ return?head; } public?void?setHead(Head?head)?{ this.head?=?head; } public?Playload?getPlayload()?{ return?playload; } public?void?setPlayload(Playload?playload)?{ this.playload?=?playload; } public?String?getSignature()?{ return?signature; } public?void?setSignature(String?signature)?{ this.signature?=?signature; } public?String?getBase64Head()?{ return?base64Head; } public?void?setBase64Head(String?base64Head)?{ this.base64Head?=?base64Head; } public?String?getBase64PlayLoad()?{ return?base64PlayLoad; } public?void?setBase64PlayLoad(String?base64PlayLoad)?{ this.base64PlayLoad?=?base64PlayLoad; } public?String?getTokenStr()?{ return?tokenStr; } public?void?setTokenStr(String?tokenStr)?{ this.tokenStr?=?tokenStr; } }
可以看到實體類里面包含了head payload signature這三部分。
然后用戶登錄成功之后創(chuàng)建token的代碼如下:
public?static?Token?createToken(String?secret,String?tokenId,TokenType?tokenType,String?userName)?{ try?{ Token?token?=?new?Token(); //創(chuàng)建頭 Token.Head?head?=?new?Token.Head(); head.setAlg("HS256"); head.setTyp("JWT"); //創(chuàng)建載荷 //簽發(fā)時間 long?iat?=?System.currentTimeMillis(); //過期時間?20?分鐘后過期 long?exp?=?iat?+?AuthConstants.TOKEN_EXP_TIME?; //最后存活時間 long?gra?=?iat?+?AuthConstants.TOKEN_GRA_TIME?; Token.Playload?playload?=?new?Token.Playload(); playload.setAud("CLIENT");?//接收token的一方 playload.setIat(iat);?//簽發(fā)時間 playload.setExp(exp); playload.setGra(gra); playload.setIss("AUTH_CENTER");//簽發(fā)者 playload.setJti(tokenId);?//token唯一身份標(biāo)識 playload.setNbf(iat);//生效時間,立即生效 playload.setSub(userName);?//token的所屬者,可以存放用戶名 playload.setTyp(tokenType.toString().toUpperCase()); //創(chuàng)建token String?base64Head?=?Base64Util.encodeStr(JSONUtil.toJson(head)?); String?base64Playload?=?Base64Util.encodeStr(JSONUtil.toJson(playload)?); String?signature?=?HmacUtil.encryptHMACSHA256(base64Head+"."+base64Playload,?secret);?//token簽名 String?tokenStr?=?base64Head+"."+base64Playload+"."+signature;?//token字符串 //組裝token對象 token.setHead(head); token.setPlayload(playload); token.setBase64Head(base64Head); token.setBase64PlayLoad(base64Playload); token.setSignature(signature); token.setTokenStr(tokenStr); return?token; }?catch?(Exception?e)?{ e.printStackTrace(); logger.error("生成token失敗:{}",e.getMessage()); } return?null; }
創(chuàng)建成功之后,要把token放到響應(yīng)頭中,setHeader方法name參數(shù)要用Authorization,value值要使用
"Bearer "+token。
然后用戶訪問需要權(quán)限的接口都需要在請求頭加上token,因為使用了Spring Cloud微服務(wù)架構(gòu),因此請求
會統(tǒng)一通過API網(wǎng)關(guān)訪問,所以需要在網(wǎng)關(guān)處驗證token的合法性,使用下面這個parseToken的方法:
/** ?*?驗證并解析token ?*?@param?token?token字符串 ?*?@param?secret?token簽名的鹽(密鑰) ?*?@return?成功返回token?,失敗返回ull ?*/ public?static?Token?parseToken(String?token,String?secret)?{ try?{ //判斷token是否是合法格式的token串 if(StringUtils.isEmpty(token))?{ throw?new?AuthException("token串為空!"); } if(StringUtils.isEmpty(secret))?{ throw?new?AuthException("解析token時,token密鑰為空!"); } String[]?tokens?=?token.split("\."); if(tokens==null?||?tokens.length!=3)?{ throw?new?AuthException("非法格式的token串:"+token); } //token分解 String?base64Head?=?tokens[0].trim();?//token頭 String?base64Playload?=?tokens[1].trim();?//token載荷 String?signature?=?tokens[2].trim();?//token簽名 //驗證簽名是否為合法的 String?signData?=?base64Head+"."+base64Playload; String?signaturedStr?=?HmacUtil.encryptHMACSHA256(signData,?secret.trim());?//token簽名 if(!signature.equals(signaturedStr))?{ throw?new?AuthException("非法的token:解析token時,token驗簽失敗!"); } Token.Head?head?=?JSONUtil.toBean(Base64Util.decodeStr(base64Head),?Token.Head.class); Token.Playload?playLoad?=?JSONUtil.toBean(Base64Util.decodeStr(base64Playload),?Token.Playload.class); Token?rs?=?new?Token(); rs.setHead(head); rs.setPlayload(playLoad); rs.setSignature(signature); return?rs; }?catch?(Exception?e)?{ e.printStackTrace(); logger.error("解析token失敗:{}",e.getMessage()); return?null; } }
使用JWT進行身份驗證的基本方法的實例就到這里,沒有接觸過JWT的同學(xué)可以先看一下JWT