golang实现JWT认证中间件插件库jwt-auth的使用
Golang实现JWT认证中间件插件库jwt-auth的使用
jwt-auth是一个用于Golang的JWT认证中间件库。
快速开始
下面是一个基本的使用示例:
package main
import (
"net/http"
"log"
"time"
"github.com/adam-hanna/jwt-auth/jwt"
)
var restrictedRoute jwt.Auth
var restrictedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome to the secret area!"))
})
var regularHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
func main() {
authErr := jwt.New(&restrictedRoute, jwt.Options{
SigningMethodString: "RS256",
PrivateKeyLocation: "keys/app.rsa", // `$ openssl genrsa -out app.rsa 2048`
PublicKeyLocation: "keys/app.rsa.pub", // `$ openssl rsa -in app.rsa -pubout > app.rsa.pub`
RefreshTokenValidTime: 72 * time.Hour,
AuthTokenValidTime: 15 * time.Minute,
Debug: false,
IsDevEnv: true,
})
if authErr != nil {
log.Println("Error initializing the JWT's!")
log.Fatal(authErr)
}
http.HandleFunc("/", regularHandler)
// 这个路由将不可用,因为我们从未发放过token
// 查看login_logout示例了解如何提供token
http.Handle("/restricted", restrictedRoute.Handler(restrictedHandler))
log.Println("Listening on localhost:3000")
http.ListenAndServe("127.0.0.1:3000", nil)
}
设计目标
理解这个认证架构的目标非常重要。它并不适用于所有用例。请阅读并理解以下目标,并根据您的具体需求调整工作流程:
- 保护非关键API(例如不用于金融、医疗保健、政府等服务)
- 无状态
- 用户会话
- CSRF保护
- Web和/或移动端
设计架构
这个认证系统基于以下三个主要组件:
- 短生命期(分钟级)的JWT认证令牌
- 较长生命期(小时/天级)的JWT刷新令牌
- CSRF密钥字符串
1. 短生命期JWT认证令牌
短生命期的jwt认证令牌允许用户向受保护的API端点发出无状态请求。默认过期时间为15分钟,将由较长生命期的刷新令牌刷新。
2. 较长生命期JWT刷新令牌
这个较长生命期的令牌将用于更新认证令牌。这些令牌默认有72小时的过期时间,每次刷新认证令牌时都会更新。
这些刷新令牌包含一个可以被授权客户端撤销的ID。
3. CSRF密钥字符串
每个客户端将获得一个CSRF密钥字符串,该字符串将与认证和刷新令牌中的CSRF密钥相同,并在每次刷新认证令牌时更改。这些密钥默认存储在"X-CSRF-Token"响应头中,但可以将头键设置为选项。
完整示例
下面是一个完整的登录/注销示例:
package main
import (
"log"
"net/http"
"time"
"github.com/adam-hanna/jwt-auth/jwt"
)
var restrictedRoute jwt.Auth
var restrictedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
csrfSecret := w.Header().Get("X-CSRF-Token")
claims, err := restrictedRoute.GrabTokenClaims(r)
log.Println(claims)
if err != nil {
http.Error(w, "Internal Server Error", 500)
} else {
w.Write([]byte("Welcome to the restricted area! CSRF: " + csrfSecret))
}
})
var loginHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 验证用户凭据...
// 创建claims
claims := jwt.ClaimsType{}
claims.StandardClaims.Id = "uniqueTokenId123" // 重要:如果你想稍后能够撤销此令牌,必须提供token ID
claims.CustomClaims = make(map[string]interface{})
claims.CustomClaims["Role"] = "user"
// 发放新令牌
err := restrictedRoute.IssueNewTokens(w, &claims)
if err != nil {
http.Error(w, "Internal Server Error", 500)
return
}
w.Write([]byte("Login successful!"))
})
var logoutHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := restrictedRoute.NullifyTokens(w, r)
if err != nil {
http.Error(w, "Internal Server Error", 500)
return
}
w.Write([]byte("Logout successful!"))
})
func main() {
authErr := jwt.New(&restrictedRoute, jwt.Options{
SigningMethodString: "RS256",
PrivateKeyLocation: "keys/app.rsa",
PublicKeyLocation: "keys/app.rsa.pub",
RefreshTokenValidTime: 72 * time.Hour,
AuthTokenValidTime: 15 * time.Minute,
Debug: false,
IsDevEnv: true,
})
if authErr != nil {
log.Println("Error initializing the JWT's!")
log.Fatal(authErr)
}
http.Handle("/login", loginHandler)
http.Handle("/logout", restrictedRoute.Handler(logoutHandler))
http.Handle("/restricted", restrictedRoute.Handler(restrictedHandler))
log.Println("Listening on localhost:3000")
http.ListenAndServe("127.0.0.1:3000", nil)
}
选项配置
type Options struct {
SigningMethodString string // 可选:"HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512"
PrivateKeyLocation string // 仅用于RSA和ECDSA签名方法;仅在VerifyOnlyServer为false时需要
PublicKeyLocation string // 仅用于RSA和ECDSA签名方法
HMACKey []byte // 仅用于HMAC-SHA签名方法
VerifyOnlyServer bool // false = 服务器可以验证和发放令牌(默认);true = 服务器只能验证令牌
BearerTokens bool // false = 服务器使用cookie传输jwt(默认);true = 服务器使用请求头
RefreshTokenValidTime time.Duration
AuthTokenValidTime time.Duration
AuthTokenName string // 默认为"AuthToken"(cookie)或"X-Auth-Token"(bearer令牌)
RefreshTokenName string // 默认为"RefreshToken"(cookie)或"X-Refresh-Token"(bearer令牌)
CSRFTokenName string // 默认为"X-CSRF-Token"
Debug bool // true = 显示更多日志
IsDevEnv bool // true = 开发模式;这将设置http cookie(如果使用)为不安全;false = 生产模式;这将设置http cookie(如果使用)为安全
}
性能
基准测试结果:
$ cd jwt && go test -bench=.
BenchmarkBaseServer-2 10000 137517 ns/op
BenchmarkValidAuthTokenWithCookies-2 5000 303160 ns/op
BenchmarkExpiredAuthTokenWithCookies-2 5000 323933 ns/op
PASS
ok github.com/adam-hanna/jwt-auth/jwt 15.463s
许可证
MIT许可证
更多关于golang实现JWT认证中间件插件库jwt-auth的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现JWT认证中间件插件库jwt-auth的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang JWT认证中间件实现
下面我将介绍如何使用Golang实现一个JWT认证中间件库,并提供完整的示例代码。
1. 基本实现
首先我们需要安装必要的依赖:
go get github.com/golang-jwt/jwt/v5
1.1 核心代码实现
package jwt_auth
import (
"errors"
"net/http"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
)
// 自定义Claims结构
type CustomClaims struct {
UserID int `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
// JWT配置
type Config struct {
SecretKey string // 密钥
ExpirationTime time.Duration // 过期时间
SigningMethod string // 签名方法 HS256/HS384/HS512
TokenLookup string // token查找位置 header:<name>
AuthScheme string // 认证方案 Bearer
}
// 默认配置
var defaultConfig = Config{
SecretKey: "your-secret-key",
ExpirationTime: 24 * time.Hour,
SigningMethod: "HS256",
TokenLookup: "header:Authorization",
AuthScheme: "Bearer",
}
// JWT认证中间件
type JWTAuth struct {
config Config
}
// 创建新的JWT认证实例
func New(config ...Config) *JWTAuth {
cfg := defaultConfig
if len(config) > 0 {
cfg = config[0]
}
return &JWTAuth{config: cfg}
}
// 生成Token
func (j *JWTAuth) GenerateToken(userID int, username string) (string, error) {
claims := CustomClaims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(j.config.ExpirationTime)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "jwt-auth",
},
}
method := jwt.GetSigningMethod(j.config.SigningMethod)
token := jwt.NewWithClaims(method, claims)
return token.SignedString([]byte(j.config.SecretKey))
}
// 解析Token
func (j *JWTAuth) ParseToken(tokenString string) (*CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return []byte(j.config.SecretKey), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
// 中间件函数
func (j *JWTAuth) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从请求中提取token
token, err := j.extractToken(r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
// 解析token
claims, err := j.ParseToken(token)
if err != nil {
http.Error(w, "invalid token", http.StatusUnauthorized)
return
}
// 将claims信息存入上下文
ctx := r.Context()
ctx = contextWithUserID(ctx, claims.UserID)
ctx = contextWithUsername(ctx, claims.Username)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
// 从请求中提取token
func (j *JWTAuth) extractToken(r *http.Request) (string, error) {
parts := strings.Split(j.config.TokenLookup, ":")
if len(parts) != 2 {
return "", errors.New("invalid token lookup format")
}
switch parts[0] {
case "header":
return j.extractTokenFromHeader(r, parts[1])
case "query":
return j.extractTokenFromQuery(r, parts[1])
case "cookie":
return j.extractTokenFromCookie(r, parts[1])
default:
return "", errors.New("unsupported token lookup")
}
}
// 从header中提取token
func (j *JWTAuth) extractTokenFromHeader(r *http.Request, key string) (string, error) {
authHeader := r.Header.Get(key)
if authHeader == "" {
return "", errors.New("authorization header is missing")
}
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == j.config.AuthScheme) {
return "", errors.New("invalid authorization header format")
}
return parts[1], nil
}
// 从query参数中提取token
func (j *JWTAuth) extractTokenFromQuery(r *http.Request, key string) (string, error) {
token := r.URL.Query().Get(key)
if token == "" {
return "", errors.New("token query parameter is missing")
}
return token, nil
}
// 从cookie中提取token
func (j *JWTAuth) extractTokenFromCookie(r *http.Request, key string) (string, error) {
cookie, err := r.Cookie(key)
if err != nil {
return "", errors.New("token cookie is missing")
}
return cookie.Value, nil
}
1.2 上下文处理
// 上下文键类型
type contextKey string
const (
userIDKey contextKey = "user_id"
usernameKey contextKey = "username"
)
// 将userID存入上下文
func contextWithUserID(ctx context.Context, userID int) context.Context {
return context.WithValue(ctx, userIDKey, userID)
}
// 从上下文中获取userID
func UserIDFromContext(ctx context.Context) (int, bool) {
userID, ok := ctx.Value(userIDKey).(int)
return userID, ok
}
// 将username存入上下文
func contextWithUsername(ctx context.Context, username string) context.Context {
return context.WithValue(ctx, usernameKey, username)
}
// 从上下文中获取username
func UsernameFromContext(ctx context.Context) (string, bool) {
username, ok := ctx.Value(usernameKey).(string)
return username, ok
}
2. 使用示例
2.1 初始化并使用中间件
package main
import (
"fmt"
"net/http"
"time"
"yourmodule/jwt_auth"
)
func main() {
// 初始化JWT认证
jwtAuth := jwt_auth.New(jwt_auth.Config{
SecretKey: "your-very-secret-key",
ExpirationTime: 24 * time.Hour,
})
// 受保护的路由
protectedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID, ok := jwt_auth.UserIDFromContext(r.Context())
if !ok {
http.Error(w, "user ID not found", http.StatusUnauthorized)
return
}
username, _ := jwt_auth.UsernameFromContext(r.Context())
fmt.Fprintf(w, "Welcome, %s (ID: %d)! This is a protected route.", username, userID)
})
// 登录路由
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
// 这里应该是你的用户认证逻辑
// 假设认证成功,我们生成token
userID := 123
username := "john_doe"
token, err := jwtAuth.GenerateToken(userID, username)
if err != nil {
http.Error(w, "failed to generate token", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"token": "%s"}`, token)
})
// 应用JWT中间件到受保护的路由
http.Handle("/protected", jwtAuth.Middleware(protectedHandler))
fmt.Println("Server started on :8080")
http.ListenAndServe(":8080", nil)
}
2.2 测试
- 首先访问
/login
获取token - 然后访问
/protected
并在Header中添加:Authorization: Bearer <your-token>
3. 功能扩展
这个基础实现可以进一步扩展:
- 刷新Token:添加刷新token的机制
- 黑名单:实现token黑名单用于登出功能
- 多角色支持:在claims中添加角色信息
- 性能优化:添加token缓存
这个JWT中间件实现提供了基本的认证功能,可以根据项目需求进行进一步定制和扩展。