SpringBoot JWT令牌的使用
介紹
JWT全稱:JSON Web Token (官網(wǎng):https://jwt.io/)
定義了一種簡潔的、自包含的格式,用于在通信雙方以json數(shù)據(jù)格式安全的傳輸信息。由于數(shù)字簽名的存在,這些信息是可靠的。
- 簡潔:是指jwt就是一個(gè)簡單的字符串??梢栽谡埱髤?shù)或者是請求頭當(dāng)中直接傳遞。
- 自包含:指的是jwt令牌,看似是一個(gè)隨機(jī)的字符串,但是我們是可以根據(jù)自身的需求在jwt令牌中存儲自定義的數(shù)據(jù)內(nèi)容。如:可以直接在jwt令牌中存儲用戶的相關(guān)信息。
- 簡單來講,jwt就是將原始的json數(shù)據(jù)格式進(jìn)行了安全的封裝,這樣就可以直接基于jwt在通信雙方安全的進(jìn)行信息傳輸了。
JWT的組成: (JWT令牌由三個(gè)部分組成,三個(gè)部分之間使用英文的點(diǎn)來分割)
- 第一部分:Header(頭), 記錄令牌類型、簽名算法等。 例如:{"alg":"HS256","type":"JWT"}
- 第二部分:Payload(有效載荷),攜帶一些自定義信息、默認(rèn)信息等。 例如:{"id":"1","username":"Tom"}
- 第三部分:Signature(簽名),防止Token被篡改、確保安全性。將header、payload,并加入指定秘鑰,通過指定簽名算法計(jì)算而來。
簽名的目的就是為了防jwt令牌被篡改,而正是因?yàn)閖wt令牌最后一個(gè)部分?jǐn)?shù)字簽名的存在,所以整個(gè)jwt 令牌是非常安全可靠的。一旦jwt令牌當(dāng)中任何一個(gè)部分、任何一個(gè)字符被篡改了,整個(gè)令牌在校驗(yàn)的時(shí)候都會失敗,所以它是非常安全可靠的。
JWT是如何將原始的JSON格式數(shù)據(jù),轉(zhuǎn)變?yōu)樽址哪兀?/p>
其實(shí)在生成JWT令牌時(shí),會對JSON格式的數(shù)據(jù)進(jìn)行一次編碼:進(jìn)行base64編碼
Base64:是一種基于64個(gè)可打印的字符來表示二進(jìn)制數(shù)據(jù)的編碼方式。既然能編碼,那也就意味著也能解碼。所使用的64個(gè)字符分別是A到Z、a到z、 0- 9,一個(gè)加號,一個(gè)斜杠,加起來就是64個(gè)字符。任何數(shù)據(jù)經(jīng)過base64編碼之后,最終就會通過這64個(gè)字符來表示。當(dāng)然還有一個(gè)符號,那就是等號。等號它是一個(gè)補(bǔ)位的符號
需要注意的是Base64是編碼方式,而不是加密方式。
JWT令牌最典型的應(yīng)用場景就是登錄認(rèn)證:
在瀏覽器發(fā)起請求來執(zhí)行登錄操作,此時(shí)會訪問登錄的接口,如果登錄成功之后,我們需要生成一個(gè)jwt令牌,將生成的 jwt令牌返回給前端。
前端拿到j(luò)wt令牌之后,會將jwt令牌存儲起來。在后續(xù)的每一次請求中都會將jwt令牌攜帶到服務(wù)端。
服務(wù)端統(tǒng)一攔截請求之后,先來判斷一下這次請求有沒有把令牌帶過來,如果沒有帶過來,直接拒絕訪問,如果帶過來了,還要校驗(yàn)一下令牌是否是有效。如果有效,就直接放行進(jìn)行請求的處理。
在JWT登錄認(rèn)證的場景中我們發(fā)現(xiàn),整個(gè)流程當(dāng)中涉及到兩步操作:
在登錄成功之后,要生成令牌。
每一次請求當(dāng)中,要接收令牌并對令牌進(jìn)行校驗(yàn)。
生成和校驗(yàn)
首先我們先來實(shí)現(xiàn)JWT令牌的生成。要想使用JWT令牌,需要先引入JWT的依賴:
<!-- JWT依賴--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
在引入完JWT來賴后,就可以調(diào)用工具包中提供的API來完成JWT令牌的生成和校驗(yàn)
工具類:Jwts
生成JWT代碼實(shí)現(xiàn):
@Test public void genJwt(){ Map<String,Object> claims = new HashMap<>(); claims.put("id",1); claims.put("username","Tom"); String jwt = Jwts.builder() .setClaims(claims) //自定義內(nèi)容(載荷) .signWith(SignatureAlgorithm.HS256, "itheima") //簽名算法 .setExpiration(new Date(System.currentTimeMillis() + 24*3600*1000)) //有效期 .compact(); System.out.println(jwt); }
運(yùn)行測試方法:
eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjcyNzI5NzMwfQ.fHi0Ub8npbyt71UqLXDdLyipptLgxBUg_mSuGJtXtBk
輸出的結(jié)果就是生成的JWT令牌,,通過英文的點(diǎn)分割對三個(gè)部分進(jìn)行分割,我們可以將生成的令牌復(fù)制一下,然后打開JWT的官網(wǎng),將生成的令牌直接放在Encoded位置,此時(shí)就會自動的將令牌解析出來。
第一部分解析出來,看到JSON格式的原始數(shù)據(jù),所使用的簽名算法為HS256。
第二個(gè)部分是我們自定義的數(shù)據(jù),之前我們自定義的數(shù)據(jù)就是id,還有一個(gè)exp代表的是我們所設(shè)置的過期時(shí)間。
由于前兩個(gè)部分是base64編碼,所以是可以直接解碼出來。但最后一個(gè)部分并不是base64編碼,是經(jīng)過簽名算法計(jì)算出來的,所以最后一個(gè)部分是不會解析的。
實(shí)現(xiàn)了JWT令牌的生成,下面我們接著使用Java代碼來校驗(yàn)JWT令牌(解析生成的令牌):
@Test public void parseJwt(){ Claims claims = Jwts.parser() .setSigningKey("itheima")//指定簽名密鑰(必須保證和生成令牌時(shí)使用相同的簽名密鑰) .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjcyNzI5NzMwfQ.fHi0Ub8npbyt71UqLXDdLyipptLgxBUg_mSuGJtXtBk") .getBody(); System.out.println(claims); }
運(yùn)行測試方法:
{id=1, exp=1672729730}
令牌解析后,我們可以看到id和過期時(shí)間,如果在解析的過程當(dāng)中沒有報(bào)錯(cuò),就說明解析成功了。
下面我們做一個(gè)測試:把令牌header中的數(shù)字9變?yōu)?,運(yùn)行測試方法后發(fā)現(xiàn)報(bào)錯(cuò):
原h(huán)eader: eyJhbGciOiJIUzI1NiJ9
修改為: eyJhbGciOiJIUzI1NiJ8
結(jié)論:篡改令牌中的任何一個(gè)字符,在對令牌進(jìn)行解析時(shí)都會報(bào)錯(cuò),所以JWT令牌是非常安全可靠的。
我們繼續(xù)測試:修改生成令牌的時(shí)指定的過期時(shí)間,修改為1分鐘
@Test public void genJwt(){ Map<String,Object> claims = new HashMap<>(); claims.put(“id”,1); claims.put(“username”,“Tom”); String jwt = Jwts.builder() .setClaims(claims) //自定義內(nèi)容(載荷) .signWith(SignatureAlgorithm.HS256, “itheima”) //簽名算法 .setExpiration(new Date(System.currentTimeMillis() + 60*1000)) //有效期60秒 .compact(); System.out.println(jwt); //輸出結(jié)果:eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjczMDA5NzU0fQ.RcVIR65AkGiax-ID6FjW60eLFH3tPTKdoK7UtE4A1ro } @Test public void parseJwt(){ Claims claims = Jwts.parser() .setSigningKey("itheima")//指定簽名密鑰 .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjczMDA5NzU0fQ.RcVIR65AkGiax-ID6FjW60eLFH3tPTKdoK7UtE4A1ro") .getBody(); System.out.println(claims); }
等待1分鐘之后運(yùn)行測試方法發(fā)現(xiàn)也報(bào)錯(cuò)了,說明:JWT令牌過期后,令牌就失效了,解析的為非法令牌。
通過以上測試,我們在使用JWT令牌時(shí)需要注意:
JWT校驗(yàn)時(shí)使用的簽名秘鑰,必須和生成JWT令牌時(shí)使用的秘鑰是配套的。
如果JWT令牌解析校驗(yàn)時(shí)報(bào)錯(cuò),則說明 JWT令牌被篡改 或 失效了,令牌非法。
登錄下發(fā)令牌
JWT令牌的生成和校驗(yàn)的基本操作我們已經(jīng)學(xué)習(xí)完了,接下來我們就需要在案例當(dāng)中通過JWT令牌技術(shù)來跟蹤會話。具體的思路我們前面已經(jīng)分析過了,主要就是兩步操作:
生成令牌
在登錄成功之后來生成一個(gè)JWT令牌,并且把這個(gè)令牌直接返回給前端
校驗(yàn)令牌
攔截前端請求,從請求中獲取到令牌,對令牌進(jìn)行解析校驗(yàn)
那我們首先來完成:登錄成功之后生成JWT令牌,并且把令牌返回給前端。
實(shí)現(xiàn)步驟:
引入JWT工具類
在項(xiàng)目工程下創(chuàng)建com.itheima.utils包,并把提供JWT工具類復(fù)制到該包下
登錄完成后,調(diào)用工具類生成JWT令牌并返回
JWT工具類
public class JwtUtils { private static String signKey = "itheima";//簽名密鑰 private static Long expire = 43200000L; //有效時(shí)間 /** * 生成JWT令牌 * @param claims JWT第二部分負(fù)載 payload 中存儲的內(nèi)容 * @return */ public static String generateJwt(Map<String, Object> claims){ String jwt = Jwts.builder() .addClaims(claims)//自定義信息(有效載荷) .signWith(SignatureAlgorithm.HS256, signKey)//簽名算法(頭部) .setExpiration(new Date(System.currentTimeMillis() + expire))//過期時(shí)間 .compact(); return jwt; } /** * 解析JWT令牌 * @param jwt JWT令牌 * @return JWT第二部分負(fù)載 payload 中存儲的內(nèi)容 */ public static Claims parseJWT(String jwt){ Claims claims = Jwts.parser() .setSigningKey(signKey)//指定簽名密鑰 .parseClaimsJws(jwt)//指定令牌Token .getBody(); return claims; } }
登錄成功,生成JWT令牌并返回 :
@RestController @Slf4j public class LoginController { //依賴業(yè)務(wù)層對象 @Autowired private EmpService empService; @PostMapping("/login") public Result login(@RequestBody Emp emp) { //調(diào)用業(yè)務(wù)層:登錄功能 Emp loginEmp = empService.login(emp); //判斷:登錄用戶是否存在 if(loginEmp !=null ){ //自定義信息 Map<String , Object> claims = new HashMap<>(); claims.put("id", loginEmp.getId()); claims.put("username",loginEmp.getUsername()); claims.put("name",loginEmp.getName()); //使用JWT工具類,生成身份令牌 String token = JwtUtils.generateJwt(claims); return Result.success(token); } return Result.error("用戶名或密碼錯(cuò)誤"); } }
重啟服務(wù),打開postman測試登錄接口:
打開瀏覽器完成前后端聯(lián)調(diào)操作:利用開發(fā)者工具,抓取一下網(wǎng)絡(luò)請求
登錄請求完成后,可以看到JWT令牌已經(jīng)響應(yīng)給了前端,此時(shí)前端就會將JWT令牌存儲在瀏覽器本地。
服務(wù)器響應(yīng)的JWT令牌存儲在本地瀏覽器哪里了呢?
在當(dāng)前案例中,JWT令牌存儲在瀏覽器的本地存儲空間local storage中了。 local storage是瀏覽器的本地存儲,在移動端也是支持的。
我們在發(fā)起一個(gè)查詢部門數(shù)據(jù)的請求,此時(shí)我們可以看到在請求頭中包含一個(gè)token(JWT令牌),后續(xù)的每一次請求當(dāng)中,都會將這個(gè)令牌攜帶到服務(wù)端。
到此這篇關(guān)于SpringBoot JWT令牌的使用的文章就介紹到這了,更多相關(guān)SpringBoot JWT令牌內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java利用Map實(shí)現(xiàn)計(jì)算文本中字符個(gè)數(shù)
這篇文章主要為大家詳細(xì)介紹了Java如何利用Map集合實(shí)現(xiàn)計(jì)算文本中字符個(gè)數(shù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-08-08SpringBoot簡單的SpringBoot后端實(shí)例
這篇文章主要介紹了SpringBoot簡單的SpringBoot后端實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03SpringMvc導(dǎo)出Excel實(shí)例代碼
本篇文章主要介紹了SpringMvc導(dǎo)出Excel實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-01-01Java動態(tài)代理實(shí)現(xiàn)方法小結(jié)
這篇文章主要介紹了Java動態(tài)代理實(shí)現(xiàn)方法,結(jié)合實(shí)例形式總結(jié)分析了java基于JDK、CGLIB及CGLIB實(shí)現(xiàn)動態(tài)代理的相關(guān)操作技巧,需要的朋友可以參考下2019-02-02Struts2實(shí)現(xiàn)文件下載功能代碼分享(文件名中文轉(zhuǎn)碼)
這篇文章主要介紹了Struts2實(shí)現(xiàn)文件下載功能代碼分享(文件名中文轉(zhuǎn)碼)的相關(guān)資料,需要的朋友可以參考下2016-06-06