前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >rk-boot/v2: 使用腾讯云 KMS 进行 JWT 验证 (Golang)

rk-boot/v2: 使用腾讯云 KMS 进行 JWT 验证 (Golang)

原创
作者头像
尹东勋
发布2022-03-27 18:36:55
1.5K0
发布2022-03-27 18:36:55
举报

什么是 JWT?

JSON 网络令牌是一种 Internet 标准,用于创建具有可选签名或可选加密的数据,让两方之间安全地表示声明。令牌使用私有秘密或公共/私有密钥进行签名。

简单来讲,就是通过 JWT 机制,让客户端通过一个密钥,把信息进行加密,添加到 HTTP 请求的 Header中,并传给服务端,服务端验证客户的合法性。

请参考 JWT 官网

棘手的问题

实现 JWT 逻辑很简单,网上有很多现成的资料可以参考。我们遇到的棘手问题就是【怎么存储密钥】。

  • 代码仓库:泄漏风险高,非常不安全
  • 私有密钥管理系统:安全,不过额外多了一个运维工作,要保证此系统不能出错

我们需要的就是一个第三方提供的【安全】,【简单】的密钥提供商。云厂商的 KMS(Key Management System)就可以完美解决这个问题。

各大云厂商都提供 KMS 服务,这里我们以腾讯云 KMS 为例,做个小 Demo。

解决方案

我们将使用 rk-boot/v2 + rk-cloud/tencent/signer + 腾讯云 KMS 快速实现后端 JWT 验证以及签名逻辑。

  • rk-boot/v2 : 可以让我们使用 YAML 文件快速启动 Golang 微服务,包括丰富的中间件(JWT)
  • rk-cloud/tencent/signer: 是 rk-boot/v2 系列的插件,可以让我们快速接入腾讯云 KMS

Demo

在这个 Demo 里,我们会模拟如下场景。提供两个 API,/v1/login & /v1/greeter。

  • /v1/login: 登陆 API,服务端会返回 accessToken
  • /v1/greeter: 发送的时候,在 HTTP Header 中加入 accessToken,否则请求返回 401

1. 开通腾讯云账户 & 开通 KMS

开通腾讯云账户 是免费的,不过建议往里存个1块钱,否则可能无法在 KMS 中创建密钥。

2.生成云访问密钥 & 创建 KMS 密钥

想要通过代码使用云上的资源,就需要使用到云访问密钥,一般称为 AK/SK,根据下面的文档生成密钥,并且保存,切记不要泄漏这个密钥。

然后,我们就可以登陆控制台,创建用于签名的 RSA 非对称密钥了。腾讯云提供了标准版 & 旗舰版 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: 可以让我们使用 YAML 文件快速启动 Golang 微服务,包括丰富的中间件(JWT)
  • rk-cloud/tencent/signer: 是 rk-boot/v2 系列的插件,可以让我们快速接入腾讯云 KMS
  • rk-gin/v2: rk-boot/v2 系列的插件,用于快速启动 gin-gonic 微服务

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 框架中间件

中间件

介绍

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

内嵌静态文件下载 Web UI

PProf

PProf Web UI

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 棘手的问题
  • 解决方案
  • Demo
    • 1. 开通腾讯云账户 & 开通 KMS
      • 2.生成云访问密钥 & 创建 KMS 密钥
        • 3.下载 rk-boot/v2
          • 4.配置 boot.yaml
            • 5.写两个 API
              • 6. 启动 main.go
                • 7. 验证
                • rk-boot/v2 介绍
                  • RK 设计理念
                    • RK 插件
                      • RK 插件列表
                      相关产品与服务
                      消息队列 TDMQ
                      消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档