SpringBoot+vue+Axios實(shí)現(xiàn)Token令牌的詳細(xì)過(guò)程
認(rèn)識(shí)Token
對(duì)Token有了解可以跳過(guò)。
使用Token存儲(chǔ)用戶信息,認(rèn)證用戶。 Token是一個(gè)訪問(wèn)系統(tǒng)的令牌(字符串)。Token 是在服務(wù)端產(chǎn)生的。前端可以使用用戶名/密碼向服務(wù)端請(qǐng)求認(rèn)證(登錄),服務(wù)端認(rèn)證成功,服務(wù)端會(huì)返回 Token 給前端。前端可以在每次請(qǐng)求的時(shí)候帶上 Token 證明自己的身份。服務(wù)器端在處理請(qǐng)求之前,先驗(yàn)證Token,驗(yàn)證通過(guò),可以訪問(wèn)系統(tǒng),執(zhí)行業(yè)務(wù)請(qǐng)求處理。
Token可以使用自己的算法自定義,比如給每個(gè)用戶分配一個(gè)UUID ,UUID值就看做是一個(gè)Token 。
Token統(tǒng)一的規(guī)范是JWT( json web token ) 。用json表示token。JWT定義了一種緊湊而獨(dú)立的方法,用于在各方之間安全地將信息作為JSON對(duì)象傳輸。
常使用的Token庫(kù)是 jjwt

JWT組成
JWT這是一個(gè)很長(zhǎng)的字符串,中間用點(diǎn)(.)分隔成三個(gè)部分。JWT 內(nèi)部是沒(méi)有換行的,一行數(shù)據(jù)。
JWT 的三個(gè)部分依次如下。
Header(頭部)
Payload(負(fù)載)
Signature(簽名)
形如:
eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTE5NzgzNDQsImlhdCI6MTY5MTk3Nzc0NCwianRpIjoiNDEyNjRhNTctYTY0ZC00Y2E5LTljMzMtY2I1MGJmNDc5YjEzIiwicm9sZSI6Iue7j-eQhiIsIm5hbWUiOiLmnY7lm5siLCJ1c2VySWQiOjEwMDF9.t4IrCIs8Y1zLwehouUugeAc-8PBlndpXz5xhibtQIgo
Header
Header: 是一個(gè)json對(duì)象,存儲(chǔ)元數(shù)據(jù)
{
"alg": "HS256", // alg:是簽名的算法名稱(algorithm),默認(rèn)是 HMAC SHA256(寫(xiě)成 HS256);
"typ": "JWT" //typ屬性表示這個(gè)令牌(token)的類型(type),JWT 令牌統(tǒng)一寫(xiě)為JWT。
}
元數(shù)據(jù)的json對(duì)象使用base64URL編碼.
Payload
Payload :負(fù)載,是一個(gè)json對(duì)象。是存放傳遞的數(shù)據(jù) ,數(shù)據(jù)分為Public的和Private的。
Public是JWT中規(guī)定的一些字段,可以自己選擇使用。
- iss (issuer):簽發(fā)人
- exp (expiration time):過(guò)期時(shí)間
- sub (subject):該JWT所面向的用戶
- aud (audience):受眾,接收該JWT的一方
- nbf (Not Before):生效時(shí)間
- iat (Issued At):簽發(fā)時(shí)間
- jti (JWT ID):編號(hào)
Private是自己定義的字段
{
"role": "經(jīng)理",
"name": "張凡",
"id": 2345
}
Playload默認(rèn)是沒(méi)有加密的, 以上數(shù)據(jù)都明文傳輸?shù)模?strong>不要放敏感數(shù)據(jù).此部分?jǐn)?shù)據(jù)也是json對(duì)象使用base64URL編碼,是一個(gè)字符串
Signature
Signature:簽名。簽名是對(duì)Header和Payload兩部分的簽名,目的是防止數(shù)據(jù)被篡改
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
簽名算法:先指定一個(gè) secret秘鑰, 把base64URL的header , base64URL的payload 和 secret秘鑰 使用 HMAC SHA256 生成簽名字符串
JWT簡(jiǎn)單使用
首先用戶使用類似像用戶名,密碼登錄服務(wù)器,校驗(yàn)用戶身份,服務(wù)器返回jwt token
客戶端獲取 jwt token , 使用cookie或者localStorage存儲(chǔ)jwt token,但這樣不能跨域, 常用的方式:
放在請(qǐng)求header 的 Authorization中 Authorization: Bearer
也可以放在 get 或者 post請(qǐng)求的參數(shù)中
客戶端訪問(wèn)其他api接口, 傳遞token給服務(wù)器, 服務(wù)器認(rèn)證token后,返回給請(qǐng)求的數(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));
// 存儲(chǔ)用戶數(shù)據(jù)
Map<String,Object> map = new HashMap();
map.put("userId",1001);
map.put("name","李四");
map.put("role","經(jīng)理");
// 獲取簽發(fā)時(shí)間
Date curDate = new Date();
// 創(chuàng)建jwt
String jwt = Jwts.builder().signWith(secretKey, SignatureAlgorithm.HS256) // Header
.setExpiration(DateUtils.addMinutes(curDate, 10)) // 設(shè)置10分鐘后過(guò)期
.setIssuedAt(curDate) // 設(shè)置簽發(fā)時(shí)間
.setId(UUID.randomUUID().toString()) // Token唯一編號(hào)
.addClaims(map).compact(); // 添加有關(guān)用戶或其他實(shí)體的聲明信息
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 沒(méi)有異常 解析成功
Jws<Claims> claims = Jwts.parserBuilder() // 1.獲取Buider對(duì)象
.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);
}
常見(jiàn)異常
ClaimJwtException 獲取Claim異常 ExpiredJwtException token過(guò)期異常 IncorrectClaimException token無(wú)效 MalformedJwtException 密鑰驗(yàn)證不一致 MissingClaimException JWT無(wú)效 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位字符串。存儲(chǔ)在配置文件中
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)) {
// 檢查驗(yàn)證碼是否正確
if (loginSmsService.checkSmsCode(phone, scode)) {
// 查找是否存賬號(hào)密碼匹配用戶
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分鐘過(guò)期,并獲取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;
}
攔截器驗(yàn)證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 {
// 請(qǐng)求頭中的 uid
String headerUid = request.getHeader("uid");
// 2 獲取Token ,驗(yàn)證
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);
// 對(duì)比 headerUid 和 JwtUid
if (headerUid.equals(String.valueOf(jwtUid))) {
// token 和發(fā)起請(qǐng)求的用戶是同一個(gè), 請(qǐng)求可以被處理
requestSend = true;
}
}
} catch (Exception e) {
requestSend = false;
}
// token 沒(méi)有通過(guò),需要給vue錯(cuò)誤提示
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;
}
}
前端
登錄+存儲(chǔ)Token
userLogin(){
this.checkPhone();
this.checkPassword();
this.checkCode();
if( this.phoneErr == '' && this.passwordErr == '' & this.codeErr==''){
//發(fā)起登錄請(qǐng)求
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){
//登錄成功,存儲(chǔ)數(shù)據(jù)到localStorage,存的是字符串
window.localStorage.setItem("token",resp.data.accessToken);
//把 json對(duì)象轉(zhuǎn)為 string
window.localStorage.setItem("userinfo", JSON.stringify(resp.data.data));
//登錄之后,如果name沒(méi)有值,需要進(jìn)入到實(shí)名認(rèn)證頁(yè)面
//如果name有值,進(jìn)入到用戶中心
if(resp.data.data.name == ''){
//實(shí)名認(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) {
// 在需要用戶登錄后的操作,在請(qǐng)求的url中加入token
// 判斷訪問(wèn)服務(wù)器的url地址, 需要提供身份信息,加入token
let storageToken = window.localStorage.getItem("token");
let userinfo = window.localStorage.getItem("userinfo");
if (storageToken && userinfo) {
// 添加需要Token驗(yàn)證的url
if (config.url == '/v1/user/realname' || config.url == '/v1/user/usercenter' ||
config.url == '/v1/recharge/records' || config.url=='/v1/invest/product') {
//在header中傳遞token 和一個(gè)userId
config.headers['Authorization'] = 'Bearer ' + storageToken;
config.headers['uid'] = JSON.parse(userinfo).uid;
}
}
return config;
}, function (err) {
console.log("請(qǐng)求錯(cuò)誤" + err);
})
//創(chuàng)建應(yīng)答攔截器,統(tǒng)一對(duì)錯(cuò)誤處理, 后端返回 code > 1000 都是錯(cuò)誤
axios.interceptors.response.use(function (resp) {
if (resp && resp.data.code > 1000) {
let code = resp.data.code;
if (code == 3000) {
//token無(wú)效,重新登錄
window.location.href = '/page/user/login';
} else {
layx.msg(resp.data.msg, {dialogIcon: 'warn', position: 'ct'});
}
}
return resp;
}, function (err) {
console.log("應(yīng)答攔截器錯(cuò)誤:" + err)
//回到首頁(yè)
window.location.href = '/';
})
其他請(qǐng)求正常請(qǐng)求就行
axios.get("/v1/user/usercenter").then(resp => {
if (resp && resp.data.code == 1000) {
this.userBaseInfo = resp.data.data;
}
});到此這篇關(guān)于SpringBoot+vue+Axios實(shí)現(xiàn)Token令牌的文章就介紹到這了,更多相關(guān)SpringBoot vue實(shí)現(xiàn)Token令牌內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring及Mybatis整合占位符解析失敗問(wèn)題解決
這篇文章主要介紹了Spring及Mybatis整合占位符解析失敗問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
SpringBoot啟動(dòng)后執(zhí)行方法的五種實(shí)現(xiàn)方式
本文介紹了SpringBoot中五種在項(xiàng)目啟動(dòng)后執(zhí)行方法的方式,包括實(shí)現(xiàn)CommandLineRunner和ApplicationRunner接口、實(shí)現(xiàn)ApplicationListener接口、使用@PostConstruct注解以及實(shí)現(xiàn)InitializingBean接口,每種方式都有其特點(diǎn)和適用場(chǎng)景2025-02-02
SpringBoot-RestTemplate實(shí)現(xiàn)調(diào)用第三方API的方式
RestTemplate?是由?Spring?提供的一個(gè)?HTTP?請(qǐng)求工具,它提供了常見(jiàn)的REST請(qǐng)求方案的模版,例如?GET?請(qǐng)求、POST?請(qǐng)求、PUT?請(qǐng)求、DELETE?請(qǐng)求以及一些通用的請(qǐng)求執(zhí)行方法?exchange?以及?execute,下面看下SpringBoot?RestTemplate調(diào)用第三方API的方式2022-12-12
詳解Spring Boot 項(xiàng)目中的 parent
這篇文章主要介紹了Spring Boot中parent作用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
詳解Java實(shí)現(xiàn)負(fù)載均衡的幾種算法代碼
本篇文章主要介紹了詳解Java實(shí)現(xiàn)負(fù)載均衡的幾種算法代碼 ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02

