Golang uses grpc + go kit to simulate OAuth authentication

Time:2022-1-6

We use the external interface of grpc to provide services and simulate the external authentication interface

First, we need to understand the basic authentication process of OAuth

在这里插入图片描述

The third-party server is in oauth2 0 as a client to request data.

Users can select a third-party login. For example, if they choose to log in to a third-party platform, they will jump to the third-party login platform

The user enters the user name and password and logs in on the third-party platform. If the login is successful, code is returned.

The client, that is, the website we want to log in, will read the code and carry the code and the password issued by the third-party website to request a token. If the code and the password obtained during registration are verified successfully, the third-party client will return a token.

The website we log in will carry this token to the server requesting the user’s identity resources. If the token comparison is successful, the user’s information will be returned, so we need some services

Codeserver, function, distribute code, and verify the accuracy of code

Tokenserver is used to distribute tokens and verify the accuracy of tokens

LoginServer, function, login successful, call codeserver to get code

Userdetailserver is used to call the token verification of tokenserver to verify whether the token is legal. If it is legal, return the basic information of the user. Continue. Let’s see how to implement these functions.

realization

codeserver

type Codeserver struc (
	GetCode ()
	ValidCode ()
)
//The specific parameters of the function will not be written today

In fact, our codes and tokens are mainly implemented using the redis database, and the expiration time is set for the applied codes and tokens, that is, a regular function is implemented in the database. If you apply for a code and don’t apply for a token for a long time, the code will expire, allowing users to log in again and obtain the code again

func (s ServicesA) GetCode(c context.Context, req *codeserver.GetCodeReuqest) (*codeserver.RCodeResponse, error) {
	Con, err: = useredis() // load redis for redis operation
	if err != nil {
		return nil , errors.New("the redis databases is not work")
	}
	Randstr: = getrandomstring (10) // randomly generate a string as code
	_ ,  err = con. Do ("hset", req. Userid, "code", randstr) // insert the database for verification when obtaining the token
	con.Do("set" , randstr , req.UserId , "EX" , 120)
	con. Do ("expire", req. Userid, 20) // set the expiration time of code
	if err != nil {
		return nil , errors.New("data is not insert")
	}
	return &codeserver.RCodeResponse{Code: randstr} , nil
}
//Check whether the code is legal
func (s ServicesA) Isvalid(c context.Context, req *codeserver.ValidRequest) (*codeserver.ValidResponse, error) {
	Con, err: = useredis() // load redis
	if err != nil {
		return nil , errors.New("the databses is not work")
	}
	r , err := con. Do ("get", req. Code) // find the code. If you can find the code, it is legal. If you can't find it, it is illegal
	if err != nil {
		return nil , err
	}
	if r == nil {
		return &codeserver.ValidResponse{IsValid: false} , nil
	} else {
		return &codeserver.ValidResponse{IsValid: true} , nil
	}
}

As for other endpoint layers and transport layers, we won’t write them first. We mainly look at how to simulate and implement OAuth in this article

tokenserver

func Isvalid (request *codeserver.ValidRequest) bool {
	lis , err := grpc.Dial("127.0.0.1:8081" , grpc.WithInsecure())
	if err != nil {
		log.Println(err)
		return false
	}
	client := codeserver.NewCodeServerClient(lis)
	rep , err := client.Isvalid(context.Background() , request)
	if err != nil {
		log.Println(err)
		return false
	}
	if rep.IsValid {
		return true
	} else {
		return false
	}
}
func (s ServiceAI) GetToken(ctx context.Context, req *tokenservice.ReqGetToken) (*tokenservice.RepGetToken, error) {
//Judge whether the code is legal
	if !Isvalid(&codeserver.ValidRequest{UserId: req.UserId , Code: req.Code}) {
		return nil , errors.New("code is not valid ")
	}
	con , err := UseRedis()
	if err != nil {
		return nil , errors.New("connet database default")
	}
	//Get ClientID via code
	User := GetUserId(req.Code)
	mysql , err := UseMysql()
	if err != nil {
		log.Println("get secrete default")
	}
	var c Client
	mysql.Table("client").Where("id = ?",req.ClientId).Find(&c)
//Search the MySQL database to see whether the password carried by the request is the same as the password given by the third party when registering. If it is different, no token will be returned.
	if c.Secret !=req.Secret {
		fmt.Println(c.Secret , " " , req.Secret)
		return nil , errors.New("not pi pei")
	}
	str := GetRandomString(11)
	_ , err = con.Do("hset" , User , "token" ,  str)
	con.Do("EXPIRE" , User , 120)
	//Insert the generated token into the database and set the expiration time. If the token is not used for many times
	con.Do("set" , str , User , "EX" , 120)
	//Set the correspondence between userid and token to avoid no correspondence. After the client gets the token, it can casually take other people's user data
	if err != nil {
		return nil , err
	}
	return &tokenservice.RepGetToken{Toen: str} , nil
}
//Judge whether the token is legal and give it to the userdetailserver. When the server receives the token, it needs to call this interface to check whether the token is legal. If it is legal, return the user data
func (s ServiceAI) IsValidToken(ctx context.Context, req *tokenservice.IsValidTokenReq) (*tokenservice.IsValidToeknRep, error) {
	con , err := UseRedis()
	if err != nil {
		log.Println(err)
		return nil , err
	}
	r , err := con.Do("get" ,req.Token)
	if err != nil {
		return nil , err
	}
	if r == nil {
		return &tokenservice.IsValidToeknRep{IsValid: false} , nil
	}
	rep := string(r.([]uint8))
	return &tokenservice.IsValidToeknRep{IsValid: true , Userid: rep} , nil
}

useroauthserver

type User struct {
	Id int
	Name string
	Password string
	Al string
	UId string
}
func usemysql () (*gorm.DB , error) {
	return gorm.Open("mysql" , "root:[email protected]/oauth?charset=utf8&parseTime=True&loc=Local")
}
//Call the codeserver interface to get the code
func getcode (userid string) string {
	con , err := grpc.Dial(":8081" , grpc.WithInsecure())
	if err != nil {
		log.Println(err , errors.New("get code default"))
	}
	client := codeserver.NewCodeServerClient(con)
	rep , err := client.GetCode(context.Background() , &codeserver.GetCodeReuqest{UserId: userid})
	if err != nil || rep == nil{
		log.Println(err)
		return ""
	}
	return rep.Code
}
//Authenticate the user and compare the uploaded user name with the password.
func (a AuthServicesA) AuthT(ctx context.Context, req *userauth.AuthRequest) (*userauth.AuthResponse, error) {
	con , err := usemysql()
	if err != nil {
		log.Println(err)
		return nil , errors.New("the database is connect default")
	}
	var u User
	con.Table("user").Where("uid =?" , req.Id).Find(&u)
	//Search in the database. If the user is not found, it means that the user does not exist or the user input is wrong
	if &u == nil {
		return nil , errors.New("the id is wrong ")
	}
	if req.Password != u.Password {
		return nil , errors.New("the user password is wrong")
	}
//If the authentication is successful, call the codeserver interface and return code
	code :=getcode(req.Id)
	if code == "" {
		return &userauth.AuthResponse{IsTrue: false} , nil
	}
	return &userauth.AuthResponse{Code: code , IsTrue: true} , nil
}

This is the basic principle, but we still need a userdetail server

The main function of this server is to get the requested token and verify it. If the verification is successful, the user data will be returned. As for how to verify, it is to call the verification interface in the tokenserver.

I won’t write here. I’ll leave it to the reader.

The three interfaces I wrote have source code on gitee and are written based on golang. The frameworks used include grpc and go kit service frameworks.

Specific addressgitee.com/silves-xiang

Supplement: go kit practice 2: go kit realizes registration discovery and load balancing

1、 Introduction

Grpc provides simple load balancing and needs to implement service discovery resolve by itself. Since we want to use go kit to manage micro services, we will use the registration, discovery and load balancing mechanism of go kit.

The load balancing scheme used in the official [stringsvc3] example of go kit is forwarded by the server. Find the service registration of the source code go kit and find that the load balancing is in the [SD] package. Next, we will introduce how to balance the client load through go kit.

Registry provided by go Kit

1、 etcd

2、 consul

3、 eureka

4、 zookeeper

Load balancing provided by go Kit

1. Random [random]

2. Roundrobin [polling]

By implementing the balancer interface, we can easily add other load balancing mechanisms


type Balancer interface {  
   Endpoint() (endpoint.Endpoint, error)  
}

Etcd registration discovery

Etcd is similar to zookeeper in that it is a highly available and highly consistent repository with service discovery capabilities. We implement service registration and discovery through the etcd package provided by go kit

2、 Example

1. Protobuf file and generate corresponding go file

syntax = "proto3";
 
//Parameter structure of request details book_ ID 32-bit shaping
message BookInfoParams {
    int32 book_id = 1;
} 
 
//Structure of book details_ Name string type
message BookInfo {
    int32 book_id = 1;
    string  book_name = 2;
}
 
//Parameter structure of request list page, limit 32-bit integer
message BookListParams {
    int32 page = 1;
    int32 limit = 2;
}
  
//Bookinfo structure array of book lists
message BookList {
    repeated BookInfo book_list = 1;
}
//Define the structure defined above for obtaining book details and book list service input and output parameters
service BookService {
    rpc GetBookInfo (BookInfoParams) returns (BookInfo) {}
    rpc GetBookList (BookListParams) returns (BookList) {}
}

Generate the corresponding go language code file: protocol — go_ out=plugins=grpc:. book. Proto (where: protobuf file name: book. Proto)

2. Server side code

package main 
import (
	"MyKit"
	"context"
	"fmt"
	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/sd/etcdv3"
	grpc_transport "github.com/go-kit/kit/transport/grpc"
	"google.golang.org/grpc"
	"net"
	"time"
)
 
type BookServer struct {
	bookListHandler grpc_transport.Handler
	bookInfoHandler grpc_transport.Handler
}
 
//The following two methods implement the interface corresponding to the go file generated by protocol:
/*
// BookServiceServer is the server API for BookService service.
type BookServiceServer interface {
	GetBookInfo(context.Context, *BookInfoParams) (*BookInfo, error)
	GetBookList(context.Context, *BookListParams) (*BookList, error)
}
*/
//When calling getbookinfo through grpc, getbookinfo only does data transparent transmission and calls the corresponding handler in bookserver Servegrpc is transferred to go kit for processing
func (s *BookServer) GetBookInfo(ctx context.Context, in *book.BookInfoParams) (*book.BookInfo, error) {
 
	_, rsp, err := s.bookInfoHandler.ServeGRPC(ctx, in)
	if err != nil {
		return nil, err
 
	}
	/*
	if info,ok:=rsp.(*book.BookInfo);ok {
		return info,nil
	}
	return nil,errors. New ("RSP. (* book. Bookinfo) assertion error")
	*/
	return rsp. (* book. Bookinfo), err // the assertion result is returned directly
}
 
//When calling getbooklist through grpc, getbooklist only does data transparent transmission and calls the corresponding handler in bookserver Servegrpc is transferred to go kit for processing
func (s *BookServer) GetBookList(ctx context.Context, in *book.BookListParams) (*book.BookList, error) {
	_, rsp, err := s.bookListHandler.ServeGRPC(ctx, in)
	if err != nil {
		return nil, err
	}
	return rsp.(*book.BookList), err
}
 
//Create endpoint for booklist
func makeGetBookListEndpoint()endpoint.Endpoint  {
	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
		b:=new(book.BookList)
		b. Booklist = append (B. booklist, & book. Bookinfo {bookid: 1, bookname: "go language introduction to Mastery"})
		b. Booklist = append (b.booklist, & book. Bookinfo {bookid: 2, bookname: "getting started with microservices to mastering"})
		b. Booklist = append (b.booklist, & book. Bookinfo {bookid: 2, bookname: "blockchain introduction to Mastery"})
		return b,nil
	}
}
 
//Create an endpoint for bookinfo
func makeGetBookInfoEndpoint() endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		//Return book information when requesting details
		req := request.(*book.BookInfoParams)
		b := new(book.BookInfo)
		b.BookId = req.BookId
		b. Bookname = "go getting started to master"
		return b, nil
	}
}
 
func decodeRequest(_ context.Context, req interface{}) (interface{}, error) {
	return req, nil
}
 
func encodeResponse(_ context.Context, rsp interface{}) (interface{}, error) {
	return rsp, nil
}
 
func main() {
	var (
		Etcdserver = "127.0.0.1:2379" // IP address of etcd service
		Prefix = "/ services / book /" // directory of the service
		Serverinstance = "127.0.0.1:50052" // the address of the current instance server
		Key = prefix + serverinstance // the registered path of the service instance
		value          = ServerInstance
		ctx            = context.Background()
		//Service listening address
		serviceAddress = ":50052"
	)
	//Etcd connection parameters
	option := etcdv3.ClientOptions{DialTimeout: time.Second * 3, DialKeepAlive: time.Second * 3}
	//Create connection
	client, err := etcdv3.NewClient(ctx, []string{etcdServer}, option)
	if err != nil {
		panic(err)
	}
	//Create registration
	registrar := etcdv3.NewRegistrar(client, etcdv3.Service{Key: key, Value: value}, log.NewNopLogger())
	registrar. Register() // start the registration service
	bookServer := new(BookServer)
	bookListHandler := grpc_transport.NewServer(
		makeGetBookListEndpoint(),
		decodeRequest,
		encodeResponse,
	)
	bookServer.bookListHandler = bookListHandler
 
	bookInfoHandler := grpc_transport.NewServer(
		makeGetBookInfoEndpoint(),
		decodeRequest,
		encodeResponse,
	)
	bookServer.bookInfoHandler = bookInfoHandler
 
	listener, err := net. Listen ("TCP", serviceaddress) // network listening. Note that the corresponding packet is: "net"
	if err != nil {
		fmt.Println(err)
		return
	}
	gs := grpc.NewServer(grpc.UnaryInterceptor(grpc_transport.Interceptor))
	book. Registerbookserviceserver (GS, bookserver) // call the registration method corresponding to the code generated by protocol
	gs. Serve (listener) // start the server
 
}

3. Client side code

package main 
import (
	"MyKit"
	"context"
	"fmt"
	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/sd"
	"github.com/go-kit/kit/sd/etcdv3"
	"github.com/go-kit/kit/sd/lb"
	"google.golang.org/grpc"
	"io"
	"time"
)
 
func main() {
 
	var (
		//Address of Registration Center
		etcdServer = "127.0.0.1:2379"
		//Listening service prefix
		prefix = "/services/book/"
		ctx    = context.Background()
	)
	options := etcdv3.ClientOptions{
		DialTimeout:   time.Second * 3,
		DialKeepAlive: time.Second * 3,
	}
	//Connect to the registry
	client, err := etcdv3.NewClient(ctx, []string{etcdServer}, options)
	if err != nil {
		panic(err)
	}
	logger := log.NewNopLogger()
	//Create an instance manager, which will listen to the directory changes of prefix in etc and update the cached service instance data
	instancer, err := etcdv3.NewInstancer(client, prefix, logger)
	if err != nil {
		panic(err)
	}
	//Create an endpoint manager that dynamically updates the endpoint created by the factory according to the changes of the factory and the monitored instances and subscribes to the instancer
	endpointer := sd. Newendpoint (instancer, reqfactory, logger) // reqfactory is a user-defined function, which is mainly used for receiving and displaying data in the endpoint layer (endpoint)
	//Create load balancer
	balancer := lb.NewRoundRobin(endpointer)
 
	/**
	We can directly obtain the requested endpoint through the load balancer and initiate the request
	reqEndPoint,_ := balancer.Endpoint()
	*/
 
	/**
	You can also define the number of attempts to make a request through retry
	*/
	reqEndPoint := lb.Retry(3, 3*time.Second, balancer)
 
	//Now we can make a request through endpoint
	req := struct{}{}
	if _, err = reqEndPoint(ctx, req); err != nil {
		panic(err)
	}
}
 
//Create the corresponding request endpoint through the incoming instance address
func reqFactory(instanceAddr string) (endpoint.Endpoint, io.Closer, error) {
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		fmt. Println ("request service:", instanceaddr)
		conn, err := grpc.Dial(instanceAddr, grpc.WithInsecure())
		if err != nil {
			fmt.Println(err)
			panic("connect error")
		}
		defer conn.Close()
		bookClient := book.NewBookServiceClient(conn)
		bi, _ := bookClient.GetBookInfo(context.Background(), &book.BookInfoParams{BookId: 1})
		fmt. Println ("get book details")
		fmt.Println("bookId: 1", " => ", "bookName:", bi.BookName)
 
		bl, _ := bookClient.GetBookList(context.Background(), &book.BookListParams{Page: 1, Limit: 10})
		fmt. Println ("get book list")
		for _, b := range bl.BookList {
			fmt.Println("bookId:", b.BookId, " => ", "bookName:", b.BookName)
		}
		return nil, nil
	}, nil, nil
}

4. Run

(1) Install etcd and start

Because etcd is found to be used in this instance service, you need to install etcd and run it before running.

(2) Etcd is a distributed consistent key value store, which is mainly used for shared configuration and service discovery of distributed systems. Etcd is written in go language

Download address: https://github.com/coreos/etcd/releases

Unzip the compressed file to the specified folder. The unzipped directory is as follows:

Etcd Exe is the server, etcdctl Exe is a client. Click etcd Exe to run the etcd service. (Note: setting the environment variable is free to decide, and this instance can also be set without setting.)

(2) Instance run

Run the server side first and the client side. The effect is as follows:

5. Problem summary

If you run, you will be prompted with an error:


panic: /debug/requests is already registered. You may have two independent copies of golang.org/x/net/trace in your binary, trying to maintain separate state. This may involve a vendored copy of golang.org/
x/net/trace.
 
goroutine 1 [running]:
go.etcd.io/etcd/vendor/golang.org/x/net/trace.init.0()
        D:/GoSrc/src/go.etcd.io/etcd/vendor/golang.org/x/net/trace/trace.go:116 +0x1ab
exit status 2

Description golang Trace and go. Org / X / net / etcd. io/etcd/vendor/golang. There is a conflict in trace under org / X / net / package. Solution: find go etcd. IO \ etcd \ vendor Directory:

Because golang. Com already exists in the SRC directory Org and Google golang. Org two packages

The above is my personal experience. I hope I can give you a reference, and I hope you can support developpaer. If you have any mistakes or don’t consider completely, please don’t hesitate to comment.