一峻村、什么是JWT
JWT全稱(chēng)JSON Web Token田巴,由三部分組成: header(頭)钠糊、payload(載體)、signature(簽名)壹哺。 隨著技術(shù)的發(fā)展抄伍,分布式web應(yīng)用的普及,通過(guò)session管理用戶(hù)登錄狀態(tài)成本越來(lái)越高管宵,因此慢慢發(fā)展成為token的方式做登錄身份校驗(yàn)截珍,然后通過(guò)token去取redis中的緩存的用戶(hù)信息,隨著之后jwt的出現(xiàn)箩朴,校驗(yàn)方式更加簡(jiǎn)單便捷化岗喉,無(wú)需通過(guò)redis緩存,而是直接根據(jù)token取出保存的用戶(hù)信息炸庞,以及對(duì)token可用性校驗(yàn)钱床,單點(diǎn)登錄更為簡(jiǎn)單。
header
JWT第一部分是header,header主要包含兩個(gè)部分,alg指加密類(lèi)型埠居,可選值為HS256查牌、RSA等等事期,typ=JWT為固定值,表示token的類(lèi)型纸颜。Payload
JWT第二部分是payload,payload是token的詳細(xì)內(nèi)容,一般包括iss (發(fā)行者), exp (過(guò)期時(shí)間), sub(用戶(hù)信息), aud (接收者),以及其他信息刑赶,詳細(xì)介紹請(qǐng)參考官網(wǎng),也可以包含自定義字段。
{
"iat": 1493090001,
"name": "張三"
}
iss:Issuer懂衩,發(fā)行者
sub:Subject撞叨,主題
aud:Audience,觀(guān)眾
exp:Expiration time浊洞,過(guò)期時(shí)間
nbf:Not before
iat:Issued at牵敷,發(fā)行時(shí)間
jti:JWT ID
-
signature
JWT第二部分是signature,這部分的內(nèi)容是這樣計(jì)算得來(lái)的:
1、EncodeString = Base64(header).Base64(payload)
2法希、最終token = HS256(EncodeString,"秘鑰")
簽名是用于驗(yàn)證消息在傳遞過(guò)程中有沒(méi)有被更改枷餐,并且,對(duì)于使用私鑰簽名的token苫亦,它還可以驗(yàn)證JWT的發(fā)送方是否為它所稱(chēng)的發(fā)送方毛肋。
二、JSON Web Tokens是如何工作的
無(wú)論何時(shí)用戶(hù)想要訪(fǎng)問(wèn)受保護(hù)的路由或者資源的時(shí)候屋剑,用戶(hù)代理(通常是瀏覽器)都應(yīng)該帶上JWT润匙,典型的,通常放在A(yíng)uthorization header中唉匾,用Bearer schema孕讳。
header應(yīng)該看起來(lái)是這樣的:
Authorization: Bearer <token>
服務(wù)器上的受保護(hù)的路由將會(huì)檢查Authorization header中的JWT是否有效,如果有效巍膘,則用戶(hù)可以訪(fǎng)問(wèn)受保護(hù)的資源厂财。如果JWT包含足夠多的必需的數(shù)據(jù),那么就可以減少對(duì)某些操作的數(shù)據(jù)庫(kù)查詢(xún)的需要峡懈,盡管可能并不總是如此璃饱。
如果token是在授權(quán)頭(Authorization header)中發(fā)送的,那么跨源資源共享(CORS)將不會(huì)成為問(wèn)題肪康,因?yàn)樗皇褂胏ookie荚恶。
- 驗(yàn)證過(guò)程
- 簽名驗(yàn)證
當(dāng)接收方接收到一個(gè)JWT的時(shí)候,首先要對(duì)這個(gè)JWT的完整性進(jìn)行驗(yàn)證梅鹦,這個(gè)就是簽名認(rèn)證裆甩。它驗(yàn)證的方法其實(shí)很簡(jiǎn)單,只要把header做base64url解碼齐唆,就能知道JWT用的什么算法做的簽名,然后用這個(gè)算法冻河,再次用同樣的邏輯對(duì)header和payload做一次簽名箍邮,并比較這個(gè)簽名是否與JWT本身包含的第三個(gè)部分的串是否完全相同茉帅,只要不同,就可以認(rèn)為這個(gè)JWT是一個(gè)被篡改過(guò)的串锭弊,自然就屬于驗(yàn)證失敗了堪澎。接收方生成簽名的時(shí)候必須使用跟JWT發(fā)送方相同的密鑰,意味著要做好密鑰的安全傳遞或共享
- 載體驗(yàn)證
iss(Issuser):如果簽發(fā)的時(shí)候這個(gè)claim的值是“a.com”味滞,驗(yàn)證的時(shí)候如果這個(gè)claim的值不是“a.com”就屬于驗(yàn)證失敗
sub(Subject):如果簽發(fā)的時(shí)候這個(gè)claim的值是“l(fā)iuyunzhuge”樱蛤,驗(yàn)證的時(shí)候如果這個(gè)claim的值不是“l(fā)iuyunzhuge”就屬于驗(yàn)證失敗
aud(Audience):如果簽發(fā)的時(shí)候這個(gè)claim的值是“['b.com','c.com']”,驗(yàn)證的時(shí)候這個(gè)claim的值至少要包含b.com剑鞍,c.com的其中一個(gè)才能驗(yàn)證通過(guò)
exp(Expiration time):如果驗(yàn)證的時(shí)候超過(guò)了這個(gè)claim指定的時(shí)間昨凡,就屬于驗(yàn)證失敗蚁署;nbf(Not Before):如果驗(yàn)證的時(shí)候小于這個(gè)claim指定的時(shí)間便脊,就屬于驗(yàn)證失敗
iat(Issued at):它可以用來(lái)做一些maxAge之類(lèi)的驗(yàn)證,假如驗(yàn)證時(shí)間與這個(gè)claim指定的時(shí)間相差的時(shí)間大于通過(guò)maxAge指定的一個(gè)值光戈,就屬于驗(yàn)證失敗
jti(JWT ID):如果簽發(fā)的時(shí)候這個(gè)claim的值是“1”哪痰,驗(yàn)證的時(shí)候如果這個(gè)claim的值不是“1”就屬于驗(yàn)證失敗
注意:在驗(yàn)證一個(gè)JWT的時(shí)候,簽名認(rèn)證是每個(gè)實(shí)現(xiàn)庫(kù)都會(huì)自動(dòng)做的久妆,但是payload的認(rèn)證是由使用者來(lái)決定的晌杰。因?yàn)镴WT里面可能不會(huì)包含任何一個(gè)標(biāo)準(zhǔn)的claim,所以它不會(huì)自動(dòng)去驗(yàn)證這些claim筷弦。
以登錄認(rèn)證來(lái)說(shuō)乎莉,在簽發(fā)JWT的時(shí)候,完全可以只用sub跟exp兩個(gè)claim奸笤,用sub存儲(chǔ)用戶(hù)的id惋啃,用exp存儲(chǔ)它本次登錄之后的過(guò)期時(shí)間,然后在驗(yàn)證的時(shí)候僅驗(yàn)證exp這個(gè)claim监右,以實(shí)現(xiàn)會(huì)話(huà)的有效期管理边灭。
三、實(shí)戰(zhàn)DEMO
- 驗(yàn)證流程
1.用戶(hù)攜帶用戶(hù)名和密碼請(qǐng)求訪(fǎng)問(wèn)
2.服務(wù)器校驗(yàn)用戶(hù)憑據(jù)
3.應(yīng)用提供一個(gè)token給客戶(hù)端
4.客戶(hù)端存儲(chǔ)token健盒,并且在隨后的每一次請(qǐng)求中都帶著它
5.服務(wù)器校驗(yàn)token并返回?cái)?shù)據(jù)
- 注意
每一次請(qǐng)求都需要token
Token應(yīng)該放在請(qǐng)求header中
我們還需要將服務(wù)器設(shè)置為接受來(lái)自所有域的請(qǐng)求绒瘦,用Access-Control-Allow-Origin: *
1、引入相關(guān)pom
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
2扣癣、編寫(xiě)JWT生成解析工具類(lèi)
public class JWTUtils {
public static String createJWT(String id,String subject,long ttlMillis,SecretKey key){
//獲取簽名算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject)
.setIssuer("user")
.setIssuedAt(now)
.signWith(key,signatureAlgorithm);
if(ttlMillis >= 0){
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
//設(shè)置過(guò)期時(shí)間
builder.setExpiration(expDate);
}
return builder.compact();
}
public static Claims parseJWT(String jwt,SecretKey keySpec){
return Jwts.parser()
.setSigningKey(keySpec)
.parseClaimsJws(jwt)
.getBody();
}
public static ResultCode validateJWT(String jwt){
ResultCode checkResult = new ResultCode();
Claims claims = null;
try {
claims = parseJWT(jwt);
checkResult.setSuccess(true);
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) {
checkResult.setErrCode("解析失敗");
checkResult.setSuccess(false);
}
return checkResult;
}
public static void main(String[] args) {
//生成密匙
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jwt = JWTUtils.createJWT("1","test",10000,key);
System.out.println(jwt);
System.out.println(JWTUtils.parseJWT(jwt,key));
}
}
3惰帽、控制器層代碼
- 登陸邏輯
@RequestMapping("/login")
public String login(用戶(hù)信息){
//在這里進(jìn)行用戶(hù)身份校驗(yàn)
......
......
//校驗(yàn)身份成功生成token返回
String jwt = JWTUtils.createJWT(用戶(hù)信息);
return jwt;
}
- 所有需要用戶(hù)身份驗(yàn)證的接口邏輯
//這里只大致寫(xiě)一些,具體驗(yàn)證失敗原因以及返回格式問(wèn)題可以根據(jù)自身細(xì)調(diào)
public String toIndex(HttpServletRequest request){
//獲取token
String authorization = request.getHeader("authorization");
if(authorization == null){
//返回登陸
return "需要登陸";
}else{
//進(jìn)行校驗(yàn)
try{
Claims claims = JWTUtils.parseJWT(authorization);
return "驗(yàn)證成gong";
}catch (Exception e){
e.printStackTrace();
return "驗(yàn)證失敗";
}
}
}
3父虑、客戶(hù)端代碼
- 輸入用戶(hù)信息该酗,申請(qǐng)登陸
$.ajax({
url:'http://localhost:8080/demo/login',
type:'GET',
data:userLoginData,
success:function(res) {
localStorage.setItem("token",res)
},
error:function (res) {
alert(res)
}
})
- 當(dāng)請(qǐng)求需要身份驗(yàn)證接口時(shí)
var token = localStorage.getItem("token");
$.ajax({
url:'http://localhost:8080/demo/toIndex',
type:'GET',
header:{
'Authorization':token
},
beforeSend : function(request) {
request.setRequestHeader("Authorization",token );
},
success:function(res) {
},
})
四、參考連接
https://github.com/jwtk/jjwt#install-jdk-maven
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
http://www.reibang.com/p/f25d62305c70
https://www.cnblogs.com/cjsblog/p/9277677.html
http://www.reibang.com/p/fe67b4bb6f2c