Go + gRPC-Gateway(V2) 构建微服务实战系列,小程序登录鉴权服务(三):RSA(RS512) 签名 JWT

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: Go + gRPC-Gateway(V2) 构建微服务实战系列,小程序登录鉴权服务(三):RSA(RS512) 签名 JWT

JWT & RS512



JWTRSA 详解,这里就不科普了。Google 一下,太多文章介绍了。

我们进入官网:https://jwt.io/


微信图片_20220611160124.png


我们这里看图说话,注意 Decoded 部分:


  1. HEADER:ALGORITHM & TOKEN TYPE
  2. PAYLOAD:DATA
  3. VERIFY SIGNATURE


没错,JWT 就这三部分组成 HEADER.PAYLOAD.SIGNATURE(头部.负载.签名),我们这里选择非对称加密算法(RS512)。


简单来说(编码时),鉴权微服务使用 Private Key 用来生成签名,其它微服务使用 Public Key 验证签名是不是 Auth 微服务签发的(过期、数据有无篡改等)。Private Key & Public Key 是成对出现的。


这里编码测试直接用 jwt.io 官网提供的,我们复制出来就好。


微信图片_20220611160140.png


RSA PRIVATE KEY


-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw
kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr
m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi
NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV
3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2
QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs
kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go
amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM
+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9
D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC
0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y
lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+
hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp
bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X
+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B
BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC
2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx
QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz
5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9
Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0
NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j
8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma
3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K
y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB
jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=
-----END RSA PRIVATE KEY-----


我们将其放在 microsvcs/auth/private.key


PUBLIC KEY


-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----


我们将其放在 microsvcs/auth/public.key


Payload


这里我们要用的测试 Data 如下:


{
  "exp": 1516246222,
  "iat": 1516239022,
  "iss": "server/auth",
  "sub": "607266aa512e006d58b79d22"
}


  • iss:表示谁签发,这里就是我们的这个鉴权微服务。
  • iattoken 签发时间
  • exptoken 过期时间
  • sub:谁被签发,这里就是我们的账号 ID


编码实战



定义 TokenGenerator 接口 & 使用


我们进入到 microsvcs/auth/auth/auth.go,延续我们之前的逻辑,因为生成 Token 是个独立的实现,所以按套路我们定义一个 TokenGenerator 接口。


type TokenGenerator interface {
  GenerateToken(accountID string, expire time.Duration) (string, error)
}


使用者定义接口使用者定义接口使用者定义接口重要事说三遍!!!

改造 type Service struct 如下:


type Service struct {
  Mongo          *dao.Mongo
  Logger         *zap.Logger
  OpenIDResolver OpenIDResolver
  TokenGenerator TokenGenerator // 生成器
  TokenExpire    time.Duration  // 过期时间
  authpb.UnimplementedAuthServiceServer
}


我们让 TokenGenerator & TokenExpire 从外面传进来,通通可配,随时替换相关实现。

用的时候就非常简单了:


...
...
tkn, err := s.TokenGenerator.GenerateToken(accountID, s.TokenExpire)
if err != nil {
  s.Logger.Error("cannot generate token", zap.Error(err))
  return nil, status.Error(codes.Internal, "")
}
return &authpb.LoginResponse{
  AccessToken: tkn,
  ExpiresIn:   int32(s.TokenExpire.Seconds()),
}, nil


实现 TokenGenerator 接口


我们编写如下代码(文件位于microsvcs/auth/token/jwt.go):


type JWTTokenGen struct {
  privateKey *rsa.PrivateKey
  issuer     string
  nowFunc    func() time.Time
}
func NewJWTTokenGen(issuer string, privateKey *rsa.PrivateKey) *JWTTokenGen {
  return &JWTTokenGen{
    issuer:     issuer,
    nowFunc:    time.Now,
    privateKey: privateKey,
  }
}
func (t *JWTTokenGen) GenerateToken(accountID string, expire time.Duration) (string, error) {
  nowSec := t.nowFunc().Unix()
  token := jwt.NewWithClaims(jwt.SigningMethodRS512, jwt.StandardClaims{
    Issuer:    t.issuer, // 签发者
    IssuedAt:  nowSec, // 签发时间
    ExpiresAt: nowSec + int64(expire.Seconds()), // 过期时间
    Subject:   accountID, // 签发给谁
  })
  return token.SignedString(t.privateKey)
}


写代码的套路都是一样的:


  • 定义一个 struct(JWTTokenGen)
  • 外部可初始化该 struct(NewJWTTokenGen),让一些公共私密的资源可配置化(如:rsa.PrivateKey
  • TokenGenerator 接口的具体实现(GenerateToken)

注意:这里定义 nowFunc,方便 Unit Tests 固定某一个时间点去做签发工作。


编写测试


具体代码位于 microsvcs/auth/token/jwt_test.go


func TestGenerateToken(t *testing.T) {
  pkFile, err := os.Open("../private.key")
  if err != nil {
    logger.Fatal("cannot open private key", zap.Error(err))
  }
  pkBytes, err := ioutil.ReadAll(pkFile)
  if err != nil {
    logger.Fatal("cannot read private key", zap.Error(err))
  }
  key, err := jwt.ParseRSAPrivateKeyFromPEM(pkBytes)
  if err != nil {
    logger.Fatal("cannot parse private key", zap.Error(err))
  }
  g := NewJWTTokenGen("server/auth", key)
  g.nowFunc = func() time.Time {
    return time.Unix(1516239022, 0)
  }
  tkn, err := g.GenerateToken("607266aa512e006d58b79d22", 2*time.Hour)
  if err != nil {
    t.Errorf("cannot generate token: %v", err)
  }
  want := "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTYyNDYyMjIsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoic2VydmVyL2F1dGgiLCJzdWIiOiI2MDcyNjZhYTUxMmUwMDZkNThiNzlkMjIifQ.nwhaGZ0dozftexVfr9KM9ZVAzsPudhLs-n-yyrrjkbFTYA69rsEd35M0vc1gJ1DNMJk_v-1yUhkgRpxzP2Jiy1Lw8fqIlAk8l9EpDE77oJ9Dal6Rl26GERYZOkCvbq02fKSVj4drlSr75fIce9EnQq2xIVyvvNNty-QvHXTX29QQv-6c8vVYIrCFxtooARN9p8OSpg0hzc-YzsXo64lbUvbLIws27TJNwhctbqrOYQuX9XU3UhJ4Ik0Yt2cLc4LjuqI52Grvf89mJMmM5jnHQv0tKI2guvxNwlC3WN50dCIcuo1zjO-_eSje5OvqP7FKR1eSwnEcZiZQ8qwDDGi8pA"
  if tkn != want {
    t.Errorf("wrong token generated. want: %q, got: %q", want, tkn)
  }
}


联调



更新 microsvcs/auth/main.go

我们增加 TokenExpireTokenGenerator 的相关配置。


authpb.RegisterAuthServiceServer(s, &auth.Service{
    OpenIDResolver: &wechat.Service{
      AppID:     "your-app-id",
      AppSecret: "your-app-secret",
    },
    Mongo:          dao.NewMongo(mongoClient.Database("grpc-gateway-auth")),
    Logger:         logger,
    TokenExpire:    2 * time.Hour,
    TokenGenerator: token.NewJWTTokenGen("server/auth", privKey),
})


小程序联调


微信图片_20220611160229.png


完美签发 Token

大家持续关注,未完待续……

相关文章
|
2月前
|
Dubbo Java 应用服务中间件
微服务框架Dubbo环境部署实战
微服务框架Dubbo环境部署的实战指南,涵盖了Dubbo的概述、服务部署、以及Dubbo web管理页面的部署,旨在指导读者如何搭建和使用Dubbo框架。
186 17
微服务框架Dubbo环境部署实战
|
8天前
|
小程序 安全 数据库连接
为什么已经提交的小程序无法连接后台服务?
【10月更文挑战第17天】为什么已经提交的小程序无法连接后台服务?
23 0
|
2月前
|
运维 持续交付 API
深入理解并实践微服务架构:从理论到实战
深入理解并实践微服务架构:从理论到实战
93 3
|
2月前
|
运维 监控 持续交付
深入浅出:微服务架构的设计与实战
微服务,一个在软件开发领域如雷贯耳的名词,它代表着一种现代软件架构的风格。本文将通过浅显易懂的语言,带领读者从零开始了解微服务的概念、设计原则及其在实际项目中的运用。我们将一起探讨如何将一个庞大的单体应用拆分为灵活、独立、可扩展的微服务,并分享一些实践中的经验和技巧。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和深入的理解。
71 3
|
2月前
|
自然语言处理 Java 网络架构
解锁跨平台微服务新纪元:Micronaut与Kotlin联袂打造的多语言兼容服务——代码、教程、实战一次打包奉送!
【9月更文挑战第6天】Micronaut是一款轻量级、高性能的Java框架,适用于微服务开发。它支持Java、Groovy和Kotlin等多种语言,提供灵活的多语言开发环境。本文通过创建一个简单的多语言兼容服务,展示如何使用Micronaut及其注解驱动特性实现REST接口,并引入国际化支持。无论是个人项目还是企业应用,Micronaut都能提供高效、一致的开发体验,成为跨平台开发的利器。通过简单的配置和代码编写,即可实现多语言支持,展现其强大的跨平台优势。
49 2
|
3月前
|
消息中间件 缓存 Kafka
go-zero微服务实战系列(八、如何处理每秒上万次的下单请求)
go-zero微服务实战系列(八、如何处理每秒上万次的下单请求)
|
3月前
|
缓存 NoSQL Redis
go-zero微服务实战系列(七、请求量这么高该如何优化)
go-zero微服务实战系列(七、请求量这么高该如何优化)
|
3月前
|
安全 数据安全/隐私保护 微服务
微服务 Token 鉴权设计:一场守护系统安全的惊心动魄之战,你敢应战吗?
【8月更文挑战第29天】在微服务架构中,Token鉴权设计至关重要,它通过在客户端与服务器间传递包含用户身份和权限信息的Token来确保系统安全。合理的Token鉴权能有效防止非法访问,保护数据安全。设计时需考虑Token的有效期、刷新机制及加密算法等,以提升安全性。随着技术发展,持续优化鉴权机制对于满足复杂的安全需求至关重要。
51 0
|
3月前
|
监控 Cloud Native 开发者
云端精英的.NET微服务秘籍:Azure上的创新实战演练
【8月更文挑战第28天】在现代软件开发中,微服务架构通过分解应用程序提升可维护性和扩展性。结合Azure与.NET框架,开发者能轻松打造高效且易管理的云原生微服务。首先,使用Docker容器化.NET应用,并借助Azure Kubernetes Service(AKS)或Azure Container Instances(ACI)部署。为确保高可用性和伸缩性,可利用Azure Traffic Manager负载均衡及Azure Autoscale动态调整实例数。
26 0
|
3月前
|
消息中间件 SQL 关系型数据库
go-zero微服务实战系列(十、分布式事务如何实现)
go-zero微服务实战系列(十、分布式事务如何实现)