Go語言jwt跨域鑒權(quán)的實(shí)現(xiàn)實(shí)例
JWT全稱JSON Web Token是一種跨域認(rèn)證解決方案,屬于一個開放的標(biāo)準(zhǔn),它規(guī)定了一種Token實(shí)現(xiàn)方式,目前多用于前后端分離項(xiàng)目和OAuth2.0業(yè)務(wù)場景下。
jwt介紹
官網(wǎng):https://jwt.io/
JWT 到底是什么?
簡而言之,jwt是一個簽名的 JSON 對象,可以做一些有用的事情(例如,身份驗(yàn)證)。它通常用于BearerOauth 2 中的令牌。令牌由三部分組成,由.'s 分隔。前兩部分是 JSON 對象,已經(jīng)過base64url編碼。最后一部分是簽名,以同樣的方式編碼。
第一部分稱為標(biāo)題。它包含驗(yàn)證最后一部分簽名的必要信息。例如,使用哪種加密方法進(jìn)行簽名以及使用了什么密鑰。
中間的部分是有趣的部分。它稱為聲明,包含您關(guān)心的實(shí)際內(nèi)容。有關(guān)保留密鑰和添加自己的正確方法的信息。
JWT 和 OAuth
值得一提的是,OAuth 和 JWT 不是一回事。JWT 令牌只是一個簽名的 JSON 對象。它可以在任何有用的地方使用。但是,存在一些混淆,因?yàn)?JWT 是 OAuth2 身份驗(yàn)證中最常見的不記名令牌類型。
不用太深入,這里是對這些技術(shù)交互的描述:
- OAuth 是一種允許身份提供者與用戶登錄的服務(wù)分開的協(xié)議。例如,每當(dāng)您使用 Facebook 登錄不同的服務(wù)(Yelp、Spotify 等)時,您都在使用 OAuth。
- OAuth 定義了幾個用于傳遞身份驗(yàn)證數(shù)據(jù)的選項(xiàng)。一種流行的方法稱為“不記名令牌”。不記名令牌只是一個字符串,只能由經(jīng)過身份驗(yàn)證的用戶持有。因此,只需出示此令牌即可證明您的身份。您可能可以從這里得出為什么 JWT 可能會成為一個好的不記名令牌。
- 因?yàn)椴挥浢钆朴糜谏矸蒡?yàn)證,所以對它們保密很重要。這就是使用不記名令牌的交易通常通過 SSL 發(fā)生的原因。
選擇簽名方法
有幾種可用的簽名方法,您可能應(yīng)該花時間了解各種選項(xiàng),然后再選擇一種。主要的設(shè)計(jì)決策很可能是對稱的還是非對稱的。
對稱簽名方法(例如 HSA)僅使用一個密鑰。這可能是最簡單的簽名方法,因?yàn)槿魏?code>[]byte都可以用作有效的秘密。它們在計(jì)算上的使用速度也略快一些,盡管這很少有關(guān)系。當(dāng)令牌的生產(chǎn)者和消費(fèi)者都受信任,甚至是同一個系統(tǒng)時,對稱簽名方法效果最好。由于相同的密鑰用于簽名和驗(yàn)證令牌,因此您無法輕松分發(fā)密鑰以進(jìn)行驗(yàn)證。
非對稱簽名方法(例如 RSA)使用不同的密鑰來簽名和驗(yàn)證令牌。這使得使用私鑰生成令牌成為可能,并允許任何消費(fèi)者訪問公鑰進(jìn)行驗(yàn)證。
簽名方法和密鑰類型
jwt-go庫支持 JWT 的解析和驗(yàn)證以及生成和簽名。當(dāng)前支持的簽名算法是 HMAC SHA、RSA、RSA-PSS 和 ECDSA
每個簽名方法都需要不同的對象類型作為其簽名密鑰。有關(guān)詳細(xì)信息,請參閱軟件包文檔。以下是最常見的:
- HMAC 簽名方法(
HS256,HS384,HS512)[]byte需要用于簽名和驗(yàn)證的值 - RSA 簽名方法( ,
RS256,RS384)RS512期望*rsa.PrivateKey用于簽名和*rsa.PublicKey驗(yàn)證 - ECDSA 簽名方法( ,
ES256,ES384)ES512期望*ecdsa.PrivateKey用于簽名和*ecdsa.PublicKey驗(yàn)證
安裝jwt
go get github.com/dgrijalva/jwt-go
簡單使用
生成JWT
type MyClaims struct {
//除了滿足下面的Claims,還需要以下用戶信息
Username string `json:"username"`
Password string `json:"password"`
//jwt中標(biāo)準(zhǔn)的Claims
jwt.StandardClaims
}
// 使用指定的 secret 簽名聲明一個 key ,便于后續(xù)獲得完整的編碼后的字符串token
var key = []byte("secret")
//GenToken 生成token的方法
func GenToken(username string, password string) (string, error) {
//創(chuàng)建一個我們自己的聲明
c := MyClaims{
username, //自定義字段
password,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 2).Unix(), //過期時間
Issuer: "Psych", //簽發(fā)人
},
}
//使用指定的簽名方法創(chuàng)建簽名對象
//這里使用HS256加密算法
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
//注意這個地方的 key 一定要是字節(jié)切片不能是字符串
return token.SignedString(key)
}
解析jwt
//ParseToken 解析token的方法
func ParseToken(tokenString string) (*MyClaims, error) {
//解析token
token, err := jwt.ParseWithClaims(tokenString, &MyClaims{},
func(token *jwt.Token) (i interface{}, err error) {
return key, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { //校驗(yàn)token
return claims, nil
}
return nil, errors.New("invalid token")
}
測試:生成token并解析token
func main() {
//生成token
token, err := GenToken("Psych", "123456")
if err != nil {
panic(err)
}
fmt.Printf("token: %v\n", token)
fmt.Println("----------------------")
//解析token
parseToken, err := ParseToken(token)
if err != nil {
panic(err)
}
fmt.Printf("parseToken.UserName: %v\n", parseToken.Username)
fmt.Printf("parseToken.Password: %v\n", parseToken.Password)
}
運(yùn)行結(jié)果:
[Running] go run "e:\golang開發(fā)學(xué)習(xí)\go_pro\main.go" token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IlBzeWNoIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJleHAiOjE2NjI1MzQ5MTEsImlzcyI6IlBzeWNoIn0.hsqMq08NB2uz5OSMXB5KT-77WlirvcSX8kgtyWIVEP0 ---------------------- parseToken.UserName: Psych parseToken.Password: 123456 [Done] exited with code=0 in 1.572 seconds
jwt在項(xiàng)目中的使用
第一步:在一個go文件中,寫生成jwt和解析jwt的方法,方便調(diào)用
package jwt
import (
"errors"
"time"
"github.com/dgrijalva/jwt-go"
)
//載荷
type Customclaims struct {
Empname string `json:"empname"`
Phone string `json:"phone"`
Role string `json:"role"`
jwt.StandardClaims
}
// MyClaims 自定義聲明結(jié)構(gòu)體并內(nèi)嵌jwt.StandardClaims
// jwt包自帶的jwt.StandardClaims只包含了官方字段
// 我們這里需要額外記錄一個UserID字段,所以要自定義結(jié)構(gòu)體
// 如果想要保存更多信息,都可以添加到這個結(jié)構(gòu)體中
type MyClaims struct {
UserName string `json:"user_name"`
jwt.StandardClaims
}
var mySecret = []byte("呼哧呼哧")
func keyFunc(_ *jwt.Token) (i interface{}, err error) {
return mySecret, nil
}
const TokenExpireDuration = time.Hour * 24 * 365
// GenToken 生成access token 和 refresh token
func GenToken(userName string) (aToken, rToken string, err error) {
// 創(chuàng)建一個我們自己的聲明
c := MyClaims{
userName, // 自定義字段
jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 過期時間
Issuer: "personal-blog", // 簽發(fā)人
},
}
// 加密并獲得完整的編碼后的字符串token
aToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, c).SignedString(mySecret)
// refresh token 不需要存任何自定義數(shù)據(jù)
rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Second * 30).Unix(), // 過期時間
Issuer: "personal-blog", // 簽發(fā)人
}).SignedString(mySecret)
// 使用指定的secret簽名并獲得完整的編碼后的字符串token
return
}
// ParseToken 解析JWT
func ParseToken(tokenString string) (claims *MyClaims, err error) {
// 解析token
var token *jwt.Token
claims = new(MyClaims)
token, err = jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
return
}
if !token.Valid { // 校驗(yàn)token
err = errors.New("invalid token")
}
return
}
// RefreshToken 刷新AccessToken
func RefreshToken(aToken, rToken string) (newAToken, newRToken string, err error) {
// 從舊access token中解析出claims數(shù)據(jù) 解析出payload負(fù)載信息
var claims MyClaims
_, err = jwt.ParseWithClaims(aToken, &claims, keyFunc)
// 當(dāng)access token是過期錯誤 并且 refresh token沒有過期時就創(chuàng)建一個新的access token
return GenToken(claims.UserName)
}
第二步:登陸的時候生成token
//登錄
func LoginHandler(c *gin.Context) {
// 1.獲取請求參數(shù) 2.校驗(yàn)數(shù)據(jù)有效性
var L models.Users
if err := c.ShouldBindJSON(&L); err != nil {
zap.L().Error("invalid params", zap.Error(err))
ResponseErrorWithMsg(c, CodeInvalidParams, err.Error())
return
}
var my models.Update_my
//用戶登錄
if err, a := mysql.Login(&L); err != nil {
zap.L().Error("mysql.Login(&u) failed", zap.Error(err))
ResponseError(c, CodeInvalidPassword)
return
} else {
my = a
}
// 生成Token
aToken, rToken, _ := jwt.GenToken(L.UserName)
ResponseSuccess(c, gin.H{
"accessToken": aToken,
"refreshToken": rToken,
"username": L.UserName,
"role": L.Role,
"realname": my.Realname,
"phone_number": my.PhoneNumber,
"id_number": my.IDNumber,
})
}
第三步:在控制器中寫一個go文件,JWTAuthMiddleware基于JWT的認(rèn)證中間件
package controller
import (
"Fever_backend/dao/mysql"
"Fever_backend/pkg/jwt"
"errors"
"fmt"
"go.uber.org/zap"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
const (
ContextUserNameKey = "userName"
)
var (
ErrorUserNotLogin = errors.New("當(dāng)前用戶未登錄")
)
// JWTAuthMiddleware 基于JWT的認(rèn)證中間件
func JWTAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
// 客戶端攜帶Token有三種方式 1.放在請求頭 2.放在請求體 3.放在URI
// 這里假設(shè)Token放在Header的Authorization中,并使用Bearer開頭
// 這里的具體實(shí)現(xiàn)方式要依據(jù)你的實(shí)際業(yè)務(wù)情況決定
authHeader := c.Request.Header.Get("Authorization")
if authHeader == "" {
ResponseErrorWithMsg(c, CodeInvalidToken, "請求頭缺少Auth Token")
c.Abort()
return
}
// 按空格分割
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
ResponseErrorWithMsg(c, CodeInvalidToken, "Token格式不對")
c.Abort()
return
}
// parts[1]是獲取到的tokenString,我們使用之前定義好的解析JWT的函數(shù)來解析它
mc, err := jwt.ParseToken(parts[1])
if err != nil {
fmt.Println(err)
ResponseError(c, CodeInvalidToken)
c.Abort()
return
}
// 將當(dāng)前請求的username信息保存到請求的上下文c上
c.Set(ContextUserNameKey, mc.UserName)
c.Next() // 后續(xù)的處理函數(shù)可以用過c.Get("userID")來獲取當(dāng)前請求的用戶信息
}
}
第四部:登陸驗(yàn)證token
//這里只是舉了個例,具體業(yè)務(wù)需要具體分析
//登錄驗(yàn)證token
v1.Use(controller.JWTAuthMiddleware())
{
//修改密碼
v1.POST("/change_password", controller.ChangePasswordHandler)
//加權(quán)限
v1.POST("/add_casbin", controller.AddCasbin)
}
到此這篇關(guān)于Go語言jwt跨域鑒權(quán)的實(shí)現(xiàn)實(shí)例的文章就介紹到這了,更多相關(guān)Go語言jwt跨域鑒權(quán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang獲取prometheus數(shù)據(jù)(prometheus/client_golang包)
本文主要介紹了使用Go語言的prometheus/client_golang包來獲取Prometheus監(jiān)控?cái)?shù)據(jù),具有一定的參考價值,感興趣的可以了解一下2025-03-03
GoLand編譯帶有構(gòu)建標(biāo)簽的程序思路詳解
這篇文章主要介紹了GoLand編譯帶有構(gòu)建標(biāo)簽的程序,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
解決Golang在Web開發(fā)時前端莫名出現(xiàn)的空白換行
最近在使用Go語言開發(fā)Web時,在前端莫名出現(xiàn)了空白換行,找了網(wǎng)上的一些資料終于找到了解決方法,現(xiàn)在分享給大家,有需要的可以參考。2016-08-08
golang基于websocket通信tcp keepalive研究記錄
這篇文章主要為大家介紹了golang基于websocket通信tcp keepalive研究記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
GoLang中panic與recover函數(shù)以及defer語句超詳細(xì)講解
這篇文章主要介紹了GoLang的panic、recover函數(shù),以及defer語句,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01

