【SpringBoot深入淺出系列】SpringBoot之集成JWT實(shí)現(xiàn)token驗(yàn)證

      網(wǎng)友投稿 1374 2025-03-31

      一、JWT 是什么?

      JWT(JSON Web Token)是一種開(kāi)放標(biāo)準(zhǔn) (RFC 7519),它定義了一種緊湊且獨(dú)立的方式,用于將信息作為 JSON 對(duì)象在各方之間安全地傳輸。該信息可以進(jìn)行驗(yàn)證和信任,因?yàn)樗墙?jīng)過(guò)數(shù)字簽名的。JWT 可以使用密鑰(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公鑰/私鑰對(duì)進(jìn)行簽名。

      通俗地說(shuō),JWT 的本質(zhì)就是一個(gè)字符串,它是將用戶信息保存到一個(gè) Json 字符串中,然后進(jìn)行編碼后得到一個(gè) JWT token,并且這個(gè) JWT token 帶有簽名信息,接收后可以校驗(yàn)是否被篡改,所以可以用于在各方之間安全地將信息作為 Json 對(duì)象傳輸。

      二、為什么使用 JWT?

      基于 token 的認(rèn)證方式相比傳統(tǒng)的 session 認(rèn)證方式更節(jié)約服務(wù)器資源,并且對(duì)移動(dòng)端和分布式更加友好。具有如下優(yōu)點(diǎn):

      1.支持跨域訪問(wèn)

      cookie 是無(wú)法跨域的,而 token 由于沒(méi)有用到 cookie(前提是將 token 放到請(qǐng)求頭中),所以跨域后不會(huì)存在信息丟失問(wèn)題。

      2.無(wú)狀態(tài)

      token 機(jī)制在服務(wù)端不需要存儲(chǔ) session 信息,因?yàn)?token 自身包含了所有登錄用戶的信息,所以可以減輕服務(wù)端壓力。

      3.更適用CDN

      可以通過(guò)內(nèi)容分發(fā)網(wǎng)絡(luò)請(qǐng)求服務(wù)端的所有資料。

      4.更適用于移動(dòng)端

      當(dāng)客戶端是非瀏覽器平臺(tái)時(shí),cookie 是不被支持的,此時(shí)采用 token 認(rèn)證方式會(huì)簡(jiǎn)單很多。

      5.無(wú)需考慮CSRF

      由于不再依賴 cookie,所以采用 token 認(rèn)證方式不會(huì)發(fā)生 CSRF,所以也就無(wú)需考慮 CSRF 的防御。

      三、何時(shí)使用 JWT?

      1.授權(quán)

      這是使用 JWT 的最常見(jiàn)方案。用戶登錄后,每個(gè)后續(xù)請(qǐng)求都將包含 JWT,允許用戶訪問(wèn)該令牌允許的路由、服務(wù)和資源。單點(diǎn)登錄是當(dāng)今廣泛使用 JWT 的一項(xiàng)功能,因?yàn)樗拈_(kāi)銷很小,并且能夠跨不同域輕松使用。

      2.信息交換

      JWT 是在各方之間安全傳輸信息的好方法。由于 JWT 可以簽名(例如,使用公鑰/私鑰對(duì)),因此您可以確定發(fā)送方就是他們所說(shuō)的人。此外,由于簽名是使用標(biāo)頭和有效負(fù)載計(jì)算的,因此您還可以驗(yàn)證內(nèi)容是否未被篡改。

      四、JWT 結(jié)構(gòu)

      JWT 由三個(gè)部分組成,它們之間由點(diǎn)(.)分隔,分別是:

      標(biāo)頭(header)

      有效載荷(payload)

      簽名(Signature)

      JWT 結(jié)構(gòu)如下所示:

      xxxxx.yyyyy.zzzzz

      1.標(biāo)頭(header)

      標(biāo)頭通常由兩部分組成:令牌的類型和正在使用的簽名算法(如 HMAC SHA256 或 RSA)。例如:

      {

      “alg”: “HS256”,

      “typ”: “JWT”

      }

      使用 Base64 URL 算法將該 JSON 對(duì)象轉(zhuǎn)換為字符串保存,形成 JWT 的第一部分。

      2.有效載荷(payload)

      JWT 的第二部分是有效負(fù)載,其中包含聲明。聲明是關(guān)于實(shí)體(通常是用戶)和其他數(shù)據(jù)的語(yǔ)句。有三種類型的聲明:注冊(cè)聲明、公共聲明和私人聲明。

      注冊(cè)聲明:這些是一組預(yù)定義的聲明,這些聲明不是必需的,但建議提供一組有用的、可互操作的聲明。其中一些是:iss(發(fā)行人),exp(到期時(shí)間),sub(主題),aud(受眾)等。

      請(qǐng)注意,聲明名稱的長(zhǎng)度只有三個(gè)字符,因?yàn)?JWT 應(yīng)該是緊湊的。

      公共聲明:這些可以由使用JWT的人隨意定義。

      私人聲明:這些是為在同意使用它們的各方之間共享信息而創(chuàng)建的自定義聲明,既不是注冊(cè)聲明也不是公開(kāi)聲明。

      【SpringBoot深入淺出系列】SpringBoot之集成JWT實(shí)現(xiàn)token驗(yàn)證

      以下是有效負(fù)載的示例:

      {

      “sub”: “1234567890”,

      “name”: “John Doe”,

      “admin”: true

      }

      然后對(duì)有效負(fù)載進(jìn)行 Base64 URL 編碼,形成 JWT 的第二部分。

      請(qǐng)注意,對(duì)于已簽名的令牌,此信息雖然受到保護(hù)以防止篡改,但任何人都可以讀取。不要將機(jī)密信息放在 JWT 的有效負(fù)載或標(biāo)頭元素中,除非它已加密。

      3.簽名(Signature)

      要?jiǎng)?chuàng)建簽名部分,必須獲取編碼的標(biāo)頭、編碼的有效負(fù)載、密鑰,通過(guò)指定的算法生成哈希,以確保數(shù)據(jù)不會(huì)被篡改。

      例如,如果要使用 HMAC SHA256 算法,將按以下方式創(chuàng)建簽名:

      HMACSHA256(

      base64UrlEncode(header) + “.” +

      base64UrlEncode(payload),

      secret)

      簽名用于驗(yàn)證消息在此過(guò)程中未發(fā)生更改。并且,對(duì)于使用私鑰簽名的令牌,它還可以驗(yàn)證 JWT 的發(fā)送者是否是它所說(shuō)的發(fā)件人。

      將所有內(nèi)容放在一起,輸出是三個(gè) Base64-URL 字符串,由點(diǎn)分隔,可以在 HTML 和 HTTP 環(huán)境中輕松傳遞,同時(shí)與基于 XML 的標(biāo)準(zhǔn)(如 SAML)相比更緊湊。

      五、創(chuàng)建項(xiàng)目集成 JWT 實(shí)現(xiàn) token 驗(yàn)證

      1.項(xiàng)目說(shuō)明

      新建 spring Initializr 項(xiàng)目 jwt,項(xiàng)目下新建controller、entity、utils 類。項(xiàng)目實(shí)現(xiàn)根據(jù)用戶信息生成 token 及驗(yàn)證 token。

      項(xiàng)目目錄結(jié)構(gòu):

      2.創(chuàng)建 spring Initializr 項(xiàng)目 jwt

      (1).添加依賴

      添加依賴,如果已按截圖操作,pom.xml 還需引入 JWT 和 lombok 依賴:

      4.0.0 org.springframework.boot spring-boot-starter-parent 2.6.3 com.chaoyue jwt 0.0.1-SNAPSHOT jwt Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web io.jsonwebtoken jjwt-api 0.11.2 io.jsonwebtoken jjwt-impl 0.11.2 io.jsonwebtoken jjwt-jackson 0.11.2 org.projectlombok lombok 1.18.22 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin

      (2).添加配置

      application.yml 文件中添加如下配置:

      server: port: 8080 jwt: secret: qnAqsQa7600vrTBcr1WB8P8dg4cbgS5i8LZGjWnpREL # 密鑰 expiration: 30 # token 有效期(S)

      PS:密鑰可通過(guò)在線工具庫(kù)生成:https://www.gjk.cn/randstr

      (3).新建實(shí)體類 User

      為減少不必要的代碼,引入 lombok 依賴。實(shí)體類代碼如下:

      package com.chaoyue.jwt.entity; import lombok.Data; @Data public class User { // @TableId(type = IdType.AUTO) private Long id; // id private String username; // 用戶名 private String password; // 密碼 }

      (4).新建 JWT 工具類 JwtUtils

      package com.chaoyue.jwt.utils; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import java.util.Date; import java.util.HashMap; import java.util.Map; @Component public class JwtUtils { private static final String CLAIM_KEY_USERNAME = "sub"; private static final String CLAIM_KEY_CREATED = "created"; /** * 密鑰 */ @Value("${jwt.secret}") private String secret; /** * token有效期 (S) */ @Value("${jwt.expiration}") private Long expiration; /** * 根據(jù)用戶信息生成 token * @param userInfo * @return */ @SneakyThrows public String generateToken(Object userInfo) { Map claims = new HashMap<>(); claims.put(CLAIM_KEY_USERNAME, new ObjectMapper().writeValueAsString(userInfo)); claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); } /** * 從 token 中獲取用戶信息 * @param token * @param valueType * @param * @return */ @SneakyThrows public T getUserInfoFromToken(String token, Class valueType) { Claims claims = getClaimsFromToken(token); return new ObjectMapper().readValue(claims.getSubject(), valueType); } /** * 判斷 token 是否有效 * @param token * @return */ public boolean isTokenExpired(String token) { Date expiredDate = getExpiredDateFromToken(token); return expiredDate.after(new Date()); } /** * 刷新 token * @param token * @return */ public String refreshToken(String token) { Claims claims = getClaimsFromToken(token); claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); } /** * 生成 token * @param claims * @return */ private String generateToken(Map claims) { return Jwts.builder() .setClaims(claims) .setExpiration(generateExpirationDate()) .signWith(generateKeyByDecoders()) .compact(); } /** * 從 token 中獲取 * @param token * @return */ private Claims getClaimsFromToken(String token) { return Jwts.parserBuilder() .setSigningKey(generateKeyByDecoders()) .build() .parseClaimsJws(token) .getBody(); } /** * 生成 token 過(guò)期時(shí)間 * @return */ private Date generateExpirationDate() { return new Date(System.currentTimeMillis() + expiration * 1000); } /** * 生成自定義 Key * @return */ private SecretKey generateKeyByDecoders() { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); } /** * 從 token 中獲取過(guò)期時(shí)間 * @param token * @return */ private Date getExpiredDateFromToken(String token) { Claims claims = getClaimsFromToken(token); return claims.getExpiration(); } }

      (5).新建控制類 LoginController

      package com.chaoyue.jwt.controller; import com.chaoyue.jwt.entity.User; import com.chaoyue.jwt.utils.JwtUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class LoginController { @Autowired private JwtUtils jwtUtils; @GetMapping("login") public String login(User user) { // 生成token User userInfo = new User(); userInfo.setId(1L); userInfo.setUsername("admin"); userInfo.setPassword("123456"); return jwtUtils.generateToken(userInfo); } }

      3.啟動(dòng)服務(wù)并測(cè)試

      啟動(dòng)服務(wù)后,瀏覽器輸入:http://localhost:8080/user/login,返回:

      PS:JWT 官網(wǎng):https://jwt.io/

      JSON Spring Boot 通用安全

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:怎么設(shè)置分頁(yè)背景,就是每一頁(yè)的背景可以單獨(dú)設(shè)置(word怎么分頁(yè)設(shè)置背景)
      下一篇:在表格里面怎么換下一行(在表格里面怎么換下一行文字)
      相關(guān)文章
      国产精品亚洲片在线观看不卡| 亚洲日韩中文无码久久| 亚洲精品韩国美女在线| 久久亚洲精品国产精品黑人| 国产亚洲大尺度无码无码专线| 亚洲区不卡顿区在线观看| 亚洲国产婷婷综合在线精品| 亚洲精品国产成人影院| mm1313亚洲国产精品美女| 亚洲成av人片在线观看天堂无码 | 国产亚洲日韩在线a不卡| 国产精品亚洲色图| 国产午夜亚洲精品不卡| 亚洲精品乱码久久久久久蜜桃 | 色偷偷亚洲第一综合网| 国产综合成人亚洲区| 亚洲6080yy久久无码产自国产| 亚洲AV无码国产一区二区三区 | 国产青草亚洲香蕉精品久久| 亚洲国产精品自在拍在线播放| 久久精品亚洲福利| 亚洲精品少妇30p| 亚洲ⅴ国产v天堂a无码二区| 亚洲日韩图片专区第1页| 亚洲成人免费网站| 在线观看亚洲AV每日更新无码| 亚洲免费网站观看视频| 婷婷亚洲天堂影院| 在线亚洲午夜理论AV大片| 久久久久亚洲精品无码系列| 久久亚洲AV成人无码| 亚洲综合久久一本伊伊区| 亚洲av色香蕉一区二区三区 | 亚洲AV无码专区日韩| 国产亚洲美女精品久久久2020| 亚洲av永久无码精品网站| 伊人久久综在合线亚洲2019| 亚洲另类图片另类电影| 亚洲av日韩av永久无码电影 | 亚洲日韩在线视频| 亚洲精品中文字幕|