Spring?Boot?集成JWT實(shí)現(xiàn)前后端認(rèn)證的示例代碼
前言
小程序、H5應(yīng)用的快速發(fā)展,使得前后端分離已經(jīng)成為了趨勢(shì),然而系統(tǒng)認(rèn)證卻是系統(tǒng)的重要一部分,本文將講解JWT如何實(shí)現(xiàn)前后端認(rèn)證。
JWT簡(jiǎn)介
JWT(全稱(chēng):Json Web Token)是一個(gè)開(kāi)放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊的、自包含的方式,用于作為JSON對(duì)象在各方之間安全地傳輸信息。
為什么要用JWT
傳統(tǒng)session認(rèn)證存在那些弊端?
每個(gè)用戶的登錄信息都會(huì)保存到服務(wù)器的Session中,隨著用戶的增多,服務(wù)器開(kāi)銷(xiāo)會(huì)明顯增大。
Session的信息存放在服務(wù)器的內(nèi)存中,對(duì)于分布式應(yīng)用會(huì)導(dǎo)致失效,雖然可以將session的信息統(tǒng)一存放在Redis的緩存中,但這樣可能增加了復(fù)雜性。
由于Session認(rèn)證是基于Cookie實(shí)現(xiàn),而針對(duì)于非瀏覽器端和手機(jī)的移動(dòng)端都不適用。
前后端分離系統(tǒng),由于前后端存在跨域,而Cookie信息無(wú)法跨越,所以采用Session認(rèn)證也是無(wú)法繼續(xù)寧跨域認(rèn)證。
JWT認(rèn)證的優(yōu)勢(shì)
簡(jiǎn)潔:JWT Token數(shù)據(jù)量小,傳輸速度也很快。
跨語(yǔ)言: JWT Token是以JSON加密形式保存在客戶端的,所以JWT是跨語(yǔ)言的,任何web形式都支持。 跨平臺(tái):不依賴于cookie和session,無(wú)需將session信息存放在服務(wù)端,非常適合于分布式應(yīng)用,應(yīng)用于擴(kuò)展。
JWT的數(shù)據(jù)結(jié)構(gòu)
Header
JWT第一部分是頭部分,它是一個(gè)描述JWT元數(shù)據(jù)的Json對(duì)象,通常如下所示。
{ "alg": "HS256", "typ": "JWT" }
alg屬性表示簽名使用的算法,默認(rèn)為HMAC SHA256(寫(xiě)為HS256),typ屬性表示令牌的類(lèi)型,JWT令牌統(tǒng)一寫(xiě)為JWT。
Payload
JWT第二部分是Payload,也是一個(gè)Json對(duì)象,除了包含需要傳遞的數(shù)據(jù),還有七個(gè)默認(rèn)的字段供選擇。 iss:發(fā)行人 exp:到期時(shí)間 sub:主題 aud:用戶 nbf:在此之前不可用 iat:發(fā)布時(shí)間 jti:JWT ID用于標(biāo)識(shí)該JWT
{ //默認(rèn)字段 "sub":"主題123", //自定義字段 "name":"java", "isAdmin":"true", "loginTime":"2021-12-05 12:00:03" }
需要注意的是,默認(rèn)情況下JWT是未加密的,任何人都可以解讀其內(nèi)容,因此如果一些敏感信息不要存放在此,以防信息泄露。JSON對(duì)象也使用Base64 URL算法轉(zhuǎn)換為字符串保存。
Signature
簽名哈希部分是對(duì)上面兩部分?jǐn)?shù)據(jù)簽名,需要使用base64編碼后的header和payload數(shù)據(jù),通過(guò)指定的算法生成哈希,以確保數(shù)據(jù)不會(huì)被篡改。
Spring Boot集成JWT
引入Jwt包
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
編寫(xiě)jwt工具類(lèi)
public class JwtUtil { //創(chuàng)建jwt public static String createJWT(String subject, String issue, Object claim, long ttlMillis) { //當(dāng)前時(shí)間 long nowMillis = System.currentTimeMillis(); //過(guò)期時(shí)間 long expireMillis = nowMillis + ttlMillis; String result = Jwts.builder() .setSubject(subject) //設(shè)置主題 .setIssuer(issue) //發(fā)行者 .setId(issue)//jwtID .setExpiration(new Date(expireMillis)) //設(shè)置過(guò)期日期 .claim("user", claim)//主題,可以包含用戶信息 .signWith(getSignatureAlgorithm(), getSignedKey())//加密算法 .compressWith(CompressionCodecs.DEFLATE).compact();//對(duì)載荷進(jìn)行壓縮 return result; } // 解析jwt public static Jws<Claims> pareseJWT(String jwt) { Jws<Claims> claims; try { claims = Jwts.parser().setSigningKey(getSignedKey()) .parseClaimsJws(jwt); } catch (Exception ex) { claims = null; } return claims; } //獲取主題信息 public static Claims getClaims(String jwt) { Claims claims; try { claims = Jwts.parser().setSigningKey(getSignedKey()) .parseClaimsJws(jwt).getBody(); } catch (Exception ex) { claims = null; } return claims; } } /** * 獲取密鑰 * * @return Key */ private static Key getSignedKey() { byte[] apiKeySecretBytes = DatatypeConverter .parseBase64Binary(getAuthKey()); Key signingKey = new SecretKeySpec(apiKeySecretBytes, getSignatureAlgorithm().getJcaName()); return signingKey; } private static SignatureAlgorithm getSignatureAlgorithm() { return SignatureAlgorithm.HS256; } //獲取密鑰,可以動(dòng)態(tài)配置 public static String getAuthKey() { String auth = "123@#1234"; }
Token認(rèn)證攔截器
Component public class TokenInterceptor extends HandlerInterceptorAdapter { public static Log logger = LogManager.getLogger(TokenInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); logger.info("start TokenInterceptor preHandle.." + uri); //需要過(guò)濾特殊請(qǐng)求 if (SystemUtil.isFree(uri) || SystemUtil.isProtected(uri)) { return true; } String metohd=request.getMethod().toString(); logger.info("TokenInterceptor request method:"+metohd); //options 方法需要過(guò)濾 if("OPTIONS".equals(metohd)) { return true; } //是否開(kāi)啟token認(rèn)證 boolean flag = SystemUtil.getVerifyToken(); ResponseResult result = new ResponseResult(); //從請(qǐng)求的head信息中獲取token String token = request.getHeader("X-Token"); if (flag) { if(StringUtils.isEmpty(token)) { token=request.getParameter("X-Token"); } // token不存在 if (StringUtils.isEmpty(token)) { result.setCode(ResultCode.NEED_AUTH.getCode()); result.setMsg(ResultCode.NEED_AUTH.getMsg()); WebUtil.writeJson(result, response); return false; } else { Claims claims = JwtUtil.getClaims(token); String subject = ""; if (claims != null) { subject = claims.getSubject(); // 驗(yàn)證主題 if (StringUtils.isEmpty(subject)) { result.setCode(ResultCode.INVALID_TOKEN.getCode()); result.setMsg(ResultCode.INVALID_TOKEN.getMsg()); WebUtil.writeJson(result, response); return false; } } else { result.setCode(ResultCode.INVALID_TOKEN.getCode()); result.setMsg(ResultCode.INVALID_TOKEN.getMsg()); WebUtil.writeJson(result, response); return false; } } } return true; } }
配置攔擊器
@Configuration public class WebConfig implements WebMvcConfigurer { @Resource private TokenInterceptor tokenInterceptor; public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor).addPathPatterns("/**"); } }
登錄驗(yàn)證流程
示例代碼
@RequestMapping("login") public Result login(HttpServletResponse response) { Map<String, Object> map = new HashMap<>(); // Result result = loginAuth(user); int code = result.getCode(); //登錄認(rèn)證成功 if (code ==ResultCode.SUCCESS) { //默認(rèn)為7天 Long ttlMillis = 7*1000 * 60 * 60 * 24; //過(guò)期時(shí)間 long expreTime = System.currentTimeMillis() + ttlMillis; String tokenKey = UUID.randomUUID().toString(); String tokenId = JwtUtil.createJWT(user.getUserId(), tokenKey, user.getPassword(), expreTime); map.put("expreTime", expreTime); map.put("tokenId", tokenId); } else { logger.error("login error:" +FastJsonUtil.toJSONString(result)); } return result; }
總結(jié)
時(shí)代在進(jìn)步,技術(shù)也在不斷更新,以前采用Session+Redis實(shí)現(xiàn)單點(diǎn)登錄,現(xiàn)在可以替換為jwt+Redis實(shí)現(xiàn),我們只有不斷的更新自己的技術(shù)棧,才能避免被無(wú)情的淘汰,關(guān)于使用注解方式實(shí)現(xiàn)和token刷新,將在后續(xù)文章中進(jìn)行講解。
到此這篇關(guān)于Spring Boot 集成JWT實(shí)現(xiàn)前后端認(rèn)證的示例代碼的文章就介紹到這了,更多相關(guān)SpringBoot JWT前后端認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 基于Springboot實(shí)現(xiàn)JWT認(rèn)證的示例代碼
- SpringBoot+Vue+JWT的前后端分離登錄認(rèn)證詳細(xì)步驟
- SpringBoot集成Spring security JWT實(shí)現(xiàn)接口權(quán)限認(rèn)證
- Springboot WebFlux集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的示例
- Springboot集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的步驟詳解
- 利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼
- SpringBoot使用Jwt處理跨域認(rèn)證問(wèn)題的教程詳解
- Springboot+SpringSecurity+JWT實(shí)現(xiàn)用戶登錄和權(quán)限認(rèn)證示例
- springboot+jwt實(shí)現(xiàn)token登陸權(quán)限認(rèn)證的實(shí)現(xiàn)
- Vue+Jwt+SpringBoot+Ldap完成登錄認(rèn)證的示例代碼
相關(guān)文章
spring boot基于DRUID實(shí)現(xiàn)數(shù)據(jù)源監(jiān)控過(guò)程解析
這篇文章主要介紹了spring boot基于DRUID實(shí)現(xiàn)數(shù)據(jù)源監(jiān)控過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12使用filter實(shí)現(xiàn)url級(jí)別內(nèi)存緩存示例
這篇文章主要介紹了使用filter實(shí)現(xiàn)url級(jí)別內(nèi)存緩存示例,只需要一個(gè)靜態(tài)類(lèi),在filter中調(diào)用,也可以全部寫(xiě)到filt里面。可以根據(jù)查詢參數(shù)分別緩存,需要的朋友可以參考下2014-03-03SpringCloud整合Consul的實(shí)現(xiàn)
這篇文章主要介紹了SpringCloud整合Consul的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01java連接sql server 2008數(shù)據(jù)庫(kù)代碼
Java的學(xué)習(xí),很重要的一點(diǎn)是對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作。2013-03-03SpringBoot 整合 Shiro 密碼登錄與郵件驗(yàn)證碼登錄功能(多 Realm 認(rèn)證)
這篇文章主要介紹了SpringBoot 整合 Shiro 密碼登錄與郵件驗(yàn)證碼登錄(多 Realm 認(rèn)證),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02java javax.annotation.Resource注解的詳解
這篇文章主要介紹了javax.annotation.Resource注解的詳解的相關(guān)資料,需要的朋友可以參考下2016-10-10解決weblogic部署springboot項(xiàng)目步驟及可能會(huì)出現(xiàn)的問(wèn)題
這篇文章主要介紹了解決weblogic部署springboot項(xiàng)目步驟及可能會(huì)出現(xiàn)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07