您现在的位置是:网站首页> 编程资料编程资料
golang使用grpc+go-kit模拟oauth认证的操作_Golang_
2023-05-26
446人已围观
简介 golang使用grpc+go-kit模拟oauth认证的操作_Golang_
我们使用grpc对外的接口,进行服务,模拟对外认证的接口
首先我们要了解oauth的基本认证过程

第三方的服务端,在oauth2.0中作为一个客户端的身份,进行请求数据。
用户进行选择第三方的登陆,比如选择到某一个第三方的平台进行登陆,则会跳转到第三方登陆平台
用户输入用户名密码,在第三方平台进行登陆,,如果登陆成功,则返回code。
客户端,也就是我们想要登陆的网站,将会读取code,并且将会携带这个code,和第三方网站所颁发的密码,进行请求token,如果code和注册时所得到的密码,都验证成功,此时,第三方客户端会返回一个token。
我们登陆的网站会携带这个token去请求用户身份资源的服务器,如果token比对成功,则返回用户的信息所以我们需要一些服务
codeserver,作用,分发code,验证code的准确性
tokenserver,作用分发token,验证token的准确性
loginserver,作用,登陆成功后,调用codeserver得到code
userdetailserver,作用调用tokenserver的token验证,验证token是否合法,如果合法,进行返回用户的基本信息继续,我们大概看一下这些功能具体怎样实现。
实现
codeserver
type Codeserver struc ( GetCode () ValidCode () ) //函数的具体传参今不写了
其实我们的code和token,主要是使用redis数据库进行实现,并且给申请的code和token设置过期时间, 也就是说,在数据库中实现一个定时的作用,如果,申请完code,长时间不申请token则这个code会过期,就会让用户重新进行登陆,重新获取code
func (s ServicesA) GetCode(c context.Context, req *codeserver.GetCodeReuqest) (*codeserver.RCodeResponse, error) { con , err := UseRedis()//加载redis,用于操作redis if err != nil { return nil , errors.New("the redis databases is not work") } randstr := GetRandomString(10)//随机生成一个字符串作为code _ , err = con.Do("hset" , req.UserId , "code" , randstr)//插入数据库,用于获取token时进行验证 con.Do("set" , randstr , req.UserId , "EX" , 120) con.Do("EXPIRE" , req.UserId , 20)//设置code的过期时间 if err != nil { return nil , errors.New("data is not insert") } return &codeserver.RCodeResponse{Code: randstr} , nil } //检查code是否合法 func (s ServicesA) Isvalid(c context.Context, req *codeserver.ValidRequest) (*codeserver.ValidResponse, error) { con , err := UseRedis()//加载redis if err != nil { return nil , errors.New("the databses is not work") } r , err := con.Do("get" , req.Code)//找到code,如果能找到code,则合法,找不到则不合法 if err != nil { return nil , err } if r == nil { return &codeserver.ValidResponse{IsValid: false} , nil } else { return &codeserver.ValidResponse{IsValid: true} , nil } } 至于其他的endpoint层和transport层等等,就先不写了,我们就这篇文章主要是看怎样模拟实现oauth
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) { //判断code是否合法 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") } //通过code获取clientid 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) //在mysql数据库中进行查找,请求所携带的密码,是否与第三方注册时给的密码是否相同,如果不相同,则不返回token。 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) //将生成的token进行插入数据库,并设置过期时间,如果避免token被多次利用 con.Do("set" , str , User , "EX" , 120) //设置userid和token的对应关系,避免没有对应上,客户端拿到token之后随便拿取其他人的用户滤数据 if err != nil { return nil , err } return &tokenservice.RepGetToken{Toen: str} , nil } //判断token是都合法,给userdetailserver用,当服务器接到token后,需要调用这个接口,查看token是否合法,如果合法返回用户数据 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:123456@/oauth?charset=utf8&parseTime=True&loc=Local") } //调用codeserver接口,进行拿取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 } //认证用户,将上传的用户名和密码进行比对。 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) //在数据库中进行查找,如果没找到该用户,说明该用户不存在,或者用户输入错误 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") } //如果认证成功,则进行调用codeserver接口,返回code code :=getcode(req.Id) if code == "" { return &userauth.AuthResponse{IsTrue: false} , nil } return &userauth.AuthResponse{Code: code , IsTrue: true} , nil } 基本原理就是这样,但是我们还是差一个userdetail的服务端
这个服务端,主要作用就是拿到请求的token,并进行检验,如果检验成功,返回用户数据,至于怎样检验,就是调用tokenserver中的检验接口。
这里就不写了,留给读者完成。
我写的这三个接口在gitee上有源码,是基于golang写的,使用的框架有grpc,go-kit的服务框架。
补充:go-kit实践之2:go-kit 实现注册发现与负载均衡
一、介绍
grpc提供了简单的负载均衡,需要自己实现服务发现resolve。我们既然要使用go-kit来治理微服务,那么我们就使用go-kit的注册发现、负载均衡机制。
go-kit官方【stringsvc3】例子中使用的负载均衡方案是通过服务端转发进行,翻找下源码go-kit的服务注册发现、负载均衡在【sd】包中。下面我们介绍怎么通过go-kit进行客户端负载均衡。
go-kit提供的注册中心
1、 etcd
2、 consul
3、 eureka
4、 zookeeper
go-kit提供的负载均衡
1、 random[随机]
2、 roundRobin[轮询]
只需实现Balancer接口,我们可以很容易的增加其它负载均衡机制
type Balancer interface { Endpoint() (endpoint.Endpoint, error) }etcd注册发现
etcd和zookeeper类似是一个高可用、强一致性的存储仓库,拥有服务发现功能。 我们就通过go-kit提供的etcd包来实现服务注册发现
二、示例
1、protobuf文件及生成对应的go文件
syntax = "proto3"; // 请求书详情的参数结构 book_id 32位整形 message BookInfoParams { int32 book_id = 1; } // 书详情信息的结构 book_name字符串类型 message BookInfo { int32 book_id = 1; string book_name = 2; } // 请求书列表的参数结构 page、limit 32位整形 message BookListParams { int32 page = 1; int32 limit = 2; } // 书列表的结构 BookInfo结构数组 message BookList { repeated BookInfo book_list = 1; } // 定义 获取书详情 和 书列表服务 入参出参分别为上面所定义的结构 service BookService { rpc GetBookInfo (BookInfoParams) returns (BookInfo) {} rpc GetBookList (BookListParams) returns (BookList) {} }生成对应的go语言代码文件:protoc --go_out=plugins=grpc:. book.proto (其中:protobuf文件名为:book.proto)
2、Server端代码
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 } //一下两个方法实现了 protoc生成go文件对应的接口: /* // BookServiceServer is the server API for BookService service. type BookServiceServer interface { GetBookInfo(context.Context, *BookInfoParams) (*BookInfo, error) GetBookList(context.Context, *BookListParams) (*BookList, error) } */ //通过grpc调用GetBookInfo时,GetBookInfo只做数据透传, 调用BookServer中对应Handler.ServeGRPC转交给go-kit处理 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)断言出错") */ return rsp.(*book.BookInfo), err //直接返回断言的结果 } //通过grpc调用GetBookList时,GetBookList只做数据透传, 调用BookServer中对应Handler.ServeGRPC转交给go-kit处理 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 } //创建bookList的EndPoint 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语言入门到精通"}) b.BookList=append(b.BookList,&book.BookInfo{BookId:2,BookName:"微服务入门到精通"}) b.BookList=append(b.BookList,&book.BookInfo{BookId:2,BookName:"区块链入门到精通"}) return b,nil } } //创建bookInfo的EndPoint func makeGetBookInfoEndpoint() endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { //请求详情时返回 书籍信息 req := request.(*book.BookInfoParams) b := new(book.BookInfo) b.BookId = req.BookId b.BookName = "Go入门到精通" 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" //etcd服务的IP地址 prefix = "/services/book/" //服务的目录 ServerInstance = "127.0.0.1:50052" //当前实例Server的地址 key = prefix + ServerInstance //服务实例注册的路径 value = ServerInstance ctx = context.Background() //服务监听地址 serviceAddress = ":50052" ) //etcd连接参数 option := etcdv3
相关内容
- go如何删除字符串中的部分字符_Golang_
- gorm update传入struct对象,零值字段不更新的解决方案_Golang_
- 一文完全掌握 Go math/rand(源码解析)_Golang_
- go实现for range迭代时修改值的操作_Golang_
- golang中for range的取地址操作陷阱介绍_Golang_
- golang如何去除多余空白字符(含制表符)_Golang_
- 用golang如何替换某个文件中的字符串_Golang_
- golang正则之命名分组方式_Golang_
- Golang 正则匹配效率详解_Golang_
- go原生库的中bytes.Buffer用法_Golang_
点击排行
本栏推荐
