JWTとは何ですか?
JSON Web Tokenは、オプションの署名またはオプションの暗号化を使用してデータを作成するためのインターネット標準であり、2者間でクレームを安全に表現できます。トークンは、秘密秘密または公開/秘密鍵で署名されます。
簡単に言うと、クライアントはJWTメカニズムを介して情報を暗号化し、HTTPリクエストのヘッダーに追加してサーバーに送信することができ、サーバーはクライアントの正当性を検証します。
JWT公式ウェブサイトを参照してください
難しい質問
JWTロジックの実装は非常に単純であり、参照用にインターネット上に多くの既製の資料があります。私たちが遭遇したトリッキーな問題は、[キーの保存方法]です。
- コードリポジトリ:リークのリスクが高く、非常に安全ではありません
- 秘密鍵管理システム:安全ですが、このシステムがうまくいかないようにするための追加の運用および保守作業があります
必要なのは、サードパーティが提供する[安全な][シンプルな]キープロバイダーです。クラウドベンダーのKMS(キー管理システム)は、この問題を完全に解決できます。
すべての主要なクラウドベンダーがKMSサービスを提供しています。ここでは、TencentCloudKMSを例として取り上げて小さなデモを作成します。
解決
rk-boot / v2 + rk-cloud / tencent / signer + Tencent Cloud KMSを使用して、バックエンドJWT検証と署名ロジックをすばやく実装します。
- rk-boot / v2:リッチミドルウェア(JWT)を含むYAMLファイルを使用してGolangマイクロサービスをすばやく開始できます
- rk-cloud / tencent / signer:これはrk-boot / v2シリーズのプラグインであり、TencentCloudKMSにすばやくアクセスできます。
デモ
このデモでは、次のシナリオをシミュレートします。/ v1/loginと/v1/greeterの2つのAPIを提供します。
- / v1 / login:APIにログインすると、サーバーはaccessTokenを返します
- / v1 / greeter:送信するときに、accessTokenをHTTPヘッダーに追加します。そうしないと、リクエストは401を返します。
1. Tencent Cloudアカウントを開き、KMSをアクティブ化します
Tencent Cloudアカウントの開設は無料ですが、1元をデポジットすることをお勧めします。そうしないと、KMSでキーが作成されない場合があります。
2.クラウドアクセスキーを生成し、KMSキーを作成します
コードを介してクラウド上のリソースを使用する場合は、一般にAK / SKと呼ばれるクラウドアクセスキーを使用する必要があります。次のドキュメントに従ってキーを生成し、保存します。このキーを漏らさないように注意してください。
次に、コンソールにログインして、署名用のRSA非対称キーを作成できます。Tencent Cloudは、KMSの標準バージョンとフラッグシップバージョンを提供します。フラッグシップバージョンはより安全ですが、高価です。中小規模のプロジェクトでは、標準バージョンを使用できます。
ここで、[非対称署名検証]と[RSA_2048]を選択してください。rk-cloud / tencentは現在、他の暗号化アルゴリズムをサポートしていません。
作成が成功したら、IDを覚えておいてください。コードでこのIDを参照します。
3. rk-boot/v2をダウンロードします
$ go get github.com/rookie-ninja/rk-boot/v2
$ go get github.com/rookie-ninja/rk-gin/v2
$ go get github.com/rookie-ninja/rk-cloud/tencent/signer
复制代码
合計3つの依存関係をダウンロードしました。
- rk-boot / v2:リッチミドルウェア(JWT)を含むYAMLファイルを使用してGolangマイクロサービスをすばやく開始できます
- rk-cloud / tencent / signer:これはrk-boot / v2シリーズのプラグインであり、TencentCloudKMSにすばやくアクセスできます。
- rk-gin / v2 : gin-gonicマイクロサービスをすばやく開始するためのrk-boot/v2シリーズのプラグイン
4.boot.yamlを構成します
boot.yaml 文件告诉 rk-boot/v2 启动哪些 Gin & KMS 配套的服务。
---
gin:
- name: demo
port: 8080
enabled: true
middleware:
logging:
enabled: true # 启动日志中间件
jwt:
enabled: true # 启动 JWT 验证中间件
ignore:
- "/v1/login" # 对于 /v1/login,忽略 JWT 验证
signerEntry: signerT # 使用 tencent.signer 定义的 SignerEntry 进行验证
tencent:
config:
- name: credT # 根据需要自由取名
override:
accessKey: "云访问密钥 AK" # 云访问密钥,Access Key ID
secretKey: "云访问密钥 SK" # 云访问密钥,Secret Key ID
signer:
- name: signerT # 根据需要自由取名
config: credT # 上面的 Config 名字,让 signer 使用上面的云访问密钥
region: ap-beijing # 根据 KMS 密钥选择相应的 Region
algorithm: RSA_PKCS1_SHA_256 # 目前,只支持 RSA_PKCS1_SHA_256
kmsKeyId: "KMS 密钥 ID" # 腾讯云 KMS 控制台里创建的 KMS 密钥 ID
复制代码
在这个例子中,为了验证,我们在 boot.yaml 里强行注入了云访问密钥,这是很不安全的。
如果本地 ~/.tencentcloud 下有 credentials 文件,并且里面有 AK/SK,则不需要强行注入。
$ cat ~/.tencentcloud/credentials [default] secret_id=<云访问密钥 AK> secret_key=<云访问密钥 SK> 复制代码
随之,boot.yaml,也会变成如下:
--- gin: - name: demo port: 8080 enabled: true middleware: logging: enabled: true # 启动日志中间件 jwt: enabled: true # 启动 JWT 验证中间件 ignore: - "/v1/login" # 对于 /v1/login,忽略 JWT 验证 signerEntry: signerT # 使用 tencent.signer 定义的 SignerEntry 进行验证 tencent: signer: - name: signerT # 根据需要自由取名 region: ap-beijing # 根据 KMS 密钥选择相应的 Region algorithm: RSA_PKCS1_SHA_256 # 目前,只支持 RSA_PKCS1_SHA_256 kmsKeyId: "KMS 密钥 ID" # 腾讯云 KMS 控制台里创建的 KMS 密钥 ID 复制代码
5.写两个 API
生成 JWT Token 的时候,rk-boot/v2 会远程调用 KMS API 获取签名。验证签名的时候,不会调用 KMS API,rk-boot/v2 会在启动的时候,拉取 Public Key,这样节省了成本。
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"github.com/rookie-ninja/rk-boot/v2"
"github.com/rookie-ninja/rk-cloud/tencent/signer"
"github.com/rookie-ninja/rk-entry/v2/entry"
"github.com/rookie-ninja/rk-gin/v2/boot"
"github.com/rookie-ninja/rk-gin/v2/middleware/context"
"net/http"
"time"
)
var (
signerEntry rkentry.SignerJwt
)
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()
// Register handler
ginEntry := rkgin.GetGinEntry("demo")
ginEntry.Router.GET("/v1/login", Login)
ginEntry.Router.GET("/v1/greeter", Greeter)
// Bootstrap
boot.Bootstrap(context.Background())
// Assign SignerEntry
signerEntry = rktencentsigner.GetTencentSigner("signerT")
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
// Login API
func Login(ctx *gin.Context) {
ctx.JSON(http.StatusOK, map[string]string{
"accessToken": GenerateAccessToken(),
})
}
// JWT claims contains UID
type CustomClaims struct {
UID string `json:"uid"`
jwt.RegisteredClaims
}
// Generate JWT access token by calling KMS
func GenerateAccessToken() string {
now := time.Now()
claims := CustomClaims{
UID: "demo-id",
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(30 * time.Minute)),
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
Issuer: "rookie-ninja",
},
}
token, _ := signerEntry.SignJwt(claims)
return token
}
// Greeter API
func Greeter(ctx *gin.Context) {
jwtToken := rkginctx.GetJwtToken(ctx)
ctx.JSON(http.StatusOK, map[string]string{
"Message": fmt.Sprintf("Hello %s!", GetUidFromJwtToken(jwtToken)),
})
}
// Get user id from jwt token
func GetUidFromJwtToken(jwtToken *jwt.Token) string {
claims := &CustomClaims{}
// convert jwt map claims to json bytes
bytes, _ := json.Marshal(jwtToken.Claims)
// convert json bytes to custom claims
json.Unmarshal(bytes, claims)
return claims.UID
}
复制代码
6. 启动 main.go
$ go run main.go
复制代码
7. 验证
第一步,发送 /v1/login 请求,获取 AccessToken
$ curl localhost:8080/v1/login
{"accessToken":"eyXXX...."}
复制代码
发送 /v1/greeter 请求,并且带着 Authorization Header,记住,AccessToken 前面要加上 [Bearer],这是 JWT 中间件默认会验证的。
$ curl localhost:8080/v1/greeter -H 'Authorization:Bearer eyXXX...'
{"Message":"Hello demo-id!"}
复制代码
如果,发送一个错误的 AccessToken,则会返回 401
$ curl localhost:8080/v1/greeter
{"error":{"code":401,"status":"Unauthorized","message":"Invalid or expired jwt","details":[]}}
复制代码
rk-boot/v2 介绍
在形容 rk-boot/v2 的时候,一直没有找到一个合适的词语,类似 Go版本的 Spring boot,也类似 Go 进程启动器。当然,功能,完整性,理念等远不及 Spring boot。
暂时还没有想到更好的词汇。不过不耽误介绍其用法和设计理念。
RK 设计理念
在设计 rk-boot/v2 的时候,我们有几个【不做清单】。
- 不创造 RPC 框架
- 不重复造轮子
- 不封装开源 API/函数,保留原生用法
- 不引入小众开源
- 不绑定特定业务
我们想要做的是,让使用者可以通过 YAML 配置文件,以及几行简单代码,一键启动【高标准】Golang 微服务。简单点说,就是在 YAML 文件里,填写几行配置,就能让进程自动提供监控,日志,安全等等附属功能。
整个 RK 系列由3个大部分组成。
模块 | 介绍 |
---|---|
rk-boot/v2 | 使用者的统一入口,Golang 项目推荐通过 rk-boot/v2 作为入口使用 |
rk-entry/v2 | 所有 RK 系列的通用接口和通用功能实现,核心库,用户可以通过实现 rk-entry 里的接口,自定义 rk-xxx 插件 |
rk-xxx | RK 系列插件,由一系列包组成,用户根据需要自行引入,每一个 rk-xxx 插件都可以应设成 YAML 文件里的配置 |
如果把实现 Golang 后端微服务比喻成制作一个产品,RK 在其中的作用相当于【材料提供商】,与使用【原生开源材料】不同点在于, 使用者不必考虑和学习如何初始化【原生开源材料】,如何配置,如何进行监控,错误处理。使用者唯一要做的就是,熟悉【原生开源材料】的使用方法,嵌入到代码里使用。 我们希望通过这种方式,节省开发者的时间成本,以及保证代码库的标准性。
RK 插件
RK 的插件我们使用了【节外生枝】的设计理念。对于 RK 来说,流行的开源产品,属于【原料】,这些【原料】通过实现 rk-entry 抽象,就形成了 RK 系列的一个插件,形成【材料】。
rk-boot/v2 会自动识别这些插件,在启动进程的时候,进行相应初始化。
RK 插件列表
目前,rk-boot/v2 还处于萌芽阶段,实现的功能插件比较少,后续会持续迭代,也希望能在开源社区里得到更多的关注,并参与进来。
分类 | 开源插件 |
---|---|
Web 框架 | gin-gonic/gin |
gRPC | |
labstack/echo | |
gogf/gf | |
gofiber/fiber | |
zeromicro/go-zero | |
gorilla/mux | |
数据库 GORM | MySQL |
SQLite | |
SQL Server | |
postgreSQL | |
ClickHouse | |
MongoDB | |
Redis | |
Cache | Redis |
Cloud | AWS |
AWS/KMS | |
AWS/KMS/Signer | |
AWS/KMS/Crypto | |
Tencent | |
Tencent/KMS | |
Tencent/KMS/Signer | |
Tencent/KMS/Crypto |
- Web 框架中间件
中间件 | 介绍 |
---|---|
Prom | API prometheus 监控中间件 |
Logging | API 日志中间件 |
Trace | 基于 open-telemetry/opentelemetry-go API 调用链中间件 |
Panic | Panic 捕获中间件 |
Meta | 自动添加 RequestId,时间等 API 元数据中间件 |
Auth | [Basic Auth] & [API Key] 授权中间件 |
RateLimit | API 限流中间件 |
Timeout | API 超时中间件 |
Gzip | API 压缩中间件 |
CORS | API CORS 中间件 |
JWT | API JWT 验证中间件 |
Secure | API 通用安全中间件 |
CSRF | API CSRF 中间件 |
- Web 框架启动项
启动项 | 介绍 |
---|---|
Config | spf13/viper 初始化 |
Logger | uber-go/zap 日志初始化 |
Event | rk-query event 初始化 |
Certificate | TLS/SSL 证书初始化 |
Prometheus | Prometheus client 初始化 |
Swagger | 内嵌 swagger UI |
Docs | 内嵌 RapiDoc API 在线文档 |
CommonService | 通用 API |
StaticFileHandler | 埋め込まれた静的ファイルのダウンロードWebUI |
PProf | PProf Web UI |