前言
JSON Web Token(JWT)是目前最流行的跨域身份驗(yàn)證解決方案夫植。微服務(wù)常見(jiàn)的認(rèn)證方案
一泊业、跨域認(rèn)證的問(wèn)題
互聯(lián)網(wǎng)服務(wù)離不開(kāi)用戶認(rèn)證。一般流程是下面這樣都哭。
1秩伞、用戶向服務(wù)器發(fā)送用戶名和密碼逞带。
2、服務(wù)器驗(yàn)證通過(guò)后纱新,在當(dāng)前對(duì)話(session)里面保存相關(guān)數(shù)據(jù)展氓,比如用戶角色、登錄時(shí)間等等脸爱。
3遇汞、服務(wù)器向用戶返回一個(gè) session_id,寫(xiě)入用戶的 Cookie簿废。
4空入、用戶隨后的每一次請(qǐng)求,都會(huì)通過(guò) Cookie族檬,將 session_id 傳回服務(wù)器歪赢。
5、服務(wù)器收到 session_id单料,找到前期保存的數(shù)據(jù)轨淌,由此得知用戶的身份。
這種模式的問(wèn)題在于看尼,擴(kuò)展性(scaling)不好递鹉。單機(jī)當(dāng)然沒(méi)有問(wèn)題,如果是服務(wù)器集群藏斩,或者是跨域的服務(wù)導(dǎo)向架構(gòu)躏结,就要求 session 數(shù)據(jù)共享,每臺(tái)服務(wù)器都能夠讀取 session狰域。
一種解決方案是 session 數(shù)據(jù)持久化媳拴,寫(xiě)入數(shù)據(jù)庫(kù)或別的持久層。各種服務(wù)收到請(qǐng)求后兆览,都向持久層請(qǐng)求數(shù)據(jù)屈溉。這種方案的優(yōu)點(diǎn)是架構(gòu)清晰,缺點(diǎn)是工程量比較大抬探。另外子巾,持久層萬(wàn)一掛了,就會(huì)單點(diǎn)失敗小压。
另一種方案是服務(wù)器索性不保存 session 數(shù)據(jù)了线梗,所有數(shù)據(jù)都保存在客戶端,每次請(qǐng)求都發(fā)回服務(wù)器怠益。JWT 就是這種方案的一個(gè)代表仪搔。
什么是JWT:一句話概括就是(通過(guò)客戶端保存數(shù)據(jù),而服務(wù)器根本不保存會(huì)話數(shù)據(jù)蜻牢,每個(gè)請(qǐng)求都被發(fā)送回服務(wù)器烤咧。)
二偏陪、JWT
JSON Web Token(JWT)是一個(gè)非常輕巧的規(guī)范。這個(gè)規(guī)范允許我們使用JWT在用戶和服務(wù)器之間傳遞安全可靠的信息煮嫌。
一個(gè)JWT實(shí)際上就是一個(gè)字符串竹挡,它由三部分組成,頭部立膛、載荷與簽名揪罕。
1、JWT的原則
JWT的原則是在服務(wù)器身份驗(yàn)證之后宝泵,將生成一個(gè)JSON對(duì)象并將其發(fā)送回用戶好啰,如下所示。
{
"UserName": "少年閏土",
"Role": "Admin",
"Expire": "2019-12-21 09:15:56"
}
以后儿奶,用戶與服務(wù)端通信的時(shí)候框往,都要發(fā)回這個(gè) JSON 對(duì)象。服務(wù)器完全只靠這個(gè)對(duì)象認(rèn)定用戶身份闯捎。為了防止用戶篡改數(shù)據(jù)椰弊,服務(wù)器在生成這個(gè)對(duì)象的時(shí)候,會(huì)加上簽名瓤鼻。
2秉版、JWT的數(shù)據(jù)結(jié)構(gòu)
樣例:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NTc5MDU4MDIsImV4cCI6MTU1NzkwNjgwMiwicm9sZXMiOiJhZG1pbiJ9.AS5Y2fNCwUzQQxXh_QQWMpaB75YqfuK-2P7VZiCXEJI
他是一個(gè)長(zhǎng)字符串,中間用.
進(jìn)行分割茬祷,代表JWT的三個(gè)組成部分清焕,如下:
Header(頭部)
Payload(負(fù)載)
-
Signature(簽名)
2.1、頭部(Header)
頭部用于描述關(guān)于該JWT的最基本的信息祭犯,例如其類型以及簽名所用的算法等秸妥。這也可以被表示成一個(gè)JSON對(duì)象。
{"typ":"JWT","alg":"HS256"}
這個(gè)json中的typ屬性沃粗,用來(lái)標(biāo)識(shí)整個(gè)token字符串是一個(gè)JWT字符串粥惧;它的alg屬性,用來(lái)說(shuō)明這個(gè)JWT簽發(fā)的時(shí)候所使用的簽名和摘要算法最盅。typ跟alg屬性的全稱其實(shí)是type跟algorithm突雪,分別是類型跟算法的意思。之所以都用三個(gè)字母來(lái)表示檩禾,也是基于JWT最終字串大小的考慮挂签,同時(shí)也是跟JWT這個(gè)名稱保持一致,這樣就都是三個(gè)字符了…typ跟alg是JWT中標(biāo)準(zhǔn)中規(guī)定的屬性名稱
在頭部指明了簽名算法是HS256算法盼产。 我們進(jìn)行BASE64編碼http://base64.xpcha.com/,編碼后的字符串如下:
eyJhbGciOiJIUzI1NiJ9
2.2勺馆、載荷(Playload)
Payload 部分也是一個(gè) JSON 對(duì)象戏售,用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù)侨核。JWT 規(guī)定了7個(gè)官方字段,供選用灌灾。
iss: jwt簽發(fā)者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過(guò)期時(shí)間搓译,這個(gè)過(guò)期時(shí)間必須要大于簽發(fā)時(shí)間
nbf: 定義在什么時(shí)間之前,該jwt都是不可用的.
iat: jwt的簽發(fā)時(shí)間
jti: jwt的唯一身份標(biāo)識(shí)锋喜,主要用來(lái)作為一次性token些己。
除了官方字段,你還可以在這個(gè)部分定義私有字段
樣例:
{"sub":"1234567890","name":"John Doe","admin":true}
然后將其進(jìn)行base64加密嘿般,得到Jwt的第二部分段标。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
2.3、簽名(Signature)
Signature 部分是對(duì)前兩部分的簽名炉奴,防止數(shù)據(jù)篡改逼庞。這個(gè)簽證信息由三部分組成:
header (base64后的)
payload (base64后的)
secret
首先,需要指定一個(gè)密鑰(secret)瞻赶。這個(gè)密鑰只有服務(wù)器才知道赛糟,不能泄露給用戶。這個(gè)部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串砸逊,然后通過(guò)header中聲明的加密方式進(jìn)行加鹽secret組合加密璧南,然后就構(gòu)成了jwt的第三部分。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
3师逸、Base64URL
前面提到穆咐,Header 和 Payload 串型化的算法是 Base64URL。這個(gè)算法跟 Base64 算法基本類似字旭,但有一些小的不同对湃。
JWT 作為一個(gè)令牌(token),有些場(chǎng)合可能會(huì)放到 URL(比如 api.example.com/?token=xxx)遗淳。Base64 有三個(gè)字符+拍柒、/和=,在 URL 里面有特殊含義屈暗,所以要被替換掉:=被省略拆讯、+替換成-,/替換成_ 养叛。這就是 Base64URL 算法种呐。
4、JWT 的使用方式
客戶端收到服務(wù)器返回的 JWT弃甥,可以儲(chǔ)存在 Cookie 里面爽室,也可以儲(chǔ)存在 localStorage。
此后淆攻,客戶端每次與服務(wù)器通信阔墩,都要帶上這個(gè) JWT嘿架。你可以把它放在 Cookie 里面自動(dòng)發(fā)送,但是這樣不能跨域啸箫,所以更好的做法是放在 HTTP 請(qǐng)求的頭信息Authorization字段里面耸彪。
Authorization: Bearer <token>
下圖顯示了如何獲取JWT并將其用于訪問(wèn)API或資源:
三、JWT使用
1忘苛、添加依賴
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
2蝉娜、工具類
package com.example.demo.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: 少年閏土
* @Date: 2019/12/11
* @Time: 下午 4:12
* @Version: v1.0
* jwt工具類
*/
@Component
public class JwtUtils {
/**
* 解析token
*
* @param token token
* @return 用戶名
*/
public static String getUserName(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("userName").asString();
} catch (JWTDecodeException e) {
e.printStackTrace();
return null;
}
}
/**
* 簽發(fā)token
*
* @param userName 用戶名
* @return token
*/
public static String sign(String userName,String secret) {
try {
//token過(guò)期時(shí)間
Date date = new Date(System.currentTimeMillis() + (60 * 60 * 1000));
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附帶username信息
return JWT.create()
.withClaim("userName", userName)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 檢驗(yàn)token是否過(guò)期
*
* @param token
* @return
*/
public static Map verify(String token,String userName, String secret) {
Map result = new HashMap<String, Object>(2);
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("userName", userName)
.build();
DecodedJWT jwt = verifier.verify(token);
result.put("isSuccess", true);
result.put("exception", null);
} catch (Exception exception) {
result.put("isSuccess", false);
result.put("exception", exception);
}
return result;
}
}
3、使用
@ApiOperation(value = "瀏覽器點(diǎn)擊登錄")
@ApiImplicitParam(name = "user", value = "用戶實(shí)體", required = true, paramType = "User")
@PostMapping("/login")
public R login(@RequestBody User user) {
log.debug("------瀏覽器點(diǎn)擊登錄------");
String userName = user.getUsername();
String passWord = user.getPassword();
User u = this.userService.getUser(userName);
String passWordSalt = MD5.md5Salt(passWord, userName);
if (u != null && u.getPassword().equals(passWordSalt)) {
String token = JwtUtils.sign(userName, passWordSalt);
return R.ok(R.SUCCESS, R.MSG_SUCCESS, token);
} else {
return R.error(R.MSG_LOGIN_ERROR);
}
}
個(gè)人博客
騰訊云社區(qū)
掘金
CSDN
OSCHINA
公眾號(hào):