SpringBoot+vue+Axios實現(xiàn)Token令牌的詳細(xì)過程
認(rèn)識Token
對Token有了解可以跳過。
使用Token存儲用戶信息,認(rèn)證用戶。 Token是一個訪問系統(tǒng)的令牌(字符串)。Token 是在服務(wù)端產(chǎn)生的。前端可以使用用戶名/密碼向服務(wù)端請求認(rèn)證(登錄),服務(wù)端認(rèn)證成功,服務(wù)端會返回 Token 給前端。前端可以在每次請求的時候帶上 Token 證明自己的身份。服務(wù)器端在處理請求之前,先驗證Token,驗證通過,可以訪問系統(tǒng),執(zhí)行業(yè)務(wù)請求處理。
Token可以使用自己的算法自定義,比如給每個用戶分配一個UUID ,UUID值就看做是一個Token 。
Token統(tǒng)一的規(guī)范是JWT( json web token ) 。用json表示token。JWT定義了一種緊湊而獨立的方法,用于在各方之間安全地將信息作為JSON對象傳輸。
常使用的Token庫是 jjwt
JWT組成
JWT這是一個很長的字符串,中間用點(.
)分隔成三個部分。JWT 內(nèi)部是沒有換行的,一行數(shù)據(jù)。
JWT 的三個部分依次如下。
Header(頭部)
Payload(負(fù)載)
Signature(簽名)
形如:
eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTE5NzgzNDQsImlhdCI6MTY5MTk3Nzc0NCwianRpIjoiNDEyNjRhNTctYTY0ZC00Y2E5LTljMzMtY2I1MGJmNDc5YjEzIiwicm9sZSI6Iue7j-eQhiIsIm5hbWUiOiLmnY7lm5siLCJ1c2VySWQiOjEwMDF9.t4IrCIs8Y1zLwehouUugeAc-8PBlndpXz5xhibtQIgo
Header
Header: 是一個json對象,存儲元數(shù)據(jù)
{ "alg": "HS256", // alg:是簽名的算法名稱(algorithm),默認(rèn)是 HMAC SHA256(寫成 HS256); "typ": "JWT" //typ屬性表示這個令牌(token)的類型(type),JWT 令牌統(tǒng)一寫為JWT。 }
元數(shù)據(jù)的json對象使用base64URL編碼.
Payload
Payload :負(fù)載,是一個json對象。是存放傳遞的數(shù)據(jù) ,數(shù)據(jù)分為Public的和Private的。
Public是JWT中規(guī)定的一些字段,可以自己選擇使用。
- iss (issuer):簽發(fā)人
- exp (expiration time):過期時間
- sub (subject):該JWT所面向的用戶
- aud (audience):受眾,接收該JWT的一方
- nbf (Not Before):生效時間
- iat (Issued At):簽發(fā)時間
- jti (JWT ID):編號
Private是自己定義的字段
{ "role": "經(jīng)理", "name": "張凡", "id": 2345 }
Playload默認(rèn)是沒有加密的, 以上數(shù)據(jù)都明文傳輸?shù)?,所?strong>不要放敏感數(shù)據(jù).此部分?jǐn)?shù)據(jù)也是json對象使用base64URL編碼,是一個字符串
Signature
Signature:簽名。簽名是對Header和Payload兩部分的簽名,目的是防止數(shù)據(jù)被篡改
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
簽名算法:先指定一個 secret秘鑰, 把base64URL的header , base64URL的payload 和 secret秘鑰 使用 HMAC SHA256 生成簽名字符串
JWT簡單使用
首先用戶使用類似像用戶名,密碼登錄服務(wù)器,校驗用戶身份,服務(wù)器返回jwt token
客戶端獲取 jwt token , 使用cookie或者localStorage存儲jwt token,但這樣不能跨域, 常用的方式:
放在請求header 的 Authorization中 Authorization: Bearer
也可以放在 get 或者 post請求的參數(shù)中
客戶端訪問其他api接口, 傳遞token給服務(wù)器, 服務(wù)器認(rèn)證token后,返回給請求的數(shù)據(jù)
創(chuàng)建JWT
@Test public void testCreateJwt(){ String key = "7d8e742c14674758b9162892eec9c59c";// 32位、全局唯一 // 創(chuàng)建secereKey SecretKey secretKey = Keys.hmacShaKeyFor(key.getBytes(StandardCharsets.UTF_8)); // 存儲用戶數(shù)據(jù) Map<String,Object> map = new HashMap(); map.put("userId",1001); map.put("name","李四"); map.put("role","經(jīng)理"); // 獲取簽發(fā)時間 Date curDate = new Date(); // 創(chuàng)建jwt String jwt = Jwts.builder().signWith(secretKey, SignatureAlgorithm.HS256) // Header .setExpiration(DateUtils.addMinutes(curDate, 10)) // 設(shè)置10分鐘后過期 .setIssuedAt(curDate) // 設(shè)置簽發(fā)時間 .setId(UUID.randomUUID().toString()) // Token唯一編號 .addClaims(map).compact(); // 添加有關(guān)用戶或其他實體的聲明信息 System.out.println("jwt = " + jwt); // eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTE5NzgzNDQsImlhdCI6MTY5MTk3Nzc0NCwianRpIjoiNDEyNjRhNTctYTY0ZC00Y2E5LTljMzMtY2I1MGJmNDc5YjEzIiwicm9sZSI6Iue7j-eQhiIsIm5hbWUiOiLmnY7lm5siLCJ1c2VySWQiOjEwMDF9.t4IrCIs8Y1zLwehouUugeAc-8PBlndpXz5xhibtQIgo }
解析JWT
@Test public void testReadJwt(){ String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTE5Nzk4MTMsImlhdCI6MTY5MTk3OTIxMywianRpIjoiYzAyYzRiMzMtODAzZS00MDk1LWI0NTctZjVmY2NkYTMyOTU2Iiwicm9sZSI6Iue7j-eQhiIsIm5hbWUiOiLmnY7lm5siLCJ1c2VySWQiOjEwMDF9.vUsmvOF-rv6XVCzuNgJZCGBohzDeROUFUf-c3iRv6NA"; String key = "7d8e742c14674758b9162892eec9c59c";// 加密用的32位key // 創(chuàng)建secretKey SecretKey secretKey = Keys.hmacShaKeyFor(key.getBytes(StandardCharsets.UTF_8)); // 解析Jwt 沒有異常 解析成功 Jws<Claims> claims = Jwts.parserBuilder() // 1.獲取Buider對象 .setSigningKey(secretKey) // 2.設(shè)置key .build() // 3.獲取Parser .parseClaimsJws(jwt); // 4.解析數(shù)據(jù) // 讀數(shù)據(jù) Claims body = claims.getBody(); Integer userId = body.get("userId",Integer.class); System.out.println("userId = " + userId); Object uId = body.get("userId"); System.out.println("uId = " + uId); Object name = body.get("name"); if(name != null){ System.out.println("name = " + name); } String jwtId = body.getId(); System.out.println("jwtId = " + jwtId); Date expiration = body.getExpiration(); System.out.println("expiration = " + expiration); }
常見異常
ClaimJwtException 獲取Claim異常 ExpiredJwtException token過期異常 IncorrectClaimException token無效 MalformedJwtException 密鑰驗證不一致 MissingClaimException JWT無效 RequiredTypeException 必要類型異常 SignatureException 簽名異常 UnsupportedJwtException 不支持JWT異常
后端
Maven依賴
<!--jwt--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred --> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
封裝JWT工具
public class JwtUtile { // 全局唯一、亂序的32位字符串。存儲在配置文件中 private String selfKey; public JwtUtile(String selfKey) { this.selfKey = selfKey; } // 創(chuàng)建Token public String creatJwt(Map<String,Object> data,Integer minute) throws Exception{ Date cruDate = new Date(); SecretKey secretKey = Keys.hmacShaKeyFor(selfKey.getBytes(StandardCharsets.UTF_8)); String jwt = Jwts.builder().signWith(secretKey, SignatureAlgorithm.HS256) .setExpiration(DateUtils.addMinutes(cruDate, minute)) .setIssuedAt(cruDate) .setId(UUID.randomUUID().toString().replaceAll("-", "").toUpperCase()) .addClaims(data) .compact(); return jwt; } // 讀取Jwt public Claims readJWT(String jwt) throws Exception{ SecretKey secretKey = Keys.hmacShaKeyFor(selfKey.getBytes(StandardCharsets.UTF_8)); Claims body = Jwts.parserBuilder().setSigningKey(secretKey) .build().parseClaimsJws(jwt).getBody(); return body; } }
獲取并響應(yīng)Token
@PostMapping("/login") public RespResult userLogin(@RequestParam String phone, @RequestParam String pword, @RequestParam String scode) throws Exception{ RespResult result = RespResult.fail(); if (CommonUtil.checkPhone(phone) && (pword != null || pword.length() == 32)) { // 檢查驗證碼是否正確 if (loginSmsService.checkSmsCode(phone, scode)) { // 查找是否存賬號密碼匹配用戶 User user = userService.userLogin(phone, pword); if (user != null) { // 登錄成功 , 生成Token Map<String ,Object> data = new HashMap<>(); data.put("uid",user.getId());// 設(shè)置荷載位uid // 設(shè)置120分鐘過期,并獲取Token String jwtToken = jwtUtile.creatJwt(data, 120); result = RespResult.ok(); // 響應(yīng)Token result.setAccessToken(jwtToken); Map<String,Object> userInfo = new HashMap<>(); userInfo.put("uid",user.getId()); userInfo.put("phone",user.getPhone()); userInfo.put("name",user.getName()); result.setData(userInfo); }else { result.setRCode(RCode.PHONE_LOGIN_PASSWORD_INVALID); } } else { result.setRCode(RCode.SMS_CODE_INVALID); } } else { result.setRCode(RCode.REQUEST_PARAM_ERR); } return result; }
攔截器驗證Token
public class TokenInterceptor implements HandlerInterceptor { private String secret = ""; public TokenInterceptor(String secret) { this.secret = secret; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1 如果是OPTIONS ,放行 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } boolean requestSend = false; try { // 請求頭中的 uid String headerUid = request.getHeader("uid"); // 2 獲取Token ,驗證 String headToken = request.getHeader("Authorization"); if (StringUtils.isNotBlank(headToken)) { // Bearer。。 Authorization中 Authorization: Bearer <token> String jwt = headToken.substring(7); // 讀jwt JwtUtile jwtUtile = new JwtUtile(secret); Claims claims = jwtUtile.readJWT(jwt); // 獲取 Jwt中的uid Integer jwtUid = claims.get("uid", Integer.class); // 對比 headerUid 和 JwtUid if (headerUid.equals(String.valueOf(jwtUid))) { // token 和發(fā)起請求的用戶是同一個, 請求可以被處理 requestSend = true; } } } catch (Exception e) { requestSend = false; } // token 沒有通過,需要給vue錯誤提示 if (requestSend == false) { // 返回失敗的JSON數(shù)據(jù)給前端 RespResult result = RespResult.fail(); result.setRCode(RCode.TOKEN_INVALID); // 使用HTTPServletResponse 輸出JSON String respJson = JSONObject.toJSONString(result); response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); out.print(respJson); out.flush(); out.close(); } return requestSend; } }
前端
登錄+存儲Token
userLogin(){ this.checkPhone(); this.checkPassword(); this.checkCode(); if( this.phoneErr == '' && this.passwordErr == '' & this.codeErr==''){ //發(fā)起登錄請求 let param = { phone:this.phone,pword:md5(this.password),scode:this.code } doPost('/v1/user/login',param).then(resp=>{ if( resp && resp.data.code == 1000){ //登錄成功,存儲數(shù)據(jù)到localStorage,存的是字符串 window.localStorage.setItem("token",resp.data.accessToken); //把 json對象轉(zhuǎn)為 string window.localStorage.setItem("userinfo", JSON.stringify(resp.data.data)); //登錄之后,如果name沒有值,需要進(jìn)入到實名認(rèn)證頁面 //如果name有值,進(jìn)入到用戶中心 if(resp.data.data.name == ''){ //實名認(rèn)證 this.$router.push({ path:'/page/user/realname' }) } else { //用戶中心 this.$router.push({ path:'/page/user/usercenter' }) } } }) } }
前端攔截器
import axios from 'axios' //創(chuàng)建攔截器 axios.interceptors.request.use(function (config) { // 在需要用戶登錄后的操作,在請求的url中加入token // 判斷訪問服務(wù)器的url地址, 需要提供身份信息,加入token let storageToken = window.localStorage.getItem("token"); let userinfo = window.localStorage.getItem("userinfo"); if (storageToken && userinfo) { // 添加需要Token驗證的url if (config.url == '/v1/user/realname' || config.url == '/v1/user/usercenter' || config.url == '/v1/recharge/records' || config.url=='/v1/invest/product') { //在header中傳遞token 和一個userId config.headers['Authorization'] = 'Bearer ' + storageToken; config.headers['uid'] = JSON.parse(userinfo).uid; } } return config; }, function (err) { console.log("請求錯誤" + err); }) //創(chuàng)建應(yīng)答攔截器,統(tǒng)一對錯誤處理, 后端返回 code > 1000 都是錯誤 axios.interceptors.response.use(function (resp) { if (resp && resp.data.code > 1000) { let code = resp.data.code; if (code == 3000) { //token無效,重新登錄 window.location.href = '/page/user/login'; } else { layx.msg(resp.data.msg, {dialogIcon: 'warn', position: 'ct'}); } } return resp; }, function (err) { console.log("應(yīng)答攔截器錯誤:" + err) //回到首頁 window.location.href = '/'; })
其他請求正常請求就行
axios.get("/v1/user/usercenter").then(resp => { if (resp && resp.data.code == 1000) { this.userBaseInfo = resp.data.data; } });
到此這篇關(guān)于SpringBoot+vue+Axios實現(xiàn)Token令牌的文章就介紹到這了,更多相關(guān)SpringBoot vue實現(xiàn)Token令牌內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot啟動后執(zhí)行方法的五種實現(xiàn)方式
本文介紹了SpringBoot中五種在項目啟動后執(zhí)行方法的方式,包括實現(xiàn)CommandLineRunner和ApplicationRunner接口、實現(xiàn)ApplicationListener接口、使用@PostConstruct注解以及實現(xiàn)InitializingBean接口,每種方式都有其特點和適用場景2025-02-02SpringBoot-RestTemplate實現(xiàn)調(diào)用第三方API的方式
RestTemplate?是由?Spring?提供的一個?HTTP?請求工具,它提供了常見的REST請求方案的模版,例如?GET?請求、POST?請求、PUT?請求、DELETE?請求以及一些通用的請求執(zhí)行方法?exchange?以及?execute,下面看下SpringBoot?RestTemplate調(diào)用第三方API的方式2022-12-12