Java中的JWT使用詳解
JWT
JSON Web Token(JSON Web令牌)
是一個開放標(biāo)準(zhǔn)(rfc7519),它定義了一種緊湊的、自包含的方式,用于在各方之間以JSON對象安全地傳輸信息。此信息可以驗證和信任,因為它是數(shù)字簽名的。
jwt可以使用秘密〈使用HNAC算法)或使用RSA或ECDSA的公鑰/私鑰對進行簽名。
通過JSON形式作為Web應(yīng)用中的令牌,用于在各方之間安全地將信息作為JSON對象傳輸。在數(shù)據(jù)傳輸過程中還可以完成數(shù)據(jù)加密、簽名等相關(guān)處理。
JWT作用:
- 授權(quán):一旦用戶登錄,每個后續(xù)請求將包括JWT,從而允許用戶訪問該令牌允許的路由,服務(wù)和資源。它的開銷很小并且可以在不同的域中使用。如:單點登錄。
- 信息交換:在各方之間安全地傳輸信息。JWT可進行簽名(如使用公鑰/私鑰對),因此可確保發(fā)件人。由于簽名是使用標(biāo)頭和有效負載計算的,因此還可驗證內(nèi)容是否被篡改。
1.傳統(tǒng)Session
1.1.認證方式
http協(xié)議本身是一種無狀態(tài)的協(xié)議,如果用戶向服務(wù)器提供了用戶名和密碼來進行用戶認證,下次請求時,用戶還要再一次進行用戶認證才行。
因為根據(jù)http協(xié)議,服務(wù)器并不能知道是哪個用戶發(fā)出的請求,所以為了讓我們的應(yīng)用能識別是哪個用戶發(fā)出的請求,我們只能在服務(wù)器存儲─份用戶登錄的信息,這份登錄信息會在響應(yīng)時傳遞給瀏覽器,告訴其保存為cookie,以便下次請求時發(fā)送給我們的應(yīng)用,這樣應(yīng)用就能識別請求來自哪個用戶。
1.2.暴露的問題
- 用戶經(jīng)過應(yīng)用認證后,應(yīng)用都要在服務(wù)端做一次記錄,以方便用戶下次請求的鑒別,通常而言session都是保存在內(nèi)存中,而隨著認證用戶的增多,服務(wù)端的開銷會明顯增大;
- 用戶認證后,服務(wù)端做認證記錄,如果認證的記錄被保存在內(nèi)存中的話,用戶下次請求還必須要請求在這臺服務(wù)器上,這樣才能拿到授權(quán)的資源。在分布式的應(yīng)用上,限制了負載均衡器的能力。以此限制了應(yīng)用的擴展能力;
- session是基于cookie來進行用戶識別,cookie如果被截獲,用戶很容易受到CSRF(跨站偽造請求攻擊)攻擊;
- 在前后端分離系統(tǒng)中應(yīng)用解耦后增加了部署的復(fù)雜性。通常用戶一次請求就要轉(zhuǎn)發(fā)多次。如果用session每次攜帶sessionid到服務(wù)器,服務(wù)器還要查詢用戶信息。同時如果用戶很多。這些信息存儲在服務(wù)器內(nèi)存中,給服務(wù)器增加負擔(dān)。還有就是sessionid就是一個特征值,表達的信息不夠豐富。不容易擴展。而且如果你后端應(yīng)用是多節(jié)點部署。那么就需要實現(xiàn)session共享機制。不方便集群應(yīng)用。
2.JWT認證
2.1.認證流程
- 前端通過Web表單將自己的用戶名和密碼發(fā)送到后端的接口。該過程一般是HTTP的POST請求。建議的方式是通過SSL加密的傳輸(https協(xié)議),從而避免敏感信息被嗅探。
- 后端核對用戶名和密碼成功后,將用戶的id等其他信息作為JWT Payload(負載),將其與頭部分別進行Base64編碼拼接后簽名,形成一個JWT(Token)。
- 后端將JWT字符串作為登錄成功的返回結(jié)果返回給前端。前端可以將返回的結(jié)果保存在localStorage(瀏覽器本地緩存)或sessionStorage(session緩存)上,退出登錄時前端刪除保存的JWT即可。
- 前端在每次請求時將JWT放入HTTP的Header中的Authorization位。(解決XSS和XSRF問題)HEADER
- 后端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正確﹔檢查Token是否過期;檢查Token的接收方是否是自己(可選)
- 驗證通過后后端使用JWT中包含的用戶信息進行其他邏輯操作,返回相應(yīng)結(jié)果。
2.2.JWT優(yōu)點
- 簡潔(Compact):可以通過URL,POST參數(shù)或者在HTTP header發(fā)送,數(shù)據(jù)量小,傳輸速度也很快;
- 自包含(Self-contained):負載中包含了所有用戶所需要的信息,避免了多次查詢數(shù)據(jù)庫;
- Token是以JSON加密的形式保存在客戶端,所以JWT是跨語言的,原則上任何web形式都支持。
- 不需要在服務(wù)端保存會話信息,特別適用于分布式微服務(wù)。
3.JWT結(jié)構(gòu)
就是令牌token,是一個String字符串,由3部分組成,中間用點隔開
令牌組成:
- 標(biāo)頭(Header)
- 有效載荷(Payload)
- 簽名(Signature)
token格式:head.payload.singurater 如:xxxxx.yyyy.zzzz
- Header:有令牌的類型和所使用的簽名算法,如HMAC、SHA256、RSA;使用Base64編碼組成;(Base64是一種編碼,不是一種加密過程,可以被翻譯成原來的樣子)
{ "alg" : "HS256", "type" : "JWT" }
- Payload :有效負載,包含聲明;聲明是有關(guān)實體(通常是用戶)和其他數(shù)據(jù)的聲明,不放用戶敏感的信息,如密碼。同樣使用Base64編碼
{ "sub" : "123", "name" : "John Do", "admin" : true }
- Signature :前面兩部分都使用Base64進行編碼,前端可以解開知道里面的信息。Signature需要使用編碼后的header和payload 加上我們提供的一個密鑰,使用header中指定的簽名算法(HS256)進行簽名。簽名的作用是保證JWT沒有被篡改過
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);
**簽名目的:**簽名的過程實際上是對頭部以及負載內(nèi)容進行簽名,防止內(nèi)容被竄改。如果有人對頭部以及負載的內(nèi)容解碼之后進行修改,再進行編碼,最后加上之前的簽名組合形成新的JWT的話,那么服務(wù)器端會判斷出新的頭部和負載形成的簽名和JWT附帶上的簽名是不一樣的。如果要對新的頭部和負載進行簽名,在不知道服務(wù)器加密時用的密鑰的話,得出來的簽名也是不一樣的。
信息安全問題:Base64是一種編碼,是可逆的,適合傳遞一些非敏感信息;JWT中不應(yīng)該在負載中加入敏感的數(shù)據(jù)。如傳輸用戶的ID被知道也是安全的,如密碼不能放在JWT中;JWT常用于設(shè)計用戶認證、授權(quán)系統(tǒng)、web的單點登錄。
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è)定,就是使用默認的 .withClaim("userId",20)//payload //自定義用戶名 .withClaim("username","zhangsan") .withExpiresAt(instance.getTime()) //指定令牌過期時間 .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();//獲取負載里面對應(yīng)的內(nèi)容 decodedJWT.getClaim("username").asString(); decodedJWT.getExpiresAt();//獲取過期時間
4.4.常見異常信息
SignatureVerificationException //簽名不一致異常 TokenExpiredException //令牌過期異常 AlgorithmMismatchException //算法不匹配異常 InvalidClaimException //失效的payload異常(傳給客戶端后,token被改動,驗證不一致)
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(); } /** * 驗證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.登錄時生成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","認證成功"); map.put("token",token);//響應(yīng)token } catch (Exception e) { map.put("state","false"); map.put("msg",e.getMessage()); }
6.2.聲明一個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驗證攔截器 */ public class JWTInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Map<String,Object> map = new HashMap<>(); //令牌建議是放在請求頭中,獲取請求頭中令牌 String token = request.getHeader("token"); try{ JWTUtils.verify(token);//驗證令牌 return true;//放行請求 } catch (SignatureVerificationException e) { e.printStackTrace(); map.put("msg","無效簽名"); } 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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用synchronized關(guān)鍵字實現(xiàn)信號量的方法
在Java中,信號量(Semaphore)是一種常用的同步工具,它可以用來控制對共享資源的訪問數(shù)量,下面,我們將使用Synchronized關(guān)鍵字來實現(xiàn)一個簡單的信號量,我們的目標(biāo)是實現(xiàn)一個計數(shù)信號量,其中信號量的計數(shù)指示可以同時訪問某一資源的線程數(shù),需要的朋友可以參考下2024-04-04springboot實現(xiàn)調(diào)用百度ocr實現(xiàn)身份識別+二要素校驗功能
本文介紹了如何使用Spring Boot調(diào)用百度OCR服務(wù)進行身份識別,并通過二要素校驗確保信息準(zhǔn)確性,感興趣的朋友一起看看吧2025-03-03詳解poi+springmvc+springjdbc導(dǎo)入導(dǎo)出excel實例
本篇文章主要介紹了poi+springmvc+springjdbc導(dǎo)入導(dǎo)出excel實例,非常具有實用價值,需要的朋友可以參考下。2017-01-01struts2中simple主題下<s:fieldError>標(biāo)簽?zāi)J樣式的移除方法
這篇文章主要給大家介紹了關(guān)于struts2中simple主題下<s:fieldError>標(biāo)簽?zāi)J樣式的移除方法,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10