微授权服务器
前置任务
- protoc(翻译proto文件为go文件)
- go grpc包(google.golang.org/grpc)
- sqlite3驱动(github.com/mattn/go-sqlite3)
- redis驱动(github.com/go-redis/redis/v8)
准备任务
需要用到的proto文件
syntax = "proto3";
package main;
option go_package = "/auth;auth"; //这里格式为"/路径;翻译过后的包名",这里/为当前目录,如果就在当前目录生成就一个/就OK了,加了路径名后会自动创建目录
service author
{
rpc auth(request) returns(response);
rpc verfiy(request) returns(response);
rpc logout(request) returns(response);
}
message request
{
string userid = 1;
string passwd = 2;
}
message response
{
int32 code = 3;
string words = 4;
}
命令行使用protoc翻译proto文件(这里不清楚读者的proto文件具体叫什么名,就直接默认翻译本目录下全部proto文件)
protoc -I. --go_out=plugins=grpc:. *.proto
这个时候生成的xxx.pb.go文件中为你自动生成了authorserver的接口,也就是这里指定的三个rpc函数。这里我们继承来这个接口
package main
import (
"context"
"encoding/base32"
"math/rand"
"strconv"
"time"
"github.com/oswaldoooo/examples/authserver/auth"
)
type authserve struct{}
var backup_resources = make(map[string]any)
func (s *authserve) Auth(ctx context.Context, req *auth.Request) (*auth.Response, error) {
row := db.QueryRow("select info from userinfo where userid=? and passwd=?", req.Userid, req.Passwd)
var resp auth.Response
if row != nil {
var info string
err := row.Scan(&info)
if err == nil {
resp.Code = 1
session := base32.StdEncoding.EncodeToString([]byte(strconv.Itoa(rand.Int())))
err = rediscli.Set(context.Background(), session, info, time.Minute*time.Duration(30)).Err()
if err == nil {
resp.Words = session
} else {
backup_resources[session] = info
}
} else {
resp.Code = 2
}
} else {
resp.Code = 2
}
return &resp, nil
}
func (s *authserve) Verfiy(ctx context.Context, req *auth.Request) (*auth.Response, error) {
cmd := rediscli.Get(context.Background(), req.Userid)
var resp auth.Response
if cmd != nil && cmd.Err() == nil {
err := cmd.Scan(&resp.Words)
if err == nil {
resp.Code = 1
} else {
resp.Code = 2
}
} else {
resp.Code = 2
}
if resp.Code == 2 {
if info, ok := backup_resources[req.Userid]; ok {
resp.Code = 1
resp.Words = info.(string)
}
}
return &resp, nil
}
func (s *authserve) Logout(ctx context.Context, req *auth.Request) (*auth.Response, error) {
cmd := rediscli.Get(context.Background(), req.Userid)
var resp auth.Response
if cmd != nil && cmd.Err() == nil {
err := cmd.Scan(&resp.Words)
if err == nil {
resp.Code = 1
err = rediscli.Del(context.Background(), req.Userid).Err()
if err != nil {
errorlog.Println("[redis error]", err.Error())
}
} else {
resp.Code = 2
}
} else {
resp.Code = 2
}
if resp.Code == 2 {
if _, ok := backup_resources[req.Userid]; ok {
resp.Code = 1
delete(backup_resources, req.Userid)
}
}
resp.Code = 1
return &resp, nil
}
有时候接口函数可能有好几个,咋这里用vscode的快捷继承,如果你使用的其它ide也打算使用快捷继承的话,请自行google。
main 函数
package main
import (
"context"
"database/sql"
"flag"
"fmt"
"log"
"net"
"os"
"github.com/go-redis/redis/v8"
_ "github.com/mattn/go-sqlite3"
"github.com/oswaldoooo/examples/authserver/auth"
"google.golang.org/grpc"
)
var db *sql.DB
var rediscli *redis.Client
var errorlog *log.Logger
var redisadd string
func init() {
var err error
db, err = sql.Open("sqlite3", "userinfo.db")
if err == nil {
row := db.QueryRowContext(context.Background(), "select info from userinfo")
if row.Err() == nil {
var info string
if row.Scan(&info) == nil {
return
}
}
_, err = db.ExecContext(context.Background(), "create table userinfo(userid varchar(20) not null UNIQUE,passwd varchar(100) not null,info varchar(100) not null)")
if err != nil {
fmt.Println("[error]", err.Error())
os.Exit(1)
}
}
rediscli = redis.NewClient(&redis.Options{Addr: redisadd})
}
func main() {
errout := flag.String("errout", "", "--errout")
flag.StringVar(&redisadd, "redadd", "localhost:6379", "--redadd xxx:xxx")
flag.Parse()
var finfo *os.File
var err error
if len(*errout) == 0 {
finfo = os.Stderr
} else {
finfo, err = os.OpenFile(*errout, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("[error] open " + *errout + " failed")
finfo = os.Stderr
}
}
errorlog = log.New(finfo, "[error]", log.LUTC|log.Llongfile)
ser := grpc.NewServer()
auth.RegisterAuthorServer(ser, &authserve{})
var listener net.Listener
listener, err = net.Listen("tcp", ":9000")
if err == nil {
errorlog.Println(ser.Serve(listener))
} else {
errorlog.Println(err.Error())
}
errorlog.Println("server exit")
}
测试的客户端代码
package main
import (
"context"
"fmt"
"github.com/oswaldoooo/examples/authserver/auth"
"google.golang.org/grpc"
)
func main() {
con, err := grpc.DialContext(context.Background(), "127.0.0.1:9000", grpc.WithInsecure())
if err == nil {
con.Connect()
cli := auth.NewAuthorClient(con)
var response *auth.Response
response, err = cli.Auth(context.Background(), &auth.Request{Userid: "authserver", Passwd: "123456"})
if err == nil {
if response.Code == 1 {
fmt.Println("get session", response.Words)
response, err = cli.Verfiy(context.Background(), &auth.Request{Userid: response.Words})
if err == nil {
if response.Code == 1 {
fmt.Println("user info is", response.Words)
} else {
fmt.Println("verify session failed")
}
}
} else {
fmt.Println("auth failed")
}
}
}
if err != nil {
fmt.Println("[error]", err.Error())
}
}
看看效果
大体这里就完成了,但是还没完全结束。
- 客户端密码传输前加个密再传避免明文传输(可自行按需添加)
- 注册和注销操作(还是数据库的crud操作,自行添加)
- 这里grpc通信是insecure,本质和裸奔性质差不多。如有需要自行查询grpc go加密操作