A series of
- Cloud native API Gateway, GRPC-Gateway V2 Exploration
- Go + GRPC-gateway (V2) Construction of micro-service combat series, small program login authentication service: the first chapter
- Go + GRPC-Gateway (V2) Construction of micro-service combat series, small program login authentication service: the second chapter
JWT & RS512
JWT and RSA are not popular here. Google it, too many articles.
IO/jwt. IO /
Here we look at the picture to speak, note the Decoded part:
HEADER
:ALGORITHM & TOKEN TYPEPAYLOAD
:DATAVERIFY SIGNATURE
Yes, JWT consists of these three parts: header.payload-signature. We choose asymmetric encryption algorithm (RS512).
In simple terms (when encoding), the authentication microservice uses the Private Key to generate the signature, and other microservices use the Public Key to verify that the signature is issued by the Auth microservice (expired, whether the data is tampered with, etc.). Private Key and Public Key come in pairs.
The code test is directly provided by jwt. IO, so let’s just copy it.
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-----
Copy the code
We put it in 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-----
Copy the code
We put it in microsvcs/auth/public key
Payload
Here we will use the following test Data:
{
"exp": 1516246222."iat": 1516239022."iss": "server/auth"."sub": "607266aa512e006d58b79d22"
}
Copy the code
iss
: indicates who issued the certificate. Here is our authentication micro service.iat
:token
The issuance of timeexp
:token
Expiration timesub
: Who is issued, here is our account numberID
Coding of actual combat
Define the TokenGenerator interface & use
We entered the microsvcs/auth/auth/auth. Go, the continuation of our previous logic, because the generated Token is a separate implementation, so generally we define a TokenGenerator interface.
type TokenGenerator interface {
GenerateToken(accountID string, expire time.Duration) (string, error)
}
Copy the code
User-defined interface! User-defined interface! User-defined interface! Important things say three times!!
Struct type Service struct:
type Service struct {
Mongo *dao.Mongo
Logger *zap.Logger
OpenIDResolver OpenIDResolver
TokenGenerator TokenGenerator / / generator
TokenExpire time.Duration // Expiration time
authpb.UnimplementedAuthServiceServer
}
Copy the code
We have TokenGenerator & TokenExpire passed in from the outside, all configurable to replace the implementation at any time.
It’s very simple to use:
. . tkn, err := s.TokenGenerator.GenerateToken(accountID, s.TokenExpire)iferr ! =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
Copy the code
Implement the TokenGenerator interface
We write the following code (file located in microsvcs/auth token/JWT. Go) :
type JWTTokenGen struct {
privateKey *rsa.PrivateKey
issuer string
nowFunc func(a) 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, / / issue
IssuedAt: nowSec, // Issue time
ExpiresAt: nowSec + int64(expire.Seconds()), // Expiration time
Subject: accountID, // To whom
})
return token.SignedString(t.privateKey)
}
Copy the code
The code is always the same:
- To define a
struct
(JWTTokenGen
) - This can be initialized externally
struct
(NewJWTTokenGen
), make some public and private resources configurable (e.g.rsa.PrivateKey
) TokenGenerator
Concrete implementation of the interface (GenerateToken
)
Note: nowFunc is defined so that Unit Tests can fix a point in time to do the issuing work.
Write the test
The code is located in Microsvcs/Auth /token/jwt_test.go:
func TestGenerateToken(t *testing.T) {
pkFile, err := os.Open(".. /private.key")
iferr ! =nil {
logger.Fatal("cannot open private key", zap.Error(err))
}
pkBytes, err := ioutil.ReadAll(pkFile)
iferr ! =nil {
logger.Fatal("cannot read private key", zap.Error(err))
}
key, err := jwt.ParseRSAPrivateKeyFromPEM(pkBytes)
iferr ! =nil {
logger.Fatal("cannot parse private key", zap.Error(err))
}
g := NewJWTTokenGen("server/auth", key)
g.nowFunc = func(a) time.Time {
return time.Unix(1516239022.0)
}
tkn, err := g.GenerateToken("607266aa512e006d58b79d22".2*time.Hour)
iferr ! =nil {
t.Errorf("cannot generate token: %v", err)
}
want := "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTYyNDYyMjIsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoic2VydmVyL2F1dGgiLCJzdWIiOi I2MDcyNjZhYTUxMmUwMDZkNThiNzlkMjIifQ.nwhaGZ0dozftexVfr9KM9ZVAzsPudhLs-n-yyrrjkbFTYA69rsEd35M0vc1gJ1DNMJk_v-1yUhkgRpxzP2J iy1Lw8fqIlAk8l9EpDE77oJ9Dal6Rl26GERYZOkCvbq02fKSVj4drlSr75fIce9EnQq2xIVyvvNNty-QvHXTX29QQv-6c8vVYIrCFxtooARN9p8OSpg0hzc- YzsXo64lbUvbLIws27TJNwhctbqrOYQuX9XU3UhJ4Ik0Yt2cLc4LjuqI52Grvf89mJMmM5jnHQv0tKI2guvxNwlC3WN50dCIcuo1zjO-_eSje5OvqP7FKR1e SwnEcZiZQ8qwDDGi8pA"
iftkn ! = want { t.Errorf("wrong token generated. want: %q, got: %q", want, tkn)
}
}
Copy the code
alignment
Update microsvcs/auth/main. Go
We add TokenExpire and TokenGenerator configurations.
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),
})
Copy the code
Small program tuning
Perfect issuance of tokens.
Keep watching, to be continued…
Refs
- API Security : API key is dead.. Long live Distributed Token by value
- Demo: go-grpc-gateway-v2-microservice
- gRPC-Gateway
- gRPC-Gateway Docs
I am weishao wechat: uuhells123 public number: hackers afternoon tea add my wechat (mutual learning exchange), pay attention to the public number (for more learning materials ~)Copy the code