溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

GoLang?jwt無(wú)感刷新與SSO單點(diǎn)登錄限制解除的方法是什么

發(fā)布時(shí)間:2023-03-31 15:07:20 來(lái)源:億速云 閱讀:117 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹了GoLang jwt無(wú)感刷新與SSO單點(diǎn)登錄限制解除的方法是什么的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇GoLang jwt無(wú)感刷新與SSO單點(diǎn)登錄限制解除的方法是什么文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

    為什么使用JWT

    Jwt提供了生成token以及token驗(yàn)證的方法,而token是一種不用存儲(chǔ)在服務(wù)端,只需要由用戶攜帶即可實(shí)現(xiàn)認(rèn)證的一種方式。在介紹JWT之前,我們也應(yīng)該先了解cookiesession。

    Cookie和Session

    為每一位用戶設(shè)定一個(gè)SessionID,每次都需要對(duì)該SessionID進(jìn)行比對(duì)。這個(gè)SessionID可能會(huì)保存在Cookie中,安全性不高,并且容易過(guò)期(一般session的過(guò)期時(shí)間都為30分鐘)。由于cookiesession需要保存在服務(wù)端,當(dāng)用戶量非常大的時(shí)候,服務(wù)端的負(fù)載就會(huì)越來(lái)越大。甚至有因此崩潰的可能。所以采用token認(rèn)證的方式。

    token (header.payload.signature)

    每個(gè)用戶在進(jìn)行登錄的時(shí)候如果登錄信息正確就會(huì)收到服務(wù)端頒發(fā)的令牌token。當(dāng)用戶每次進(jìn)行請(qǐng)求時(shí)都會(huì)攜帶一個(gè)token。該token會(huì)在服務(wù)端進(jìn)行校驗(yàn)處理,復(fù)雜一點(diǎn)還需要經(jīng)過(guò)一系列中間件的處理,確認(rèn)token格式和參數(shù)是否正確。如果一切正常就需要對(duì)該用戶的本次操作進(jìn)行放行。

    token 安全性

    如果token被非用戶人員獲取到,由于token已經(jīng)頒發(fā),在此token生效期間服務(wù)端無(wú)法對(duì)其進(jìn)行解除,因?yàn)樗⒉辉诜?wù)端內(nèi)部進(jìn)行保存。也就是說(shuō)服務(wù)端的token一旦頒發(fā)就無(wú)法取消。

    基于token安全性的處理

    access token 和 refresh token

    以下access token簡(jiǎn)稱 atokenrefresh token 簡(jiǎn)稱 rtoken。無(wú)感刷新方式。

    在用戶登錄的時(shí)候頒發(fā)兩個(gè)token,atokenrtokenatoken 的有效期很短,根據(jù)業(yè)務(wù)實(shí)際需求可以自定義。一般設(shè)置為10分鐘足夠。rtoken有效期較長(zhǎng),一般可以設(shè)置為一星期或者一個(gè)月,根據(jù)實(shí)際業(yè)務(wù)需求可以自行定義。(根據(jù)查詢資料得知 rtoken需要進(jìn)行client-sercet才能有效)。當(dāng)atoken過(guò)期之后可以通過(guò)rtoken進(jìn)行刷新,但是rtoken過(guò)期之后,只能重新登錄來(lái)獲取。

    當(dāng)atoken丟失之后沒(méi)關(guān)系,因?yàn)樗行诤芏?。?dāng)rtoken丟失之后也沒(méi)關(guān)系,因?yàn)樗枰浜?code>client-sercet才能使用。

    客戶端與服務(wù)端基于無(wú)感刷新流程圖

    GoLang?jwt無(wú)感刷新與SSO單點(diǎn)登錄限制解除的方法是什么

    golang實(shí)現(xiàn)atoken和rtoken

    引入jwt庫(kù) go get -u github.com/golang-jwt/jwt/v4

    頒發(fā)token

    // GenToken 頒發(fā)token access token 和 refresh token
    func GenToken(UserID int64, Username string) (atoken, rtoken string, err error) {
    	rc := jwt.RegisteredClaims{
    		ExpiresAt: getJWTTime(ATokenExpiredDuration),
    		Issuer:    TokenIssuer,
    	}
    	at := MyClaim{
    		UserID,
    		Username,
    		rc,
    	}
    	atoken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, at).SignedString(mySecret)
    	// refresh token 不需要保存任何用戶信息
    	rt := rc
    	rt.ExpiresAt = getJWTTime(RTokenExpiredDuration)
    	rtoken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, rt).SignedString(mySecret)
    	return
    }

    在驗(yàn)證用戶登錄之后,根據(jù)傳入的UID和Uname,生成atokenrtoken。在頒發(fā)token中可以規(guī)定token的過(guò)期時(shí)間

    func (t *Token) SignedString(key interface{}) (string, error)
    SignedString creates and returns a complete, signed JWT. The token is signed using the SigningMethod specified in the token.

    SignedString該方法主要用于token的數(shù)字簽名

    校驗(yàn)token

    // VerifyToken 驗(yàn)證Token
    func VerifyToken(tokenID string) (*MyClaim, error) {
    	var myc = new(MyClaim)
    	token, err := jwt.ParseWithClaims(tokenID, myc, keyFunc)
    	if err != nil {
    		return nil, err
    	}
    	if !token.Valid {
    		return nil, ErrorInvalidToken
    	}
    	return myc, nil
    }

    根據(jù)傳入的token值來(lái)判斷是否有錯(cuò)誤,如果錯(cuò)誤為無(wú)效,說(shuō)明token格式不正確。然后校驗(yàn)token是否過(guò)期。

    無(wú)感刷新token

    // RefreshToken 通過(guò) refresh token 刷新 atoken
    func RefreshToken(atoken, rtoken string) (newAtoken, newRtoken string, err error) {
    	// rtoken 無(wú)效直接返回
    	if _, err = jwt.Parse(rtoken, keyFunc); err != nil {
    		return
    	}
    	// 從舊access token 中解析出claims數(shù)據(jù)
    	var claim MyClaim
    	_, err = jwt.ParseWithClaims(atoken, &claim, keyFunc)
    	// 判斷錯(cuò)誤是不是因?yàn)閍ccess token 正常過(guò)期導(dǎo)致的
    	v, _ := err.(*jwt.ValidationError)
    	if v.Errors == jwt.ValidationErrorExpired {
    		return GenToken(claim.UserID, claim.Username)
    	}
    	return
    }

    注釋已經(jīng)寫得很明白了,會(huì)根據(jù)舊的atoken和rtoken來(lái)返回新token。

    完整實(shí)現(xiàn)代碼

    package main
    import (
    	"errors"
    	"time"
    	"github.com/golang-jwt/jwt/v4"
    )
    const (
    	ATokenExpiredDuration  = 2 * time.Hour
    	RTokenExpiredDuration  = 30 * 24 * time.Hour
    	TokenIssuer            = ""
    )
    var (
    	mySecret          = []byte("xxxx")
    	ErrorInvalidToken = errors.New("verify Token Failed")
    )
    type MyClaim struct {
    	UserID   int64  `json:"user_id"`
    	Username string `json:"username"`
    	jwt.RegisteredClaims
    }
    func getJWTTime(t time.Duration) *jwt.NumericDate {
    	return jwt.NewNumericDate(time.Now().Add(t))
    }
    func keyFunc(token *jwt.Token) (interface{}, error) {
    	return mySecret, nil
    }
    // GenToken 頒發(fā)token access token 和 refresh token
    func GenToken(UserID int64, Username string) (atoken, rtoken string, err error) {
    	rc := jwt.RegisteredClaims{
    		ExpiresAt: getJWTTime(ATokenExpiredDuration),
    		Issuer:    TokenIssuer,
    	}
    	at := MyClaim{
    		UserID,
    		Username,
    		rc,
    	}
    	atoken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, at).SignedString(mySecret)
    	// refresh token 不需要保存任何用戶信息
    	rt := rc
    	rt.ExpiresAt = getJWTTime(RTokenExpiredDuration)
    	rtoken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, rt).SignedString(mySecret)
    	return
    }
    // VerifyToken 驗(yàn)證Token
    func VerifyToken(tokenID string) (*MyClaim, error) {
    	var myc = new(MyClaim)
    	token, err := jwt.ParseWithClaims(tokenID, myc, keyFunc)
    	if err != nil {
    		return nil, err
    	}
    	if !token.Valid {
    		err = ErrorInvalidToken
    		return nil, err
    	}
    	return myc, nil
    }
    // RefreshToken 通過(guò) refresh token 刷新 atoken
    func RefreshToken(atoken, rtoken string) (newAtoken, newRtoken string, err error) {
    	// rtoken 無(wú)效直接返回
    	if _, err = jwt.Parse(rtoken, keyFunc); err != nil {
    		return
    	}
    	// 從舊access token 中解析出claims數(shù)據(jù)
    	var claim MyClaim
    	_, err = jwt.ParseWithClaims(atoken, &claim, keyFunc)
    	// 判斷錯(cuò)誤是不是因?yàn)閍ccess token 正常過(guò)期導(dǎo)致的
    	v, _ := err.(*jwt.ValidationError)
    	if v.Errors == jwt.ValidationErrorExpired {
    		return GenToken(claim.UserID, claim.Username)
    	}
    	return
    }

    SSO(Single Sign On)單用戶登錄以及無(wú)感刷新token

    實(shí)現(xiàn)思路

    因?yàn)?code>token是由服務(wù)端頒發(fā)并且每次用戶的操作都要在服務(wù)端校驗(yàn)token的有效性。因此兩個(gè)用戶在不同時(shí)間段登錄同一個(gè)賬號(hào),那么他們的token肯定會(huì)因?yàn)闀r(shí)間而有所差別。我們可以將token存放在redis中,與用戶ID進(jìn)行key-value綁定。如果通過(guò)userID查詢到的token不同,那么說(shuō)明這個(gè)用戶的token已經(jīng)被更換(該賬號(hào)又被登錄了)或者token錯(cuò)誤。就需要重新進(jìn)行登錄操作。

    實(shí)戰(zhàn)代碼

    // parts[1]是獲取到的atoken,我們使用之前定義好的解析JWT的函數(shù)來(lái)解析它
    mc, err := jwt.VerifyToken(parts[1])
    if err != nil {
        // 如果解析失敗,可能是因?yàn)閠oken過(guò)期,可以進(jìn)入refreshToken進(jìn)行判斷
       if newAtoken, newRtoken, err := jwt.RefreshToken(parts[1],rtoken); err == nil {
           // 如果無(wú)錯(cuò)誤,就更新redis中的token
          if err = redis.SetSingleUserToken(mc.Username, newAtoken); err == nil {
              // 這里根據(jù)需求返回給前端,由前端進(jìn)行處理
             c.Writer.Header().Set("newAtoken", newAtoken)
             c.Writer.Header().Set("newRtoken", newRtoken)
             // 如果無(wú)錯(cuò)誤,請(qǐng)求繼續(xù)
              c.Next()
          }
       }
        // 這里使用的是gin框架, 如果有錯(cuò)誤直接阻止并返回
       c.Abort()
       return
    }
    // 如果解析成功,就在redis中進(jìn)行判斷,是否單用戶登錄
    // 通過(guò)獲取redis中的token來(lái)校驗(yàn)是否單用戶登錄
    token, err := redis.GetSingleUserToken(mc.Username)
    if err != nil {
       serializer.ResponseError(c, e.CodeServerBusy)
       c.Abort()
       return
    }

    判斷過(guò)程

    • 請(qǐng)求從前端傳來(lái),經(jīng)過(guò)認(rèn)證中間件進(jìn)行校驗(yàn)token,如果沒(méi)有問(wèn)題就進(jìn)行redis單用戶校驗(yàn)。

    • 如果有問(wèn)題,可能是token過(guò)期。進(jìn)行無(wú)感刷新,如果刷新成功將新token設(shè)置在header中,請(qǐng)求繼續(xù)

    • 如果無(wú)感刷新失敗請(qǐng)求阻止。

    關(guān)于“GoLang jwt無(wú)感刷新與SSO單點(diǎn)登錄限制解除的方法是什么”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“GoLang jwt無(wú)感刷新與SSO單點(diǎn)登錄限制解除的方法是什么”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI