基于go-micro微服务的实战-Gateway网关层的身份认证(五)

基于go-micro微服务的实战-Gateway网关层的身份认证(五)

文章最后附带完整代码

这节主要是如何实现身份的认证,身份认证可以放在网关层,也可以放在单独服务,具体放哪里认证需要根据自己的应用场景,本节是放在Gateway网关层,提供统一的认证入口,通过再转发服务。

认证用的是jwt(json-web-token),基于第三方库"github.com/golang-jwt/jwt

第一步:封装实现jwt的生成和验证接口函数

grpc_gateway的工具目录utils新创建一个jwt.go文件,封装jwt的生成和验证,并复制一份到grpc_user/user的工具目录中。
具体的jwt其它用法可参考github.com/golang-jwt/jwt

导入第三方包

import (
	"github.com/golang-jwt/jwt"
)

声明认证结构体和秘钥,结构体中的属性字段自定义,这里只用了用户id(UserId)

type CustomClaims struct {
	UserId int32
	jwt.StandardClaims
}

const (
	secret = "123456"
)

生成jwt

func GetToken(user_id int32, expire int64) (string, error) {
	claims := CustomClaims{
		user_id,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Unix()+expire,
			Issuer:    "admin",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenStr, err := token.SignedString([]byte(secret))
	return tokenStr, err
}

验证jwt

func VerityToken(token string) error{
	if token == ""{
		return errors.New("auth token empty")
	}
	tokens, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(secret), nil
	})

	if claims, ok := tokens.Claims.(jwt.MapClaims); ok && tokens.Valid {
		if int(claims["UserId"].(float64)) <= 0 {
			return errors.New("auth token: data is 0")
		}
		return nil
	} else {
		fmt.Println("auth token:", err)
		return err
	}
}

获取jwt中自定义属性值

func GetJwtData(token string) (int32, error){
	tokens, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(secret), nil
	})
	if claims, ok := tokens.Claims.(jwt.MapClaims); ok{
		return int32(claims["UserId"].(float64)), nil
	}

	return 0,  err
}
第二步:在用户服务的登录接口返回jwt的token信息

修改user.proto,把user_id改成token,执行pb.bat生成编译

message LoginResp{
    int32 status = 1;
    string token = 2;
    string msg = 3;
}

用户服务的登录接口返回token,这里只贴部分代码,基于第4节 的登录接口,调整UserId为Token

    models.Db.Table("user").Select("user.user_id, user.pwd").Where("user.phone = ?", req.Phone).Scan(&res)
	if utils.Md5(req.Pwd) != res.Pwd {
		resp.Status = common.RESP_ERROR
		resp.Msg = "auth error"
		return nil
	}

	resp.Status = common.RESP_SUCCESS
	resp.Token, _ = utils.GetToken(res.UserId, 600)
	resp.Msg = "success"
	return nil
第三步:在网关层Gateway实现身份认证

同步第二步的user.proto修改到grpc_gatewayuser.proto,执行pb.bat生成

token都是放在Http请求体头部的Authorization字段,通过解析该字段验证。这里我们把测试的认证放在TestUserGet测试,验证不通过则不能请求用户服务,在网关这里就被拦截掉。

func TestUserGet(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	//验证token
	token := r.Header.Get("Authorization")
	if err :=utils.VerityToken(token); err!=nil{
		http.Error(w, err.Error(), 500)
		return
	}

其它需要验证的网关层接口函数,只需贴上该代码即可

	//验证token
	token := r.Header.Get("Authorization")
	if err :=utils.VerityToken(token); err!=nil{
		http.Error(w, err.Error(), 500)
		return
	}

当然,如果需要传递token带的用户id等属性到用户服务,可以把token放进上下文,带到用户服务解析,怎么放到上下文?

把token通过元数据metadata,类似于 HTTP 请求中的 Cookie 数据,采用的键值对列表的形式。以下是在TestUserGet接口上修改,在用户服务接口TestUser可以通过utils.GetJwtData获取到用户id
注意,这里导入的包是micro v3提供的github.com/asim/go-micro/v3/metadata,并非直接用grpc提供的google.golang.org/grpc/metadata

import "github.com/asim/go-micro/v3/metadata"

ctx := metadata.Set(context.Background(), "token", token)
resp, err := service.TestUser(ctx, &pb.TestReq{Id:int32(user_id)})
第四步:启动网关服务和用户服务

启动服务基于前面几节细讲,执行gateway.bat或者gateway.sh启动网关服务,执行user.bat或者user.sh启动用户服务

先登录,获取token,写到文件里(为了测试用)

ioutil.WriteFile("log.txt", []byte(string(resp.Token)), 0655)

先用ApiPost登录,获取token,如下图
login

这里再写一个http请求客户端grpc_gateway/client/test.go,读取log.txt的token发起请求TestUserGet接口

	client := &http.Client{
		Timeout: 3 * time.Second,
	}

	req, err := http.NewRequest("GET", "http://localhost:55001/user/test/11", nil)
	
    //头部带上token
    token, _ := ioutil.ReadFile("../log.txt")
	req.Header.Add("Authorization", string(token))

	if err != nil {
		fmt.Println(err)
	}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(body))

req

gitee完整代码链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值