技能學(xué)習(xí):學(xué)習(xí)使用golang(gin框架) + vue.js,開發(fā)前端全棧網(wǎng)站-9.登...
技能學(xué)習(xí):學(xué)習(xí)使用golang(gin框架) + vue.js,開發(fā)前端全棧網(wǎng)站-9.登錄功能(三):登錄的token驗(yàn)證
相關(guān)文章:
技能學(xué)習(xí):學(xué)習(xí)使用golang(gin框架) + vue.js,開發(fā)前端全棧網(wǎng)站-1.工具和本地環(huán)境
…
技能學(xué)習(xí):學(xué)習(xí)使用golang(gin框架) + vue.js,開發(fā)前端全棧網(wǎng)站-8.模型的關(guān)聯(lián)——無(wú)限層級(jí)分類
技能學(xué)習(xí):學(xué)習(xí)使用golang(gin框架) + vue.js,開發(fā)前端全棧網(wǎng)站-9.登錄功能(一):管理員功能的實(shí)現(xiàn)
技能學(xué)習(xí):學(xué)習(xí)使用golang(gin框架) + vue.js,開發(fā)前端全棧網(wǎng)站-9.登錄功能(二):用戶登錄和密碼驗(yàn)證
技能學(xué)習(xí):學(xué)習(xí)使用golang(gin框架) + vue.js,開發(fā)前端全棧網(wǎng)站-9.登錄功能(三):登錄的token驗(yàn)證
技能學(xué)習(xí):學(xué)習(xí)使用golang(gin框架) + vue.js,開發(fā)前端全棧網(wǎng)站-10.生產(chǎn)環(huán)境編譯
…
1.生成token
go語(yǔ)言中生成token的方法被封裝在了jwt中,gin框架使用jwt又進(jìn)行了進(jìn)一步封裝,在"github.com/dgrijalva/jwt-go"里。
(1)安裝jwt包:jwt-go
go get github.com/dgrijalva/jwt-go

(2)編寫生成token方法
// 定義一個(gè)全局token密鑰,token密鑰隨意定義一串字符串
var jwtkey = []byte("lskjdghfhkagflkagh")
// 定義字符串格式token,方便之后token的轉(zhuǎn)化
var tokenString string
// 定義一個(gè)token模型,用于存放token信息,識(shí)別不同賬號(hào)
type claims struct {
UserId string
jwt.StandardClaims
}
// 生成token
// 傳入字符串格式用戶id,輸出字符串格式的token和錯(cuò)誤值
func setToken(id string) (tokenString string, err error) {
//
// 定義token過期時(shí)間,一天后過期
expireTime := time.Now().Add(1 * 24 * time.Hour)
claims := &claims{
UserId: id,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(), // 過期時(shí)間,用上方定義的過期時(shí)間
IssuedAt: time.Now().Unix(), // 生效時(shí)間,生成token的這一刻起
Issuer: "127.0.0.1", // 生成者,本域名
Subject: "user token", // 生成主題
},
}
// 生成token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 根據(jù)之前定義的密鑰,將token轉(zhuǎn)化為加密字符串
tokenString, err = token.SignedString(jwtkey)
// 輸出
return
}

(2)編輯登錄接口方法,在其中使用token方法獲取token值
編譯啟動(dòng),測(cè)試:
成功獲取token值。
(3)最后將token值保存到前端本地儲(chǔ)存空間中,用于驗(yàn)證登錄狀態(tài):
Login.vue:
async login() {
const res = await this.$http.post('login', this.model)
console.log(res)
// 存儲(chǔ)token值
// sessionStorage是頁(yè)面緩存存儲(chǔ),關(guān)閉頁(yè)面后token值被清除,每次進(jìn)入頁(yè)面都需要進(jìn)行賬號(hào)登陸
// localStorage是本地存儲(chǔ),關(guān)閉頁(yè)面后token值不會(huì)被清除,只要不清理瀏覽器緩存就無(wú)需再次進(jìn)行登錄操作
localStorage.token = res.data.token
// 登錄成功跳轉(zhuǎn)到網(wǎng)站首頁(yè)
this.$router.push('/')
// 使用vue效果在頁(yè)面展示結(jié)果
this.$message({
type: 'success',
message: '登陸成功'
})
}

保存測(cè)試,跳轉(zhuǎn)成功:
查找頁(yè)面存儲(chǔ)的token,可查詢到:
2.驗(yàn)證token
我們需要將token值存入發(fā)送請(qǐng)求的請(qǐng)求頭中,做到只要調(diào)用接口,就可以將token一并發(fā)送到后端,從而對(duì)token登陸狀態(tài)進(jìn)行比對(duì)驗(yàn)證。
(1)前端admin端通過請(qǐng)求頭將token傳值給后端
在http.js中全局設(shè)置獲取token,將token值傳入請(qǐng)求頭Request
Headers中,然后后臺(tái)接口中直接從請(qǐng)求頭中獲取token,從而實(shí)現(xiàn)驗(yàn)證。
這里使用axios里的Interceptors方法,詳細(xì)可查閱axios手冊(cè):
admin/http.js前端admin端將token傳入請(qǐng)求頭Request Header:
// 使用axios的interceptors攔截器,將http調(diào)用時(shí)攔截
http.interceptors.request.use(function(config){
// 將token值傳入請(qǐng)求頭,"bearer + 空格"是代碼規(guī)范,看到Bearer(持票人)大家就明白是對(duì)token的驗(yàn)證
config.headers.Authorization = 'bearer ' + localStorage.token
return config
}, function(error){
// 錯(cuò)誤處理
return Promise.reject(error)
})

此時(shí)調(diào)用接口就可以將本地token傳給請(qǐng)求頭,測(cè)試:
(2)后端獲取請(qǐng)求頭中的token值,并解析token
// 解析token
func getToken() gin.HandlerFunc {
return func(ctx *gin.Context) {
// 從請(qǐng)求頭獲取token值
tokenString := ctx.GetHeader("Authorization")
// 判斷如果沒有token
if tokenString == "" {
ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "您未登錄"})
// 中間件中使用next()就執(zhí)行下一步,如果執(zhí)行abort()就不會(huì)執(zhí)行下一步
ctx.Abort()
// return
}
// 去除authorization中的"bearer"
tokenString = strings.Trim(tokenString, "bearer")
// 去除authorization中的空格
tokenString = strings.Replace(tokenString, " ", "", -1)
fmt.Println(tokenString)
// 判斷token是否失效
token, claims, err := ParseToken(tokenString)
if err != nil || !token.Valid{
ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "權(quán)限失效,請(qǐng)重新登錄"})
ctx.Abort()
// return
}
// 打印userid,既然定義這個(gè)值就必須使用,留下來以備以后的需要
fmt.Println(claims.UserId)
if claims.UserId != "" {
// 如果沒有token沒問題,則退出中間件執(zhí)行下一步
ctx.Next()
}
}
}
// 解析字符串token中的信息
func ParseToken(tokenString string) (*jwt.Token, *claims, error) {
claims := &claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (i interface{}, err error) {
return jwtkey, nil
})
return token, claims, err
}

此時(shí),刪除本地token:
刷新頁(yè)面,進(jìn)行token判斷:
此時(shí)接口無(wú)法調(diào)用,這里是因?yàn)槲覀儎h除了本地存儲(chǔ)的token,如果本地有token,但仍然錯(cuò)誤,就是因?yàn)橹皌oken設(shè)定的時(shí)間過期了:
同時(shí)報(bào)錯(cuò)中顯示為空白,我們需要讓它顯示出來報(bào)錯(cuò)原因,修改兩處:
(1)將報(bào)錯(cuò)返回的json值return返回:
(2)修改前端頁(yè)面的vue攔截信息,我們返回的錯(cuò)誤是msg:
此時(shí)再次刷新頁(yè)面:
既然報(bào)錯(cuò),則跳轉(zhuǎn)到登陸頁(yè)面:
刷新頁(yè)面,此時(shí)跳轉(zhuǎn)到登錄頁(yè)面:
登錄進(jìn)入頁(yè)面測(cè)試,無(wú)法登錄:
這事因?yàn)槲覀兊膖oken判斷中間件,同樣作用于login接口,所以需要進(jìn)行改動(dòng)后端接口路由:
將登錄接口放在token解析中間件上方,中間件就不會(huì)對(duì)登錄接口作用了,只會(huì)作用于下方接口,保存編譯測(cè)試:
到此,登錄的token解析功能實(shí)現(xiàn)。
3.解析token總結(jié)
只要我們確立好實(shí)現(xiàn)token驗(yàn)證的過程方向后就可以完成這個(gè)功能。登錄的token驗(yàn)證過程非常簡(jiǎn)單,就是調(diào)用接口→發(fā)送token→接收token→判斷token→將token判斷結(jié)果傳給前端→如已登錄運(yùn)行接口(若未登錄跳轉(zhuǎn)登錄頁(yè))→返回?cái)?shù)據(jù),我們的搭建過程就是對(duì)每個(gè)步驟進(jìn)行逐一尋找找方法、解決。
4.導(dǎo)航守衛(wèi)
此時(shí)我們只要調(diào)用接口就會(huì)解析token,但是進(jìn)入沒有接口的頁(yè)面就不會(huì)跳轉(zhuǎn)。如果我們不想讓用戶在不登陸的狀態(tài)下訪問我們的所有網(wǎng)頁(yè),就需要在前端也設(shè)置token判斷,做到只要不登錄,就無(wú)法訪問所有頁(yè)面。
這里我們就需要用到Vue的導(dǎo)航守衛(wèi)。
大家可以查看我之前的文章進(jìn)行設(shè)置,純前端操作,不涉及后端:
技能學(xué)習(xí):學(xué)習(xí)使用Node.js + Vue.js,開發(fā)前端全棧網(wǎng)站-12-4.登陸的前端vue-router路由驗(yàn)證(導(dǎo)航守衛(wèi))
到此就完成了一個(gè)簡(jiǎn)單的全棧項(xiàng)目,大家可以參照學(xué)習(xí)過程研究更多功能,下篇文章將admin端放入server端,通過server端進(jìn)行項(xiàng)目的訪問,將前后端分離狀態(tài)合并成一個(gè)完整的獨(dú)立項(xiàng)目(改為待上線模式,還是前后端分離)。
更多設(shè)計(jì)、功能的學(xué)習(xí)經(jīng)驗(yàn),大家也可以去我的公眾號(hào)查看!
————
