SpringBoot如何集成Token
SpringBoot集成Token
簡(jiǎn)介
在項(xiàng)目開(kāi)發(fā)中,Token 是常見(jiàn)且重要的一個(gè)功能,目前對(duì)于 Token 設(shè)計(jì)有很多成熟的方案;
比如使用 Redis 存儲(chǔ)管理 Token,不過(guò)這種方式需要而額外集成 Redis 服務(wù),雖然 Redis 查詢效率很高,但是對(duì)于普通項(xiàng)目來(lái)說(shuō),還是增加了開(kāi)發(fā)難度;
本章將介紹一個(gè)簡(jiǎn)單易用的 Token 插件:jjwt,該插件直接與 SpringBoot 集成即可,其原理是:在服務(wù)端加密生成一個(gè)三段式加密字符串,前端每次請(qǐng)求都要將該 Token 傳遞給服務(wù)端(建議放在 Header 中),后臺(tái)通過(guò)解析該 Token 字符串,判斷其是否合法,超時(shí)等
基本原理
從這個(gè)架構(gòu)圖中可以看出 JWT 主體分為 3 個(gè)部分:user,application server,authentication server;
非常常見(jiàn)的一個(gè)架構(gòu),首先用戶需要 通過(guò)登錄等手段向 authentication server 發(fā)送一個(gè)認(rèn)證請(qǐng)求,authentication會(huì)返回給用戶一個(gè) JWT (這個(gè)JWT 的具體內(nèi)容格式是啥后面會(huì)說(shuō),先理解成一個(gè)簡(jiǎn)單的字符串好了)
此后用戶向application server發(fā)送的所有請(qǐng)求都要捎帶上這個(gè) JWT,然后application server 會(huì)驗(yàn)證這個(gè) JWT 的合法性,驗(yàn)證通過(guò)則說(shuō)明用戶請(qǐng)求時(shí)來(lái)自合法守信的客戶端
JWT 結(jié)構(gòu)
這個(gè) JWT 的格式,就是一個(gè)由三部分組成的字符串:header.payload.signatue;
其中 header 主要包含了加密算法等信息;
payload 則主要包含后端服務(wù)器放入的自定義信息,如:登錄用戶的信息;signature 就是使用算法生成的能夠?qū)崿F(xiàn)身份認(rèn)證的字符串
實(shí)現(xiàn)步驟
1. 在項(xiàng)目的 pom.xml 配置文件中添加如下依賴
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
2. 添加一個(gè)公共類 Token,提供生成 Token,校驗(yàn)等基本方法
/** * @ClassName Token * @Author Andy * @Date 2020/7/14 14:32 * @Description 用于生成, 解析 token 的工具類 **/ public class Token { // 密鑰 private static SecretKeySpec key = ""; // 對(duì)密鑰加密 static { key = new SecretKeySpec(Constant.SECRET_KEY.getBytes(), SignatureAlgorithm.HS512.getJcaName()); } // 生成 token public static String createToken(String subject, Map < String, Object > claims, Date expireDate) { JwtBuilder builder = Jwts.builder() .setClaims(claims) // payload 私有申明,存放一些個(gè)人信息,必須放在第一個(gè) .setIssuer(Constant.AUTHOR) // token 的簽發(fā)人 .setIssuedAt(new Date()) // token 的簽發(fā)時(shí)間 .setSubject(subject) // token 的所有人, 一般放用戶的 id 之類的 .setExpiration(expireDate) // 過(guò)期時(shí)間 .signWith(SignatureAlgorithm.HS512, key); // token 的簽名算法 return builder.compact(); } // 生成 token public static String createToken(String subject, Date expireDate) { return createToken(subject, new HashMap < > (), expireDate); } // 解析 token public static Claims parseToken(String token) { try { return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); } catch (Exception e) { throw new CommonException(Exceptions.TOKEN_PARSE_ERROR); } } // 將 token 標(biāo)記為過(guò)期 public static void markTokenExpired(String token) { Date expireDate = CommonUtil.currentTimeAddSeconds(1); Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().setExpiration(expireDate); } // 判斷 token 是否過(guò)期 public static boolean checkTokenExpired(String token) { Claims claims = parseToken(token); return !CommonUtil.checkExpired(claims.getExpiration()); } // 獲取 token 的 subject 信息, 登錄成功保存的時(shí)用戶的主鍵 public static String getSubject(HttpServletRequest request) { return parseToken(getToken(request)).getSubject(); } // 根據(jù) token 獲取 subject 信息 public static String getSubject(String token) { return parseToken(token).getSubject(); } // 根據(jù) HttpServletRequest 獲取 token public static String getToken(HttpServletRequest request) { return request.getHeader(Constant.HEADER_TOKEN); } // 獲取 token 的簽發(fā)人 public static String getIssuer(String token) { return parseToken(token).getIssuer(); } }
這里使用到其它關(guān)聯(lián)類的方法或?qū)傩匀缦?/p>
- Constant.class
// token 簽發(fā)者 public static final String AUTHOR = "duzimei"; // 獲取 header 中 token 標(biāo)識(shí) public static final String HEADER_TOKEN = "token"; // token 密鑰加密字符串(取自 《肖申克的救贖》經(jīng)典臺(tái)詞) public static final String SECRET_KEY = "Fear can hold you prisoner.Hope can set you free"; // 設(shè)置 token 的過(guò)期時(shí)間為 1 個(gè)小時(shí)(單位秒) public static final Integer EXPIRE_DATE = 3600;
- CommonUtil.class
// 在當(dāng)前時(shí)間基礎(chǔ)上加 seconds 秒 public static Date currentTimeAddSeconds(int seconds) { Calendar now = Calendar.getInstance(); now.add(Calendar.SECOND, seconds); return now.getTime(); } // 判斷于當(dāng)前日期是否過(guò)期 public static boolean checkExpired(Date expiration) { return expiration.after(new Date()); }
異常定義請(qǐng)忽略,根據(jù)自己項(xiàng)目而實(shí)現(xiàn)
3. 定義一個(gè) UserController.class,添加登錄方法,當(dāng)用戶登錄成功后,返回一個(gè) Token
@RestController @RequestMapping("/user") public class User { @Autowired private UserService userService; @PostMapping("/login") public Map<Integer, String> login(@RequestBody JSONObject params) { Map<Integer, String> result = new HashMap<>(); String userAccount = params.getString("account"); String password = params.getString("password"); User tempUser = userService.getUserByAccount(userAccount); if(null == tempUser) { result.put(-1, "用戶不存在"); } else if(!password.equals(tempUser.getPassword())) { result.put("-2", "密碼不正確"); } else { // 用戶登錄成功, 添加 token 返回給客戶端 // 1. 設(shè)置 token 過(guò)期時(shí)間 Date expireDate = CommonUtil.currentTimeAddSeconds(Contant.EXPIRE_DATE); // 2. 這里我們把用戶的 id 信息放到 subject 中 String token = Token.createToken(tempUser.getUserId(), expireDate); result.put(0, token); } return result; } }
4. 前端當(dāng)用戶登錄成功后,向后臺(tái)發(fā)送其它請(qǐng)求的時(shí)候,將 Token 放到 Header 中一起傳送給后臺(tái)
$.ajax({ headers: { "token": 從后臺(tái)獲取到的token }, url: "http://xxx:8080/user/info", method: "GET", data: null, dataType: "json", contentType: "application/json", success: function(data) { console.log(data); }, error: function(x, s, e) { console.log("異常信息: " + x.responseText); } })
5.解析Token
在后臺(tái)對(duì)應(yīng)方法中,就可以通過(guò)解析 Token 獲取用戶 id,判斷請(qǐng)求是否合法;
像下面這種請(qǐng)求,前端根本不用傳遞用戶的 id 到后臺(tái),后臺(tái)通過(guò)解析 Token,獲取 Token 的 subject 屬性就能拿到用戶 id,在一定程度上提高了安全性
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/info") public Map<Integer, Object> getUserInfo(HttpServletRequest request) { Map<Integer, Object> result = new HashMap<>(); String token = Token.getToken(request); // 對(duì) token 進(jìn)行校驗(yàn) if(StringUtils.isEmpty(token)) { // 如果沒(méi)有獲取到 Token result.put(-1, "沒(méi)有獲取到 Token, 請(qǐng)求被阻止"); } else if(!Constant.AUTHOR.equals(Token.getIssuer(token))) { // 如果 Token 的簽發(fā)人不正確 result.put(-2, "非法的 Token, 請(qǐng)求被阻止"); } else if(Token.checkTokenExpired(token)) { // 如果 Token 已過(guò)期 result.put(-3, "過(guò)期的 Token, 請(qǐng)求被阻止"); } else { String userId = Token.getSubject(token); User user = userService.getUserById(userId); result.put(0, user); } return result; } }
這里只是介紹的關(guān)于 JWT 實(shí)現(xiàn) Token 的簡(jiǎn)單用法,在實(shí)際開(kāi)發(fā)過(guò)程中,建議使用 Spring 的 AOP 技術(shù)實(shí)現(xiàn)對(duì) Token 的校驗(yàn);
我們這里只是實(shí)現(xiàn)了一個(gè)最簡(jiǎn)單的 Token,這 Token 中還可以放入其它信息,由于是單向加密的,所以數(shù)據(jù)傳輸非常安全;
更進(jìn)一步的實(shí)現(xiàn),應(yīng)根據(jù)實(shí)際開(kāi)發(fā)具體實(shí)現(xiàn)
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Maven訪問(wèn)倉(cāng)庫(kù)順序代碼實(shí)例解析
這篇文章主要介紹了Maven訪問(wèn)倉(cāng)庫(kù)順序?qū)嵗馕?文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08springboot項(xiàng)目配置logback-spring.xml實(shí)現(xiàn)按日期歸檔日志的方法
本文主要介紹了springboot項(xiàng)目配置logback-spring.xml實(shí)現(xiàn)按日期歸檔日志的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08Springboot使用Redis中ZSetOperations實(shí)現(xiàn)博客訪問(wèn)量
在日常的網(wǎng)站使用中,經(jīng)常會(huì)碰到頁(yè)面的訪問(wèn)量,本文主要介紹了Springboot使用Redis中ZSetOperations實(shí)現(xiàn)博客訪問(wèn)量,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01SpringCache 分布式緩存的實(shí)現(xiàn)方法(規(guī)避redis解鎖的問(wèn)題)
這篇文章主要介紹了SpringCache 分布式緩存的實(shí)現(xiàn)方法(規(guī)避redis解鎖的問(wèn)題),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11PowerJob的QueryConvertUtils工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的QueryConvertUtils工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01IDEA:Git stash 暫存分支修改的實(shí)現(xiàn)代碼
這篇文章主要介紹了IDEA:Git stash 暫存分支修改的實(shí)現(xiàn)代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03SpringBoot使用ResponseBodyEmitter處理流式日志和進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何使用ResponseBodyEmitter處理流式日志和進(jìn)度條,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-02-02Java面試題沖刺第二十六天--實(shí)戰(zhàn)編程
這篇文章主要為大家分享了最有價(jià)值的三道java實(shí)戰(zhàn)面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下2021-08-08