A series of

  1. Cloud native API Gateway, GRPC-Gateway V2 Exploration
  2. Go + GRPC-gateway (V2) Construction of micro-service combat series, small program login authentication service: the first chapter
  3. 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:

  1. HEADER:ALGORITHM & TOKEN TYPE
  2. PAYLOAD:DATA
  3. VERIFY 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:tokenThe issuance of time
  • exp:tokenExpiration time
  • sub: 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 astruct(JWTTokenGen)
  • This can be initialized externallystruct(NewJWTTokenGen), make some public and private resources configurable (e.g.rsa.PrivateKey)
  • TokenGeneratorConcrete 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