JSON Web Token(缩写 JWT) 目前最流行、最常见的跨域认证解决方案,前端后端都需要会使用的东西
JSON Web Token(縮寫(xiě) JWT)是目前最流行,也是最常見(jiàn)的跨域認(rèn)證解決方案。無(wú)論是咱們后端小伙伴,還是前端小伙伴對(duì)都是需要了解。
本文介紹它的原理、使用場(chǎng)景、用法。
關(guān)于封面:這個(gè)冬天你過(guò)得開(kāi)心嗎
一、跨域認(rèn)證的問(wèn)題
1.1、常見(jiàn)的前后端認(rèn)證方式
- Session-Cookie
- Token 驗(yàn)證(包括JWT,SSO)
- OAuth2.0(開(kāi)放授權(quán))
1.2、Session-Cookie實(shí)現(xià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ù),由此得知用戶的身份。
這種模式在單機(jī)時(shí)不存在什么問(wèn)題,但是一旦服務(wù)器變?yōu)榧耗J綍r(shí),或者是跨域的服務(wù)器時(shí),這個(gè)時(shí)候Session就必須實(shí)現(xiàn)數(shù)據(jù)共享。
這個(gè)時(shí)候就要考慮每臺(tái)服務(wù)器如何實(shí)現(xiàn)對(duì) Session 的數(shù)據(jù)共享呢??
二、什么是 JWT ?
根據(jù)官網(wǎng)介紹:
JSON Web Token (JWT) 是一個(gè)開(kāi)放標(biāo)準(zhǔn),它定義了一種緊湊且自包含的方式,用于在各方之間作為 JSON 對(duì)象安全地傳輸信息。該信息可以被驗(yàn)證和信任,因?yàn)樗墙?jīng)過(guò)數(shù)字簽名的。JWT 可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公鑰/私鑰對(duì)進(jìn)行簽名。
簡(jiǎn)單來(lái)理解就是 JWT 就是一個(gè)JSON對(duì)象經(jīng)過(guò)加密和簽名的,可以在網(wǎng)絡(luò)中安全的傳輸信息,并且可以被驗(yàn)證和信任。
2.1、什么時(shí)候應(yīng)該使用 JWT ?
我目前用的最多的地方就是在授權(quán)方面,這也是 JWT 最常見(jiàn)的場(chǎng)景,其次還可以用來(lái)交換信息。
授權(quán)例子:
用戶登錄后,服務(wù)器端返回一個(gè)JWT,用戶保存在本地,之后的每次請(qǐng)求都將包含JWT,服務(wù)器驗(yàn)證用戶攜帶的JWT,來(lái)判斷是否允許訪問(wèn)服務(wù)和資源。
另外,單點(diǎn)登錄(SSO) 也是當(dāng)今廣泛使用JWT的一項(xiàng)功能,就是在A網(wǎng)站登錄后,在B網(wǎng)站也能夠?qū)崿F(xiàn)自動(dòng)登錄,而不需要重復(fù)登錄,如你在淘寶登錄了,在身份沒(méi)有過(guò)期前,你去看天貓網(wǎng)站,也會(huì)發(fā)現(xiàn)你已經(jīng)登錄了。
簡(jiǎn)而言之:用戶只需要登錄一次就可以訪問(wèn)所有相互信任的應(yīng)用系統(tǒng)。并且能夠輕松跨不同域使用,服務(wù)器也不需要存儲(chǔ)session相關(guān)信息,減輕了負(fù)擔(dān)。
2.2、JWT 原理
其實(shí) JWT 的原理就是,服務(wù)器認(rèn)證以后,將一個(gè) JSON 對(duì)象加密成一個(gè)緊湊的字符串(Token),發(fā)回給用戶,就像下面這樣。
// JSON 對(duì)象 {"姓名": "王五","角色": "管理員","到期時(shí)間": "2021年9月21日0點(diǎn)0分" } //加密后 eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VybmFtZSIsIm5iZiI6MTYzMjI3NzU1NCwiaXNzIjoiY3J1c2giLCJleHAiOjE2MzIyNzc2NTQsImRlbW8iOiLlj6_lrZjlgqjkv6Hmga8iLCJpYXQiOjE2MzIyNzc1NTQsImRlbW8yIjoi5Y-v5a2Y5YKo5L-h5oGvMiJ9.OuqG5Ha_Ofmh5R9Et1vqLYSAlIO85oW9D9Jq9cKKYODO643ZLiDTyQs8dl3PLsZ-_5t0xv6kfKhCzCkCYznBNA在認(rèn)證之后,用戶和服務(wù)器通信時(shí),每次都會(huì)攜帶上這個(gè)Token。服務(wù)器端不再存儲(chǔ)session信息,完全依靠用戶攜帶的Token來(lái)判斷用戶身份。為了安全,服務(wù)器在生成Token的時(shí)候,都會(huì)加上一個(gè)數(shù)字簽名。
這樣做的優(yōu)勢(shì):服務(wù)器不需要再保存 session數(shù)據(jù),減輕了服務(wù)器負(fù)擔(dān),并且基于 JWT 認(rèn)證機(jī)制的應(yīng)用不需要去考慮用戶在哪一臺(tái)服務(wù)器登錄,為應(yīng)用的擴(kuò)展提供了便利。
2.3、JWT 數(shù)據(jù)結(jié)構(gòu)
JSON Web Tokens 由用點(diǎn) ( .)分隔的三個(gè)部分組成,它們是:
- Header(頭部)
- Payload(負(fù)載)
- Signature(簽名)
因此,JWT 通常如下所示。注意:實(shí)際上是未分行的,這里是為了便于展示。
xxxxx.yyyyy.zzzzz 如: eyJhbGciOiJIUzUxMiJ9. eyJzdWIiOiJ1c2VybmFtZSIsIm5iZiI6MTYzMjI3NzU1NCwiaXNzIjoiY3J1c2giLCJleHAiOjE2MzIyNzc2NTQsImRlbW8iOiLlj6_lrZjlgqjkv6Hmga8iLCJpYXQiOjE2MzIyNzc1NTQsImRlbW8yIjoi5Y-v5a2Y5YKo5L-h5oGvMiJ9. OuqG5Ha_Ofmh5R9Et1vqLYSAlIO85oW9D9Jq9cKKYODO643ZLiDTyQs8dl3PLsZ-_5t0xv6kfKhCzCkCYznBNA2.3.1、Header (標(biāo)題)
jwt的頭部承載兩部分信息:
- 聲明類(lèi)型,這里是jwt
- 聲明加密的算法 通常直接使用 HMAC SHA256
Header 部分是一個(gè) JSON 對(duì)象,描述 JWT 的元數(shù)據(jù),通常是下面的樣子。
{ "alg": "HS256", "typ": "JWT" }上面代碼中,alg屬性表示簽名的算法(algorithm),默認(rèn)是 HMAC SHA256(寫(xiě)成 HS256);typ屬性表示這個(gè)令牌(token)的類(lèi)型(type),JWT 令牌統(tǒng)一寫(xiě)為JWT。
2.3.2、Payload(有效載荷)
Payload 部分也是一個(gè) JSON 對(duì)象,用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù)。JWT 規(guī)定了7個(gè)官方字段,供選用。
- iss (issuer):簽發(fā)人
- exp (expiration time):過(guò)期時(shí)間
- sub (subject):主題 jwt所面向的用戶
- aud (audience):受眾 接收jwt的一方
- nbf (Not Before):生效時(shí)間
- iat (Issued At):簽發(fā)時(shí)間
- jti (JWT ID):編號(hào),jwt的唯一身份標(biāo)識(shí),主要用來(lái)作為一次性token,從而回避重放攻擊。
除了官方字段,你還可以在這個(gè)部分定義私有字段,下面就是一個(gè)例子。
{"sub": "1234567890","name": "John Doe","admin": true }注意,JWT 默認(rèn)是不加密的,任何人都可以讀到,所以不要把秘密信息放在這個(gè)部分。
2.3.3、Signature(簽名)
Signature 部分是對(duì)前兩部分的簽名,防止數(shù)據(jù)篡改。
首先,需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認(rèn)是 HMAC SHA256),按照下面的公式產(chǎn)生簽名。
HMACSHA256 (base64UrlEncode(header) + "." +base64UrlEncode(payload),secret )算出簽名以后,把 Header、Payload、Signature 三個(gè)部分拼成一個(gè)字符串,每個(gè)部分之間用"點(diǎn)"(.)分隔,就可以返回給用戶。
注意:簽名用于驗(yàn)證消息在此過(guò)程中沒(méi)有更改,并且在使用私鑰簽名的令牌的情況下,它還可以驗(yàn)證 JWT 的發(fā)送者是它所說(shuō)的人。secret是保存在服務(wù)器端的,jwt的簽發(fā)生成也是在服務(wù)器端的,secret就是用來(lái)進(jìn)行jwt的簽發(fā)和jwt的驗(yàn)證的關(guān)鍵,所以,它就是我們服務(wù)端的私鑰,在任何場(chǎng)景都不應(yīng)該泄露出去。一旦客戶端得知這個(gè)secret, 那就意味著客戶端是可以自我簽發(fā)jwt了,那么安全將不復(fù)存在。
2.3.4、 Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個(gè)算法跟 Base64 算法基本類(lèi)似,但有一些小的不同。
JWT 作為一個(gè)令牌(token),有些場(chǎng)合可能會(huì) 放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個(gè)字符+、/和=,在 URL 里面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 算法。
2.4、JWT工具類(lèi)
相關(guān)依賴:
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version> </dependency>如果是Jdk11使用的話,可能會(huì)報(bào)這樣的一個(gè)錯(cuò)誤:
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverterat io.jsonwebtoken.impl.Base64Codec.decode(Base64Codec.java:26)at io.jsonwebtoken.impl.DefaultJwtBuilder.signWith(DefaultJwtBuilder.java:99)at com.crush.jwt.utils.JwtUtils.createJwt(JwtUtils.java:47)at com.crush.jwt.utils.JwtUtils.main(JwtUtils.java:127) Caused by: java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverterat java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)... 4 more好像是因?yàn)镴dk11中沒(méi)有這個(gè)類(lèi)了,得加上下面這樣的一個(gè)依賴:
<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.0</version> </dependency>工具類(lèi)
import io.jsonwebtoken.*;import java.util.Date; import java.util.HashMap;/*** @Author: crush* @Date: 2021-09-21 22:18* version 1.0*/ public class JwtUtils {/*** 服務(wù)器端密鑰*/private static final String SECRET = "jwtsecretdemo";/*** 頒發(fā)者*/private static final String ISS = "crush";/*** 這里創(chuàng)建用到的時(shí)間、用戶名、應(yīng)該是傳入進(jìn)來(lái)的,* 登錄時(shí)選擇是否記住我,過(guò)期時(shí)間應(yīng)當(dāng)是不一致的。* @return*/public static String createJwt() {HashMap<String, Object> map = new HashMap<>();map.put("demo", "可存儲(chǔ)信息");map.put("demo2","可存儲(chǔ)信息2");String jwt = Jwts.builder().setClaims(map)// jwt所面向的用戶.setSubject("username")//設(shè)置頒發(fā)者.setIssuer(ISS)// 定義在什么時(shí)間之前,該jwt都是不可用的..setNotBefore(new Date())//簽發(fā)時(shí)間.setIssuedAt(new Date())//設(shè)置 JWT 聲明exp (到期)值.setExpiration(new Date(System.currentTimeMillis() + 100000)).signWith(SignatureAlgorithm.HS512, SECRET)//實(shí)際構(gòu)建 JWT 并根據(jù)JWT 緊湊序列化 規(guī)則將其序列化為緊湊的、URL 安全的字符串。.compact();return jwt;}/*** 獲取 Claims 實(shí)例* Claims :一個(gè) JWT聲明集 。* 這最終是一個(gè) JSON 映射,可以向其中添加任何值,但為了方便起見(jiàn),JWT 標(biāo)準(zhǔn)名稱(chēng)作為類(lèi)型安全的 getter 和 setter 提供。* 因?yàn)檫@個(gè)接口擴(kuò)展了Map<String, Object> , 如果您想添加自己的屬性,只需使用 map 方法,* 例如:* claims.put("someKey", "someValue");** @param jwt* @return*/public static Claims getBody(String jwt) {return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(jwt).getBody();}/*** 判斷 JWT 是否已過(guò)期** @param jwt* @return*/public static boolean isExpiration(String jwt) {return getBody(jwt)//返回 JWT exp (到期)時(shí)間戳,如果不存在則返回null 。.getExpiration()//測(cè)試此日期是否在指定日期之前。.before(new Date());}/*** Subject:獲取 jwt 所面向的用戶** @param jwt* @return*/public static String getSubject(String jwt) {return getBody(jwt).getSubject();}/*** Issuer:獲取頒發(fā)者** @param jwt* @return*/public static String getIssuer(String jwt) {return getBody(jwt).getIssuer();}/*** getClaimsValue** @param jwt* @return*/public static String getClaimsValue(String jwt) {return (String) getBody(jwt).get("demo");}/*** getClaimsValue** @param jwt* @return*/public static String getClaimsValue2(String jwt) {return (String) getBody(jwt).get("demo2");}public static void main(String[] args) {String jwt = createJwt();System.out.println(jwt);System.out.println("jwt 是否已經(jīng)過(guò)期:"+isExpiration(jwt));System.out.println("Claims 中所存儲(chǔ)信息:"+getBody(jwt).toString());System.out.println("jwt 所面向的用戶:"+getSubject(jwt));System.out.println("jwt 頒發(fā)者:"+getIssuer(jwt));System.out.println("通過(guò)鍵值,取出我們自己放進(jìn) Jwt 中的信息:"+getClaimsValue(jwt));System.out.println("通過(guò)鍵值,取出我們自己放進(jìn) Jwt 中的信息2:"+getClaimsValue2(jwt));} }三、如何應(yīng)用
此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè) JWT。你可以把它放在 Cookie 里面自動(dòng)發(fā)送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請(qǐng)求的頭信息Authorization字段里面。
Authorization: Bearer <token>一般是在請(qǐng)求頭里加入Authorization,并加上Bearer標(biāo)注:
fetch('api/user/1', {headers: {'Authorization': 'Bearer ' + token} })服務(wù)端會(huì)驗(yàn)證token,如果驗(yàn)證通過(guò)就會(huì)返回相應(yīng)的資源。整個(gè)流程就是這樣的:
實(shí)際使用過(guò)程中,我們通常是結(jié)合著Security安全框架一起使用的,大家感興趣的話,可以來(lái)一起看看我寫(xiě)的這篇文章。
SpringBoot整合Security安全框架、控制權(quán)限
也可以直接看源碼:Security-Gitee
四、總結(jié)
4.1、優(yōu)點(diǎn):
- 因?yàn)閖son的通用性,JWT支持多語(yǔ)言,像JAVA,JavaScript,NodeJS,PHP等很多語(yǔ)言都可以使用。
- 因?yàn)橛辛藀ayload部分,所以JWT可以在自身存儲(chǔ)一些其他業(yè)務(wù)邏輯所必要的非敏感信息。
- 可以用于交換信息。有效使用 JWT,可以降低服務(wù)器查詢數(shù)據(jù)庫(kù)的次數(shù)。
- 便于傳輸,jwt的構(gòu)成非常簡(jiǎn)單,字節(jié)占用很小,所以它是非常便于傳輸?shù)摹?/li>
- 它不需要在服務(wù)端保存會(huì)話信息, 所以它易于應(yīng)用的擴(kuò)展
4.2、安全相關(guān):
- 保護(hù)好secret私鑰,該私鑰非常重要。如果密鑰泄露,用戶自己即可頒布JWT令牌,安全將不復(fù)存在。
- 如果條件允許,JWT 不應(yīng)該使用 HTTP 協(xié)議明碼傳輸,而是要使用 HTTPS 協(xié)議傳輸。Https協(xié)議更安全。
- JWT 的有效期應(yīng)該設(shè)置得比較短。對(duì)于一些比較重要的權(quán)限,使用時(shí)應(yīng)該再次對(duì)用戶進(jìn)行認(rèn)證。
4.3、缺點(diǎn):
- JWT 的最大優(yōu)點(diǎn)是不需要在服務(wù)端保存會(huì)話信息,最大的缺點(diǎn)也是如此,由于服務(wù)器不保存 session 狀態(tài),因此無(wú)法在使用過(guò)程中廢止某個(gè) token,或者更改 token 的權(quán)限。也就是說(shuō),一旦 JWT 簽發(fā)了,在到期之前就會(huì)始終有效。
五、自言自語(yǔ)
本文就是簡(jiǎn)單介紹了,具體使用具體情況具體分析啦。
你好,我是博主寧在春:主頁(yè)
希望本篇文章能讓你感到有所收獲!!!
祝 我們:待別日相見(jiàn)時(shí),都已有所成。
參考:
jwt
JSON Web Token 入門(mén)教程
總結(jié)
以上是生活随笔為你收集整理的JSON Web Token(缩写 JWT) 目前最流行、最常见的跨域认证解决方案,前端后端都需要会使用的东西的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 我们:待别日相见时,我们都已有所成。挥手
- 下一篇: Java设计模式-观察者模式(订阅发布模