Go语言jwt跨域鉴权的实现实例
作者:PPPsych
JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场景下。
jwt介绍
JWT 到底是什么?
简而言之,jwt是一个签名的 JSON 对象,可以做一些有用的事情(例如,身份验证)。它通常用于BearerOauth 2 中的令牌。令牌由三部分组成,由.'s 分隔。前两部分是 JSON 对象,已经过base64url编码。最后一部分是签名,以同样的方式编码。
第一部分称为标题。它包含验证最后一部分签名的必要信息。例如,使用哪种加密方法进行签名以及使用了什么密钥。
中间的部分是有趣的部分。它称为声明,包含您关心的实际内容。有关保留密钥和添加自己的正确方法的信息。
JWT 和 OAuth
值得一提的是,OAuth 和 JWT 不是一回事。JWT 令牌只是一个签名的 JSON 对象。它可以在任何有用的地方使用。但是,存在一些混淆,因为 JWT 是 OAuth2 身份验证中最常见的不记名令牌类型。
不用太深入,这里是对这些技术交互的描述:
- OAuth 是一种允许身份提供者与用户登录的服务分开的协议。例如,每当您使用 Facebook 登录不同的服务(Yelp、Spotify 等)时,您都在使用 OAuth。
- OAuth 定义了几个用于传递身份验证数据的选项。一种流行的方法称为“不记名令牌”。不记名令牌只是一个字符串,只能由经过身份验证的用户持有。因此,只需出示此令牌即可证明您的身份。您可能可以从这里得出为什么 JWT 可能会成为一个好的不记名令牌。
- 因为不记名令牌用于身份验证,所以对它们保密很重要。这就是使用不记名令牌的交易通常通过 SSL 发生的原因。
选择签名方法
有几种可用的签名方法,您可能应该花时间了解各种选项,然后再选择一种。主要的设计决策很可能是对称的还是非对称的。
对称签名方法(例如 HSA)仅使用一个密钥。这可能是最简单的签名方法,因为任何[]byte都可以用作有效的秘密。它们在计算上的使用速度也略快一些,尽管这很少有关系。当令牌的生产者和消费者都受信任,甚至是同一个系统时,对称签名方法效果最好。由于相同的密钥用于签名和验证令牌,因此您无法轻松分发密钥以进行验证。
非对称签名方法(例如 RSA)使用不同的密钥来签名和验证令牌。这使得使用私钥生成令牌成为可能,并允许任何消费者访问公钥进行验证。
签名方法和密钥类型
jwt-go库支持 JWT 的解析和验证以及生成和签名。当前支持的签名算法是 HMAC SHA、RSA、RSA-PSS 和 ECDSA
每个签名方法都需要不同的对象类型作为其签名密钥。有关详细信息,请参阅软件包文档。以下是最常见的:
- HMAC 签名方法(
HS256,HS384,HS512)[]byte需要用于签名和验证的值 - RSA 签名方法( ,
RS256,RS384)RS512期望*rsa.PrivateKey用于签名和*rsa.PublicKey验证 - ECDSA 签名方法( ,
ES256,ES384)ES512期望*ecdsa.PrivateKey用于签名和*ecdsa.PublicKey验证
安装jwt
go get github.com/dgrijalva/jwt-go
简单使用
生成JWT
type MyClaims struct {
//除了满足下面的Claims,还需要以下用户信息
Username string `json:"username"`
Password string `json:"password"`
//jwt中标准的Claims
jwt.StandardClaims
}
// 使用指定的 secret 签名声明一个 key ,便于后续获得完整的编码后的字符串token
var key = []byte("secret")
//GenToken 生成token的方法
func GenToken(username string, password string) (string, error) {
//创建一个我们自己的声明
c := MyClaims{
username, //自定义字段
password,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 2).Unix(), //过期时间
Issuer: "Psych", //签发人
},
}
//使用指定的签名方法创建签名对象
//这里使用HS256加密算法
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
//注意这个地方的 key 一定要是字节切片不能是字符串
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 { //校验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)
}
运行结果:
[Running] go run "e:\golang开发学习\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在项目中的使用
第一步:在一个go文件中,写生成jwt和解析jwt的方法,方便调用
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 自定义声明结构体并内嵌jwt.StandardClaims
// jwt包自带的jwt.StandardClaims只包含了官方字段
// 我们这里需要额外记录一个UserID字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
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) {
// 创建一个我们自己的声明
c := MyClaims{
userName, // 自定义字段
jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
Issuer: "personal-blog", // 签发人
},
}
// 加密并获得完整的编码后的字符串token
aToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, c).SignedString(mySecret)
// refresh token 不需要存任何自定义数据
rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Second * 30).Unix(), // 过期时间
Issuer: "personal-blog", // 签发人
}).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 { // 校验token
err = errors.New("invalid token")
}
return
}
// RefreshToken 刷新AccessToken
func RefreshToken(aToken, rToken string) (newAToken, newRToken string, err error) {
// 从旧access token中解析出claims数据 解析出payload负载信息
var claims MyClaims
_, err = jwt.ParseWithClaims(aToken, &claims, keyFunc)
// 当access token是过期错误 并且 refresh token没有过期时就创建一个新的access token
return GenToken(claims.UserName)
}
第二步:登陆的时候生成token
//登录
func LoginHandler(c *gin.Context) {
// 1.获取请求参数 2.校验数据有效性
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的认证中间件
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("当前用户未登录")
)
// JWTAuthMiddleware 基于JWT的认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
// 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI
// 这里假设Token放在Header的Authorization中,并使用Bearer开头
// 这里的具体实现方式要依据你的实际业务情况决定
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的函数来解析它
mc, err := jwt.ParseToken(parts[1])
if err != nil {
fmt.Println(err)
ResponseError(c, CodeInvalidToken)
c.Abort()
return
}
// 将当前请求的username信息保存到请求的上下文c上
c.Set(ContextUserNameKey, mc.UserName)
c.Next() // 后续的处理函数可以用过c.Get("userID")来获取当前请求的用户信息
}
}
第四部:登陆验证token
//这里只是举了个例,具体业务需要具体分析
//登录验证token
v1.Use(controller.JWTAuthMiddleware())
{
//修改密码
v1.POST("/change_password", controller.ChangePasswordHandler)
//加权限
v1.POST("/add_casbin", controller.AddCasbin)
}
到此这篇关于Go语言jwt跨域鉴权的实现实例的文章就介绍到这了,更多相关Go语言jwt跨域鉴权内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
