SpringBoot集成?JWT實(shí)現(xiàn)用戶登錄認(rèn)證的項(xiàng)目實(shí)踐
前言:當(dāng)今前后端分離時(shí)代,基于Token的會(huì)話保持機(jī)制比傳統(tǒng)的Session/Cookie機(jī)制更加方便,下面我會(huì)介紹SpringBoot快速集成JWT庫(kù)java-jwt以完成用戶登錄認(rèn)證。
一、JWT 簡(jiǎn)介
1.1、 JWT的概念
JWT 是 JSON Web Token 的縮寫,是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于 JSON
的開放標(biāo)準(zhǔn)((RFC 7519)。定義了一種簡(jiǎn)潔的,自包含的方法用于通信雙方之間以 JSON
對(duì)象的形式安全的傳遞信息。因?yàn)閿?shù)字簽名的存在,這些信息是可信的,JWT 可以使用 HMAC
算法或者是 RSA
的公私秘鑰對(duì)進(jìn)行簽名。
1.2、JWT請(qǐng)求流程
JWT 請(qǐng)求流程
- 用戶使用賬號(hào)和密碼發(fā)起 POST 請(qǐng)求;
- 服務(wù)器使用私鑰創(chuàng)建一個(gè) JWT;
- 服務(wù)器返回這個(gè) JWT 給瀏覽器;
- 瀏覽器將該 JWT 串在請(qǐng)求頭中像服務(wù)器發(fā)送請(qǐng)求;
- 服務(wù)器驗(yàn)證該 JWT;
- 返回響應(yīng)的資源給瀏覽器。
1.3、JWT 的主要應(yīng)用場(chǎng)景
身份認(rèn)證在這種場(chǎng)景下,一旦用戶完成了登錄,在接下來(lái)的每個(gè)請(qǐng)求中包含 JWT,可以用來(lái)驗(yàn)證用戶身份以及對(duì)路由,服務(wù)和資源的訪問權(quán)限進(jìn)行驗(yàn)證。由于它的開銷非常小,可以輕松的在不同域名的系統(tǒng)中傳遞,所有目前在單點(diǎn)登錄(SSO)中比較廣泛的使用了該技術(shù)。 信息交換在通信的雙方之間使用 JWT 對(duì)數(shù)據(jù)進(jìn)行編碼是一種非常安全的方式,由于它的信息是經(jīng)過簽名的,可以確保發(fā)送者發(fā)送的信息是沒有經(jīng)過偽造的。
1.4、JWT 數(shù)據(jù)結(jié)構(gòu)
JWT 是由三段信息構(gòu)成的,將這三段信息文本用 .
連接一起就構(gòu)成了 JWT 字符串。JWT 的三個(gè)部分依次為頭部:Header,負(fù)載:Payload 和簽名:Signature。
①Header:
Header 部分是一個(gè) JSON 對(duì)象,描述 JWT 的元數(shù)據(jù),通常是下面的樣子。
{ "alg": "HS256", "typ": "JWT" }
上面代碼中, alg
屬性表示簽名的算法(algorithm),默認(rèn)是 HMAC SHA256(寫成 HS256); typ
屬性表示這個(gè)令牌(token)的類型(type),JWT 令牌統(tǒng)一寫為 JWT
。
最后,將上面的 JSON 對(duì)象使用 Base64URL 算法轉(zhuǎn)成字符串。
②Payload:
Payload 部分也是一個(gè) JSON 對(duì)象,用來(lái)存放實(shí)際需要傳遞的有效信息。有效信息包含三個(gè)部分:
- 標(biāo)準(zhǔn)中注冊(cè)的聲明
- 公共的聲明
- 私有的聲明
標(biāo)準(zhǔn)中注冊(cè)的聲明 (建議但不強(qiáng)制使用) :
- iss (issuer):簽發(fā)人
- exp (expiration time):過期時(shí)間,必須要大于簽發(fā)時(shí)間
- sub (subject):主題
- aud (audience):受眾
- nbf (Not Before):生效時(shí)間
- iat (Issued At):簽發(fā)時(shí)間
- jti (JWT ID):編號(hào),JWT 的唯一身份標(biāo)識(shí),主要用來(lái)作為一次性
token
,從而回避重放攻擊。
公共的聲明 :公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息。但不建議添加敏感信息,因?yàn)樵摬糠衷诳蛻舳丝山饷堋?/p>
私有的聲明 :私有聲明是提供者和消費(fèi)者所共同定義的聲明,一般不建議存放敏感信息,因?yàn)?nbsp; base64
是對(duì)稱解碼的,意味著該部分信息可以歸類為明文信息。這個(gè) JSON 對(duì)象也要使用 Base64URL 算法轉(zhuǎn)成字符串。
③Signature:
Signature 部分是對(duì)前兩部分的簽名,防止數(shù)據(jù)篡改。
首先,需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認(rèn)是 HMAC SHA256),按照下面的公式產(chǎn)生簽名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出簽名以后,把 Header、Payload、Signature 三個(gè)部分拼成一個(gè)字符串,每個(gè)部分之間用"點(diǎn)"( .
)分隔,就可以返回給用戶。
Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個(gè)算法跟 Base64 算法基本類似,但有一些小的不同。
JWT 作為一個(gè)令牌(token),有些場(chǎng)合可能會(huì)放到 URL(比如 api.example.com/?token=xxx
)。Base64 有三個(gè)字符 +
、 /
和 =
,在 URL 里面有特殊含義,所以要被替換掉: =
被省略、 +
替換成 -
, /
替換成 _
。這就是 Base64URL 算法。
1.5、JWT 的使用方式
客戶端收到服務(wù)器返回的 JWT 之后需要在本地做保存。此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè) JWT。一般的的做法是放在 HTTP 請(qǐng)求的頭信息 Authorization
字段里面。
Authorization: Bearer <token>
這樣每個(gè)請(qǐng)求中,服務(wù)端就可以在請(qǐng)求頭中拿到 JWT 進(jìn)行解析與認(rèn)證。
1.6、JWT 的特性
- JWT 默認(rèn)是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次。
- JWT 不加密的情況下,不能將秘密數(shù)據(jù)寫入 JWT。
- JWT 不僅可以用于認(rèn)證,也可以用于交換信息。有效使用 JWT,可以降低服務(wù)器查詢數(shù)據(jù)庫(kù)的次數(shù)。
- JWT 的最大缺點(diǎn)是,由于服務(wù)器不保存 session 狀態(tài),因此無(wú)法在使用過程中廢止某個(gè) token,或者更改 token 的權(quán)限。也就是說,一旦 JWT 簽發(fā)了,在到期之前就會(huì)始終有效,除非服務(wù)器部署額外的邏輯。
- JWT 本身包含了認(rèn)證信息,一旦泄露,任何人都可以獲得該令牌的所有權(quán)限。為了減少盜用,JWT 的有效期應(yīng)該設(shè)置得比較短。對(duì)于一些比較重要的權(quán)限,使用時(shí)應(yīng)該再次對(duì)用戶進(jìn)行認(rèn)證。
- 為了減少盜用,JWT 不應(yīng)該使用 HTTP 協(xié)議明碼傳輸,要使用 HTTPS 協(xié)議傳輸。
二、SpringBoot整合JWT
新建一個(gè)spring boot項(xiàng)目spring-boot-jwt,按照下面步驟操作。
2.1、pom.xml引入jar包
<!-- 引入jwt--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.2</version> </dependency>
順便貼一下下面要用到的User類:
package com.hs.demo.entity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor @ApiModel public class User { //實(shí)體類中,Integer類型的屬性加@ApiModelProperty時(shí),必須要給example參數(shù)賦值,且值必須為數(shù)字類型。 @ApiModelProperty(value = "用戶id",example = "1") private Integer id; @ApiModelProperty(value = "用戶名") private String userName; @ApiModelProperty(value = "用戶密碼") private String password; //getter/setter用@Data注解自動(dòng)生成 }
2.2、新建Jwt工具類
Jwt工具類進(jìn)行token的生成和認(rèn)證,工具類代碼如下:
package com.hs.demo.jwt; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.hs.demo.entity.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @description: Jwt工具類,生成JWT和認(rèn)證 * @author: heshi */ public class JwtUtil { private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class); /** * 密鑰 */ private static final String SECRET = "my_secret"; /** * 過期時(shí)間 **/ private static final long EXPIRATION = 1800L;//單位為秒 /** * 生成用戶token,設(shè)置token超時(shí)時(shí)間 */ public static String createToken(User user) { //過期時(shí)間 Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000); Map<String, Object> map = new HashMap<>(); map.put("alg", "HS256"); map.put("typ", "JWT"); String token = JWT.create() .withHeader(map)// 添加頭部 //可以將基本信息放到claims中 .withClaim("id", user.getId())//userId .withClaim("userName", user.getUserName())//userName .withClaim("password", user.getPassword())//password .withExpiresAt(expireDate) //超時(shí)設(shè)置,設(shè)置過期的日期 .withIssuedAt(new Date()) //簽發(fā)時(shí)間 .sign(Algorithm.HMAC256(SECRET)); //SECRET加密 return token; } /** * 校驗(yàn)token并解析token */ public static Map<String, Claim> verifyToken(String token) { DecodedJWT jwt = null; try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build(); jwt = verifier.verify(token); //decodedJWT.getClaim("屬性").asString() 獲取負(fù)載中的屬性值 } catch (Exception e) { logger.error(e.getMessage()); logger.error("token解碼異常"); //解碼異常則拋出異常 return null; } return jwt.getClaims(); } }
2.3、添加JWT過濾器
JWT過濾器中進(jìn)行token的校驗(yàn)和判斷,token不合法直接返回,合法則解密數(shù)據(jù)并把數(shù)據(jù)放到request中供后續(xù)使用。
為了使過濾器生效,需要在啟動(dòng)類添加注解@ServletComponentScan(basePackages = "com.hs.demo.jwt")。
JWT過濾器代碼如下:
package com.hs.demo.jwt; import com.auth0.jwt.interfaces.Claim; import lombok.extern.slf4j.Slf4j; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; /** * JWT過濾器,攔截 /secure的請(qǐng)求 */ @Slf4j @WebFilter(filterName = "JwtFilter", urlPatterns = "/secure/*") public class JwtFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; response.setCharacterEncoding("UTF-8"); //獲取 header里的token final String token = request.getHeader("authorization"); if ("OPTIONS".equals(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); chain.doFilter(request, response); } // Except OPTIONS, other request should be checked by JWT else { if (token == null) { response.getWriter().write("沒有token!"); return; } Map<String, Claim> userData = JwtUtil.verifyToken(token); if (userData == null) { response.getWriter().write("token不合法!"); return; } Integer id = userData.get("id").asInt(); String userName = userData.get("userName").asString(); String password= userData.get("password").asString(); //攔截器 拿到用戶信息,放到request中 request.setAttribute("id", id); request.setAttribute("userName", userName); request.setAttribute("password", password); chain.doFilter(req, res); } } @Override public void destroy() { } }
2.4、添加LoginController
LoginController進(jìn)行登錄操作,登錄成功后生產(chǎn)token并返回。
LoginController代碼如下:
package com.hs.demo.jwt; import com.hs.demo.entity.User; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; /** * 登錄Controller */ @Slf4j @RestController public class LoginController { static Map<Integer, User> userMap = new HashMap<>(); static { //模擬數(shù)據(jù)庫(kù) User user1 = new User(1,"張三","123456"); userMap.put(1, user1); User user2 = new User(2,"李四","123123"); userMap.put(2, user2); } /** * 模擬用戶 登錄 */ @RequestMapping("/login") public String login(User user) { for (User dbUser : userMap.values()) { if (dbUser.getUserName().equals(user.getUserName()) && dbUser.getPassword().equals(user.getPassword())) { log.info("登錄成功!生成token!"); String token = JwtUtil.createToken(dbUser); return token; } } return ""; } }
2.5、添加SecureController
SecureController中的請(qǐng)求會(huì)被JWT過濾器攔截,合法后才能訪問。
SecureController代碼如下:
package com.hs.demo.jwt; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; /** * 需要登錄后攜帶JWT才能訪問 */ @Slf4j @RestController public class SecureController { /** * 查詢 用戶信息,登錄后攜帶JWT才能訪問 */ @RequestMapping("/secure/getUserInfo") public String login(HttpServletRequest request) { Integer id = (Integer) request.getAttribute("id"); String userName = request.getAttribute("userName").toString(); String password= request.getAttribute("password").toString(); return "當(dāng)前用戶信息id=" + id + ",userName=" + userName+ ",password=" + password; } }
三、接口測(cè)試
測(cè)試分兩步,首先訪問登錄接口,登錄成功后獲取token,然后拿著token在訪問查詢用戶信息接口。
3.1、訪問登錄接口
打開PostMan,訪問http://localhost:8080/login?userName=zhangsan&password=123456,登錄成功后接口返回token,請(qǐng)求成功截圖如下:
3.2、訪問用戶信息接口
打開PostMan,訪問http://localhost:8080/secure/getUserInfo,header里需要攜帶token,請(qǐng)求成功截圖如下:
四、Token 認(rèn)證的優(yōu)勢(shì)
相比于 Session 認(rèn)證的方式來(lái)說,使用 token 進(jìn)行身份認(rèn)證主要有下面三個(gè)優(yōu)勢(shì):
4.1、無(wú)狀態(tài)
JWT實(shí)現(xiàn)的Token 自身包含了身份驗(yàn)證所需要的所有信息,使得我們的服務(wù)器不需要存儲(chǔ) Session 信息,這顯然增加了系統(tǒng)的可用性和伸縮性,大大減輕了服務(wù)端的壓力。但是,也正是由于 token 的無(wú)狀態(tài),也導(dǎo)致了它最大的缺點(diǎn):當(dāng)后端在token 有效期內(nèi)廢棄一個(gè) token 或者更改它的權(quán)限的話,不會(huì)立即生效,一般需要等到有效期過后才可以。另外,當(dāng)用戶 Logout 的話,token 也還有效。除非,我們?cè)诤蠖嗽黾宇~外的處理邏輯。
4.2、有效避免了CSRF 攻擊
CSRF(Cross Site Request Forgery) 一般被翻譯為 跨站請(qǐng)求偽造,屬于網(wǎng)絡(luò)攻擊領(lǐng)域范圍。相比于 SQL 腳本注入、XSS等等安全攻擊方式,CSRF 的知名度并沒有它們高。但是,它的確是每個(gè)系統(tǒng)都要考慮的安全隱患,就連技術(shù)帝國(guó) Google 的 Gmail 在早些年也被曝出過存在 CSRF 漏洞,這給 Gmail 的用戶造成了很大的損失。
那么究竟什么是 跨站請(qǐng)求偽造 呢?說簡(jiǎn)單用你的身份去發(fā)送一些對(duì)你不友好的請(qǐng)求。舉個(gè)簡(jiǎn)單的例子:
小壯登錄了某網(wǎng)上銀行,他來(lái)到了網(wǎng)上銀行的帖子區(qū),看到一個(gè)帖子下面有一個(gè)鏈接寫著“科學(xué)理財(cái),年盈利率過萬(wàn)”,小壯好奇的點(diǎn)開了這個(gè)鏈接,結(jié)果發(fā)現(xiàn)自己的賬戶少了10000元。這是這么回事呢?原來(lái)黑客在鏈接中藏了一個(gè)請(qǐng)求,這個(gè)請(qǐng)求直接利用小壯的身份給銀行發(fā)送了一個(gè)轉(zhuǎn)賬請(qǐng)求,也就是通過你的 Cookie 向銀行發(fā)出請(qǐng)求。
<a src=http://www.mybank.com/Transfer?bankId=11&money=10000>科學(xué)理財(cái),年盈利率過萬(wàn)</>
導(dǎo)致這個(gè)問題很大的原因就是:Session 認(rèn)證中 Cookie 中的 session_id 是由瀏覽器發(fā)送到服務(wù)端的,借助這個(gè)特性,攻擊者就可以通過讓用戶誤點(diǎn)攻擊鏈接,達(dá)到攻擊效果。
那為什么 token 不會(huì)存在這種問題呢?
我是這樣理解的:一般情況下我們使用 JWT 的話,在我們登錄成功獲得 token 之后,一般會(huì)選擇存放在 local storage 中。然后我們?cè)谇岸送ㄟ^某些方式會(huì)給每個(gè)發(fā)到后端的請(qǐng)求加上這個(gè) token,這樣就不會(huì)出現(xiàn) CSRF 漏洞的問題。因?yàn)?,即使有個(gè)你點(diǎn)擊了非法鏈接發(fā)送了請(qǐng)求到服務(wù)端,這個(gè)非法請(qǐng)求是不會(huì)攜帶 token 的,所以這個(gè)請(qǐng)求將是非法的。
但是這樣會(huì)存在 XSS 攻擊中被盜的風(fēng)險(xiǎn),為了避免 XSS 攻擊,你可以選擇將 token 存儲(chǔ)在標(biāo)記為 httpOnly
的cookie 中。但是,這樣又導(dǎo)致了你必須自己提供CSRF保護(hù)。
具體采用上面哪兩種方式存儲(chǔ) token 呢,大部分情況下存放在 local storage 下都是最好的選擇,某些情況下可能需要存放在標(biāo)記為 httpOnly
的cookie 中會(huì)更好。
4.3、適合移動(dòng)端應(yīng)用
使用 Session 進(jìn)行身份認(rèn)證的話,需要保存一份信息在服務(wù)器端,而且這種方式會(huì)依賴到 Cookie(需要 Cookie 保存 SessionId),所以不適合移動(dòng)端。
但是,使用 token 進(jìn)行身份認(rèn)證就不會(huì)存在這種問題,因?yàn)橹灰?token 可以被客戶端存儲(chǔ)就能夠使用,而且 token 還可以跨語(yǔ)言使用。
4.4、單點(diǎn)登錄友好
使用 Session 進(jìn)行身份認(rèn)證的話,實(shí)現(xiàn)單點(diǎn)登錄,需要我們把用戶的 Session 信息保存在一臺(tái)電腦上,并且還會(huì)遇到常見的 Cookie 跨域的問題。但是,使用 token 進(jìn)行認(rèn)證的話, token 被保存在客戶端,不會(huì)存在這些問題。
五、Token 認(rèn)證常見問題以及解決辦法
5.1、注銷登錄等場(chǎng)景下 token 還有效
- 與之類似的具體相關(guān)場(chǎng)景有:
- 退出登錄;
- 修改密碼;
- 服務(wù)端修改了某個(gè)用戶具有的權(quán)限或者角色;
- 用戶的帳戶被刪除/暫停。
- 用戶由管理員注銷;
這個(gè)問題不存在于 Session 認(rèn)證方式中,因?yàn)樵? Session 認(rèn)證方式中,遇到這種情況的話服務(wù)端刪除對(duì)應(yīng)的 Session 記錄即可。但是,使用 token 認(rèn)證的方式就不好解決了。我們也說過了,token 一旦派發(fā)出去,如果后端不增加其他邏輯的話,它在失效之前都是有效的。那么,我們?nèi)绾谓鉀Q這個(gè)問題呢?查閱了很多資料,總結(jié)了下面幾種方案:
- 將 token 存入內(nèi)存數(shù)據(jù)庫(kù):將 token 存入 DB 中,redis 內(nèi)存數(shù)據(jù)庫(kù)在這里是是不錯(cuò)的選擇。如果需要讓某個(gè) token 失效就直接從 redis 中刪除這個(gè) token 即可。但是,這樣會(huì)導(dǎo)致每次使用 token 發(fā)送請(qǐng)求都要先從 DB 中查詢 token 是否存在的步驟,而且違背了 JWT 的無(wú)狀態(tài)原則。
- 黑名單機(jī)制:和上面的方式類似,使用內(nèi)存數(shù)據(jù)庫(kù)比如 redis 維護(hù)一個(gè)黑名單,如果想讓某個(gè) token 失效的話就直接將這個(gè) token 加入到 黑名單 即可。然后,每次使用 token 進(jìn)行請(qǐng)求的話都會(huì)先判斷這個(gè) token 是否存在于黑名單中。
- 修改密鑰 (Secret) : 我們?yōu)槊總€(gè)用戶都創(chuàng)建一個(gè)專屬密鑰,如果我們想讓某個(gè) token 失效,我們直接修改對(duì)應(yīng)用戶的密鑰即可。但是,這樣相比于前兩種引入內(nèi)存數(shù)據(jù)庫(kù)帶來(lái)了危害更大,比如:1??如果服務(wù)是分布式的,則每次發(fā)出新的 token 時(shí)都必須在多臺(tái)機(jī)器同步密鑰。為此,你需要將必須將機(jī)密存儲(chǔ)在數(shù)據(jù)庫(kù)或其他外部服務(wù)中,這樣和 Session 認(rèn)證就沒太大區(qū)別了。2?? 如果用戶同時(shí)在兩個(gè)瀏覽器打開系統(tǒng),或者在手機(jī)端也打開了系統(tǒng),如果它從一個(gè)地方將賬號(hào)退出,那么其他地方都要重新進(jìn)行登錄,這是不可取的。
- 保持令牌的有效期限短并經(jīng)常輪換 :很簡(jiǎn)單的一種方式。但是,會(huì)導(dǎo)致用戶登錄狀態(tài)不會(huì)被持久記錄,而且需要用戶經(jīng)常登錄。
對(duì)于修改密碼后 token 還有效問題的解決還是比較容易的,說一種我覺得比較好的方式:使用用戶的密碼的哈希值對(duì) token 進(jìn)行簽名。因此,如果密碼更改,則任何先前的令牌將自動(dòng)無(wú)法驗(yàn)證。
5.2、token 的續(xù)簽問題
token 有效期一般都建議設(shè)置的不太長(zhǎng),那么 token 過期后如何認(rèn)證,如何實(shí)現(xiàn)動(dòng)態(tài)刷新 token,避免用戶經(jīng)常需要重新登錄?
我們先來(lái)看看在 Session 認(rèn)證中一般的做法:假如 session 的有效期30分鐘,如果 30 分鐘內(nèi)用戶有訪問,就把 session 有效期被延長(zhǎng)30分鐘。
- 類似于 Session 認(rèn)證中的做法:這種方案滿足于大部分場(chǎng)景。假設(shè)服務(wù)端給的 token 有效期設(shè)置為30分鐘,服務(wù)端每次進(jìn)行校驗(yàn)時(shí),如果發(fā)現(xiàn) token 的有效期馬上快過期了,服務(wù)端就重新生成 token 給客戶端??蛻舳嗣看握?qǐng)求都檢查新舊token,如果不一致,則更新本地的token。這種做法的問題是僅僅在快過期的時(shí)候請(qǐng)求才會(huì)更新 token ,對(duì)客戶端不是很友好。
- 每次請(qǐng)求都返回新 token :這種方案的的思路很簡(jiǎn)單,但是,很明顯,開銷會(huì)比較大。
- token 有效期設(shè)置到半夜 :這種方案是一種折衷的方案,保證了大部分用戶白天可以正常登錄,適用于對(duì)安全性要求不高的系統(tǒng)。
- 用戶登錄返回兩個(gè) token :第一個(gè)是 acessToken ,它的過期時(shí)間 token 本身的過期時(shí)間比如半個(gè)小時(shí),另外一個(gè)是 refreshToken 它的過期時(shí)間更長(zhǎng)一點(diǎn)比如為1天??蛻舳说卿浐?,將 accessToken和refreshToken 保存在本地,每次訪問將 accessToken 傳給服務(wù)端。服務(wù)端校驗(yàn) accessToken 的有效性,如果過期的話,就將 refreshToken 傳給服務(wù)端。如果有效,服務(wù)端就生成新的 accessToken 給客戶端。否則,客戶端就重新登錄即可。該方案的不足是:1??需要客戶端來(lái)配合;2??用戶注銷的時(shí)候需要同時(shí)保證兩個(gè) token 都無(wú)效;3??重新請(qǐng)求獲取 token 的過程中會(huì)有短暫 token 不可用的情況(可以通過在客戶端設(shè)置定時(shí)器,當(dāng)accessToken 快過期的時(shí)候,提前去通過 refreshToken 獲取新的accessToken)。
總結(jié)
JWT 最適合的場(chǎng)景是不需要服務(wù)端保存用戶狀態(tài)的場(chǎng)景,如果考慮到 token 注銷和 token 續(xù)簽的場(chǎng)景話,沒有特別好的解決方案,大部分解決方案都給 token 加上了狀態(tài),這就有點(diǎn)類似 Session 認(rèn)證了。
參考鏈接:
基于Token的WEB后臺(tái)認(rèn)證機(jī)制
Spring Boot實(shí)戰(zhàn):攔截器與過濾器
chain.doFilter(request,response)含義
到此這篇關(guān)于SpringBoot集成 JWT實(shí)現(xiàn)用戶登錄認(rèn)證的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)SpringBoot JWT用戶登錄認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot結(jié)合JWT實(shí)現(xiàn)用戶登錄、注冊(cè)、鑒權(quán)
- springBoot整合jwt實(shí)現(xiàn)token令牌認(rèn)證的示例代碼
- springboot中通過jwt令牌校驗(yàn)及前端token請(qǐng)求頭進(jìn)行登錄攔截實(shí)戰(zhàn)記錄
- SpringBoot整合JWT(JSON?Web?Token)生成token與驗(yàn)證的流程及示例
- SpringSecurity角色權(quán)限控制(SpringBoot+SpringSecurity+JWT)
- Springboot+jwt實(shí)現(xiàn)在線用戶功能(示例代碼)
相關(guān)文章
Springboot+mybatis-plus+注解實(shí)現(xiàn)數(shù)據(jù)權(quán)限隔離
本文將結(jié)合實(shí)例代碼,介紹Springboot+mybatis-plus+注解實(shí)現(xiàn)數(shù)據(jù)權(quán)限隔離,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07解決MyBatis-Plus使用動(dòng)態(tài)表名selectPage不生效的問題
這篇文章主要介紹了如惡化解決MyBatis-Plus使用動(dòng)態(tài)表名selectPage不生效的問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11java List循環(huán)與Map循環(huán)的總結(jié)
這篇文章主要介紹了java List循環(huán)與Map循環(huán)的總結(jié)的相關(guān)資料,并附代碼實(shí)例,幫助大家學(xué)習(xí)理解,需要的朋友可以參考下2016-11-11基于Java編寫一個(gè)限流工具類RateLimiter
這篇文章主要為大家詳細(xì)介紹了如何基于Java編寫一個(gè)限流工具類RateLimiter,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01SpringBoot+jsp項(xiàng)目啟動(dòng)出現(xiàn)404的解決方法
這篇文章主要介紹了SpringBoot+jsp項(xiàng)目啟動(dòng)出現(xiàn)404的解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2019-03-03springboot實(shí)現(xiàn)極驗(yàn)校驗(yàn)的項(xiàng)目實(shí)踐
在系統(tǒng)業(yè)務(wù)中,需要想客戶發(fā)送手機(jī)驗(yàn)證碼,進(jìn)行驗(yàn)證后,才能提交,本文主要介紹了springboot實(shí)現(xiàn)極驗(yàn)校驗(yàn)的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09Java實(shí)現(xiàn)數(shù)組反轉(zhuǎn)翻轉(zhuǎn)的方法實(shí)例
本篇文章主要介紹了Java實(shí)現(xiàn)數(shù)組反轉(zhuǎn)翻轉(zhuǎn)的方法實(shí)例,詳細(xì)的介紹了3種實(shí)現(xiàn)方法,有興趣的可以了解一下。2017-04-04