Java中的JWT使用詳解
JWT
JSON Web Token(JSON Web令牌)
是一個(gè)開放標(biāo)準(zhǔn)(rfc7519),它定義了一種緊湊的、自包含的方式,用于在各方之間以JSON對(duì)象安全地傳輸信息。此信息可以驗(yàn)證和信任,因?yàn)樗菙?shù)字簽名的。
jwt可以使用秘密〈使用HNAC算法)或使用RSA或ECDSA的公鑰/私鑰對(duì)進(jìn)行簽名。
通過JSON形式作為Web應(yīng)用中的令牌,用于在各方之間安全地將信息作為JSON對(duì)象傳輸。在數(shù)據(jù)傳輸過程中還可以完成數(shù)據(jù)加密、簽名等相關(guān)處理。
JWT作用:
- 授權(quán):一旦用戶登錄,每個(gè)后續(xù)請(qǐng)求將包括JWT,從而允許用戶訪問該令牌允許的路由,服務(wù)和資源。它的開銷很小并且可以在不同的域中使用。如:?jiǎn)吸c(diǎn)登錄。
- 信息交換:在各方之間安全地傳輸信息。JWT可進(jìn)行簽名(如使用公鑰/私鑰對(duì)),因此可確保發(fā)件人。由于簽名是使用標(biāo)頭和有效負(fù)載計(jì)算的,因此還可驗(yàn)證內(nèi)容是否被篡改。
1.傳統(tǒng)Session
1.1.認(rèn)證方式
http協(xié)議本身是一種無(wú)狀態(tài)的協(xié)議,如果用戶向服務(wù)器提供了用戶名和密碼來(lái)進(jìn)行用戶認(rèn)證,下次請(qǐng)求時(shí),用戶還要再一次進(jìn)行用戶認(rèn)證才行。
因?yàn)楦鶕?jù)http協(xié)議,服務(wù)器并不能知道是哪個(gè)用戶發(fā)出的請(qǐng)求,所以為了讓我們的應(yīng)用能識(shí)別是哪個(gè)用戶發(fā)出的請(qǐng)求,我們只能在服務(wù)器存儲(chǔ)─份用戶登錄的信息,這份登錄信息會(huì)在響應(yīng)時(shí)傳遞給瀏覽器,告訴其保存為cookie,以便下次請(qǐng)求時(shí)發(fā)送給我們的應(yīng)用,這樣應(yīng)用就能識(shí)別請(qǐng)求來(lái)自哪個(gè)用戶。

1.2.暴露的問題
- 用戶經(jīng)過應(yīng)用認(rèn)證后,應(yīng)用都要在服務(wù)端做一次記錄,以方便用戶下次請(qǐng)求的鑒別,通常而言session都是保存在內(nèi)存中,而隨著認(rèn)證用戶的增多,服務(wù)端的開銷會(huì)明顯增大;
- 用戶認(rèn)證后,服務(wù)端做認(rèn)證記錄,如果認(rèn)證的記錄被保存在內(nèi)存中的話,用戶下次請(qǐng)求還必須要請(qǐng)求在這臺(tái)服務(wù)器上,這樣才能拿到授權(quán)的資源。在分布式的應(yīng)用上,限制了負(fù)載均衡器的能力。以此限制了應(yīng)用的擴(kuò)展能力;
- session是基于cookie來(lái)進(jìn)行用戶識(shí)別,cookie如果被截獲,用戶很容易受到CSRF(跨站偽造請(qǐng)求攻擊)攻擊;
- 在前后端分離系統(tǒng)中應(yīng)用解耦后增加了部署的復(fù)雜性。通常用戶一次請(qǐng)求就要轉(zhuǎn)發(fā)多次。如果用session每次攜帶sessionid到服務(wù)器,服務(wù)器還要查詢用戶信息。同時(shí)如果用戶很多。這些信息存儲(chǔ)在服務(wù)器內(nèi)存中,給服務(wù)器增加負(fù)擔(dān)。還有就是sessionid就是一個(gè)特征值,表達(dá)的信息不夠豐富。不容易擴(kuò)展。而且如果你后端應(yīng)用是多節(jié)點(diǎn)部署。那么就需要實(shí)現(xiàn)session共享機(jī)制。不方便集群應(yīng)用。
2.JWT認(rèn)證

2.1.認(rèn)證流程
- 前端通過Web表單將自己的用戶名和密碼發(fā)送到后端的接口。該過程一般是HTTP的POST請(qǐng)求。建議的方式是通過SSL加密的傳輸(https協(xié)議),從而避免敏感信息被嗅探。
- 后端核對(duì)用戶名和密碼成功后,將用戶的id等其他信息作為JWT Payload(負(fù)載),將其與頭部分別進(jìn)行Base64編碼拼接后簽名,形成一個(gè)JWT(Token)。
- 后端將JWT字符串作為登錄成功的返回結(jié)果返回給前端。前端可以將返回的結(jié)果保存在localStorage(瀏覽器本地緩存)或sessionStorage(session緩存)上,退出登錄時(shí)前端刪除保存的JWT即可。
- 前端在每次請(qǐng)求時(shí)將JWT放入HTTP的Header中的Authorization位。(解決XSS和XSRF問題)HEADER
- 后端檢查是否存在,如存在驗(yàn)證JWT的有效性。例如,檢查簽名是否正確﹔檢查Token是否過期;檢查Token的接收方是否是自己(可選)
- 驗(yàn)證通過后后端使用JWT中包含的用戶信息進(jìn)行其他邏輯操作,返回相應(yīng)結(jié)果。
2.2.JWT優(yōu)點(diǎn)
- 簡(jiǎn)潔(Compact):可以通過URL,POST參數(shù)或者在HTTP header發(fā)送,數(shù)據(jù)量小,傳輸速度也很快;
- 自包含(Self-contained):負(fù)載中包含了所有用戶所需要的信息,避免了多次查詢數(shù)據(jù)庫(kù);
- Token是以JSON加密的形式保存在客戶端,所以JWT是跨語(yǔ)言的,原則上任何web形式都支持。
- 不需要在服務(wù)端保存會(huì)話信息,特別適用于分布式微服務(wù)。
3.JWT結(jié)構(gòu)
就是令牌token,是一個(gè)String字符串,由3部分組成,中間用點(diǎn)隔開
令牌組成:
- 標(biāo)頭(Header)
- 有效載荷(Payload)
- 簽名(Signature)
token格式:head.payload.singurater 如:xxxxx.yyyy.zzzz
- Header:有令牌的類型和所使用的簽名算法,如HMAC、SHA256、RSA;使用Base64編碼組成;(Base64是一種編碼,不是一種加密過程,可以被翻譯成原來(lái)的樣子)
{
"alg" : "HS256",
"type" : "JWT"
}- Payload :有效負(fù)載,包含聲明;聲明是有關(guān)實(shí)體(通常是用戶)和其他數(shù)據(jù)的聲明,不放用戶敏感的信息,如密碼。同樣使用Base64編碼
{
"sub" : "123",
"name" : "John Do",
"admin" : true
}- Signature :前面兩部分都使用Base64進(jìn)行編碼,前端可以解開知道里面的信息。Signature需要使用編碼后的header和payload 加上我們提供的一個(gè)密鑰,使用header中指定的簽名算法(HS256)進(jìn)行簽名。簽名的作用是保證JWT沒有被篡改過
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);
**簽名目的:**簽名的過程實(shí)際上是對(duì)頭部以及負(fù)載內(nèi)容進(jìn)行簽名,防止內(nèi)容被竄改。如果有人對(duì)頭部以及負(fù)載的內(nèi)容解碼之后進(jìn)行修改,再進(jìn)行編碼,最后加上之前的簽名組合形成新的JWT的話,那么服務(wù)器端會(huì)判斷出新的頭部和負(fù)載形成的簽名和JWT附帶上的簽名是不一樣的。如果要對(duì)新的頭部和負(fù)載進(jìn)行簽名,在不知道服務(wù)器加密時(shí)用的密鑰的話,得出來(lái)的簽名也是不一樣的。
信息安全問題:Base64是一種編碼,是可逆的,適合傳遞一些非敏感信息;JWT中不應(yīng)該在負(fù)載中加入敏感的數(shù)據(jù)。如傳輸用戶的ID被知道也是安全的,如密碼不能放在JWT中;JWT常用于設(shè)計(jì)用戶認(rèn)證、授權(quán)系統(tǒng)、web的單點(diǎn)登錄。

4.JWT使用
4.1.引入依賴
<!--引入JWT-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.0</version>
</dependency>4.2.生成token
HashMap<String,Object> map = new HashMap<>();
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,20);
String token = JWT.create()
.withHeader(map) //可以不設(shè)定,就是使用默認(rèn)的
.withClaim("userId",20)//payload //自定義用戶名
.withClaim("username","zhangsan")
.withExpiresAt(instance.getTime()) //指定令牌過期時(shí)間
.sign(Algorithm.HMAC256("fdahuifeuw78921"));//簽名4.3.根據(jù)令牌和簽名解析數(shù)據(jù)
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("fdahuif921")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
decodedJWT.getClaim("userId").asInt();//獲取負(fù)載里面對(duì)應(yīng)的內(nèi)容
decodedJWT.getClaim("username").asString();
decodedJWT.getExpiresAt();//獲取過期時(shí)間4.4.常見異常信息
SignatureVerificationException //簽名不一致異常 TokenExpiredException //令牌過期異常 AlgorithmMismatchException //算法不匹配異常 InvalidClaimException //失效的payload異常(傳給客戶端后,token被改動(dòng),驗(yàn)證不一致)
5.封裝工具類
public class JWTUtils {
private static String SIGNATURE = "token!@#$%^7890";
/**
* 生成token
* @param map //傳入payload
* @return 返回token
*/
public static String getToken(Map<String,String> map){
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,7);
builder.withExpiresAt(instance.getTime());
return builder.sign(Algorithm.HMAC256(SIGNATURE)).toString();
}
/**
* 驗(yàn)證token
* @param token
*/
public static void verify(String token){
JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
}
/**
* 獲取token中payload
* @param token
* @return
*/
public static DecodedJWT getToken(String token){
return JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
}
}6.SpringBoot整合JWT
6.1.登錄時(shí)生成token
//controller層接收數(shù)據(jù),生成token,并響應(yīng)
Map<String,Object> map = new HashMap<>();
try{
User userDB = userService.login(user);
Map<String,String> payload = new HashMap<>();
payload.put("id",userDB.getId());
payload.put("name",userDB.getName());
//生成JWT令牌
String token = JWTUtils.getToken(payload);
map.put("state",true);
map.put("msg","認(rèn)證成功");
map.put("token",token);//響應(yīng)token
} catch (Exception e) {
map.put("state","false");
map.put("msg",e.getMessage());
}6.2.聲明一個(gè)token攔截器類
package com.liup.interceptor;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.office.utils.JWTUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* JWT驗(yàn)證攔截器
*/
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String,Object> map = new HashMap<>();
//令牌建議是放在請(qǐng)求頭中,獲取請(qǐng)求頭中令牌
String token = request.getHeader("token");
try{
JWTUtils.verify(token);//驗(yàn)證令牌
return true;//放行請(qǐng)求
} catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("msg","無(wú)效簽名");
} catch (TokenExpiredException e) {
e.printStackTrace();
map.put("msg","token過期");
} catch (AlgorithmMismatchException e) {
e.printStackTrace();
map.put("msg","token算法不一致");
} catch (Exception e) {
e.printStackTrace();
map.put("msg","token失效");
}
map.put("state",false);//設(shè)置狀態(tài)
//將map轉(zhuǎn)化成json,response使用的是Jackson
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print(json);
return false;
}
}6.3.配置攔截器
package com.liup.config;
import com.office.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/**");
}
}到此這篇關(guān)于Java中的JWT使用詳解的文章就介紹到這了,更多相關(guān)Java中的JWT內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot項(xiàng)目引入MCP的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot項(xiàng)目引入MCP的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
使用synchronized關(guān)鍵字實(shí)現(xiàn)信號(hào)量的方法
在Java中,信號(hào)量(Semaphore)是一種常用的同步工具,它可以用來(lái)控制對(duì)共享資源的訪問數(shù)量,下面,我們將使用Synchronized關(guān)鍵字來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的信號(hào)量,我們的目標(biāo)是實(shí)現(xiàn)一個(gè)計(jì)數(shù)信號(hào)量,其中信號(hào)量的計(jì)數(shù)指示可以同時(shí)訪問某一資源的線程數(shù),需要的朋友可以參考下2024-04-04
雙token實(shí)現(xiàn)token超時(shí)策略示例
用于restful的app應(yīng)用無(wú)狀態(tài)無(wú)sesion登錄示例,需要的朋友可以參考下2014-02-02
Java 同步鎖(synchronized)詳解及實(shí)例
這篇文章主要介紹了Java 同步鎖(synchronized)詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-03-03
springboot實(shí)現(xiàn)調(diào)用百度ocr實(shí)現(xiàn)身份識(shí)別+二要素校驗(yàn)功能
本文介紹了如何使用Spring Boot調(diào)用百度OCR服務(wù)進(jìn)行身份識(shí)別,并通過二要素校驗(yàn)確保信息準(zhǔn)確性,感興趣的朋友一起看看吧2025-03-03
詳解poi+springmvc+springjdbc導(dǎo)入導(dǎo)出excel實(shí)例
本篇文章主要介紹了poi+springmvc+springjdbc導(dǎo)入導(dǎo)出excel實(shí)例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-01-01
struts2中simple主題下<s:fieldError>標(biāo)簽?zāi)J(rèn)樣式的移除方法
這篇文章主要給大家介紹了關(guān)于struts2中simple主題下<s:fieldError>標(biāo)簽?zāi)J(rèn)樣式的移除方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10
Java實(shí)現(xiàn)控制小數(shù)精度的方法
這篇文章主要介紹了Java實(shí)現(xiàn)控制小數(shù)精度的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
JAVA 多線程之信號(hào)量(Semaphore)實(shí)例詳解
這篇文章主要介紹了JAVA 多線程之信號(hào)量(Semaphore)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-01-01

