详讲api网关之kong的基本概念及安装和使用(二)

consul的服务注册与发现 如果不知道consul的使用,可以点击上方链接,这是我写的关于consul的一篇文档。

upstream+consul实现负载均衡

我们知道,配置upstream可以实现负载均衡,而consul实现了服务注册与发现,那么接下来我们就来看看利用这两个组件怎么实现负载均衡吧。

安装consul

  • docker安装consul,注意端口,注意将consul的网络加入到kong的网络中,即--network=kong-ee-net
ini 复制代码
docker run -d --name consul \
  --network=kong-ee-net \
  -p 8500:8500 \
  -p 8300:8309 \
  -p 8301:8301 \
  -p8302:8302 \
  -p 8600:8600/udp \
  hashicorp/consul consul agent -dev -client=0.0.0.0

gin服务注册到consul

  • 我们启动3个gin服务,并注册到consul中,相关代码如下,注意,服务端口是随机获取的,并注册到consul中,我们几乎不需要关心端口是多少
go 复制代码
package main
import (
	"fmt"
	"gin-demo/config"
	"gin-demo/db"
	"gin-demo/route"
	"gin-demo/utils"
	"github.com/fvbock/endless"
	consulapi "github.com/hashicorp/consul/api"
)
// 定义服务注册的信息
type ServiceConfig struct {
	ID      string
	Name    string
	Tags    []string
	Port    int
	Address string
}

// consul的服务地址
var consulIp = "127.0.0.1:8500"

// 注册
func RegisterSevice(s ServiceConfig) error {
	consulConfig := consulapi.DefaultConfig()
	consulConfig.Address = consulIp

	//获取到客户端
	client, err := consulapi.NewClient(consulConfig)
	if err != nil {
		fmt.Printf("create consul client : %v\n", err.Error())
		return err
	}
	registration := &consulapi.AgentServiceRegistration{
		ID:      s.ID,
		Name:    s.Name,
		Port:    s.Port,
		Tags:    s.Tags,
		Address: s.Address,
	}

	if err := client.Agent().ServiceRegister(registration); err != nil {
		fmt.Printf("register to consul error: %v\n", err.Error())
		return err
	}
	return nil
}

func main() {
	//配置文件
	config.InitConfig()
	//sql初始化
	db.NewDb().InitAllSql()
	//启动
	r := route.InitRoute()
	//随机获取一个可用端口
	_, port := utils.RandomPort()

	//注册到consul
	consulPort := fmt.Sprintf("%d", port)
	service := ServiceConfig{
		ID:      consulPort,
		Name:    "blog_service",
		Tags:    []string{"blog"},
		Port:    port,
		Address: "192.168.11.20",
	}
	err := RegisterSevice(service)
	if err != nil {
		fmt.Printf("register to consul error: %v\n", err.Error())
	}

	//启动gin服务
	addr := fmt.Sprintf(":%d", port)
	err = endless.ListenAndServe(addr, r)
	if err != nil {
		return
	}
}
  • 启动完毕之后,我们访问consul的web页面已注册成功http://127.0.0.1:8500/ui/dc1/services/blog_service/instances

  • 我们使用dns的方式访问一下

css 复制代码
dig @127.0.0.1 -p 8600 blog_service.service.consul

kong里面配置consul

  • 通过docker network inspect kong-ee-net 查看consul的ip为172.23.0.4
  • 这里我们需要删除原来的kong容器,并使用以下命令创建一个新看kong,需要注意的是,由于你指定了KONG_DNS_RESOLVER,那么原来指定的KONG_PG_HOST就不可以再用dns的方式,因为consul里面并没有这个域名,会导致服务启动报错。所以要直接指定ip。
ini 复制代码
docker run -d --name kong \
  --network=kong-ee-net \
  -e "KONG_DATABASE=postgres" \
  -e "KONG_DNS_RESOLVER=192.168.11.20:8600" \
  -e "KONG_PG_HOST=172.23.0.2" \
  -e "KONG_PG_USER=kong" \
  -e "KONG_PG_PASSWORD=kong" \
  -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
  -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
  -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
  -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
  -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
  -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
  -p 8000:8000 \
  -p 8443:8443 \
  -p 8001:8001 \
  -p 8444:8444 \
  kong-oss:latest
  • 启动之后,我们会再konga里面看到下面这行,这个时候,我们在kong里面配置的域名都会被 consul解析

  • 我们配置blog_service.service.consul给service,就是咱们原来创建的blog的service

  • 我们现在多访问几次接口 http://127.0.0.1:8000/v1/blog/detail?id=12,返回数据正常,我们多访问几次,查看三台服务的日志,均收到了请求

jwt实现权限校验

一般情况下,上游api服务都需要客户端有身份验证,并且不影响错误的请求或者无认证的请求通过,那么此时,我们可以利用kong的jwt插件来实现,直接在网关这一层过滤掉请求

consumers

一个consumer可以代表一个微服务或者一个应用。为什么?因为你想给一个应用配置插件服务的话,比如说jwt认证,这个插件怎么样才能作用到这个应用上面呢?就是需要创建一个consumer,插件作用于consumer,consumer绑定应用。

  • 接下来我们创建一个consumer
  • 添加jwt认证
  • 全局配置jwt,配置header头的token名
  • 此时我们再来访问http://127.0.0.1:8000/v1/blog/detail?id=12,已经不通过了
  • 那么我们先手动构造一个token来验证一下,有两点需要注意,一个是jwt,是我们配置的验证名称,一个是iss
  • 我们通过https://jwt.io/生成token,payload如下,注意secret去consumer中获取
json 复制代码
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "iss":"jwt"
}

项目中引入jwt

  • 关于怎么使用jwt,我在jwt讲解这篇文章中说过。
  • 注意两个地方,一个是jwt的秘钥kong和gin必须保持一致
  • 一个是issuer必须保持一致,相关代码如下
go 复制代码
package middleware

import (
	"gin-demo/service"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"log"
	"strings"
	"time"
)

// 定义自己的秘钥 所有的服务必须用一个秘钥才能正确解析token
// 注意注意:这个秘钥必须和kong保持一致
var privateKey = []byte("FLHzZzGqZPlSzKgNyq4wXg4bKUhFRsH1")

// UserClaims 我们声明一个结构体,里面包含我们想要保存的信息
type UserClaims struct {
	Uid                  int64
	jwt.RegisteredClaims // 内嵌标准的声明
}

// GenToken 生成token
func GenToken(uid int64) (string, error) {
	//初始化结构体
	claims := UserClaims{
		Uid: uid,
		RegisteredClaims: jwt.RegisteredClaims{
			//设置过期时间
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * 3600)),
			//颁发时间
			IssuedAt: jwt.NewNumericDate(time.Now()),
			//主题
			Subject: "Token",
			//注意这里 发行人要和kong保持一致
			Issuer: "jwt",
		},
	}
	//生成token  使用hs256 加密 结构体,然后再用秘钥对其做数字签名
	return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(privateKey)
}

// 解析token
func ParseToken(c *gin.Context) {
	claims := new(UserClaims)
	xtoken := c.GetHeader("x-token")
	tokenString := strings.Split(xtoken, " ")
	if len(xtoken) == 0 || len(tokenString) < 2 {
		service.ErrorResponse(c, 9999, "请先登录!")
		return
	}
	log.Printf("解析到的token是:%v", tokenString)
	token, err := jwt.ParseWithClaims(tokenString[1], claims, keyFunc)
	log.Printf("解析到的token是:%v", err)
	if err != nil {
		c.Abort()
		service.ErrorResponse(c, 9999, "用户名密码错误!")
		return
	}
	//建议token是否有效
	if token.Valid {
		log.Printf("解析到的uid是:%d", claims.Uid)
		c.Set("uid", claims.Uid)
		c.Next()
	} else {
		c.Abort()
		service.ErrorResponse(c, 9999, "用户名密码错误!")
		return
	}
}

func keyFunc(token *jwt.Token) (interface{}, error) {
	return privateKey, nil
}
  • 结果

  • 失败结果,kong直接拦截,并未到达服务层实例

流量控制

  • 我们现在来配置一个每秒接收5个请求的限制
  • 然后我们用jmeter测试一下,一秒内模拟10个用户发送10个请求
  • 查看结果

黑白名单

  • 我们可以选择指定给service或者route添加插件,下图我给route添加了插件
  • 再次访问接口

总结

kong的相关功能就演示完毕了,其他功能比如反爬策略,协议转换感兴趣的小伙伴们可以自己探索一下。

相关参考

相关参考 相关参考 官方文档 中文文档 相关文档 相关文档

相关推荐
清尘沐歌2 小时前
有什么好用的 WebSocket 测试工具吗?
websocket·网络协议·测试工具
清尘沐歌2 小时前
有什么好用的 WebSocket 调试工具吗?
网络·websocket·网络协议
earthzhang20213 小时前
《深入浅出HTTPS》读书笔记(7):安全的密码学Hash算法
网络·网络协议·http·https·1024程序员节
找藉口是失败者的习惯4 小时前
探索 HTTP 请求方法:GET、POST、PUT、DELETE 等的用法详解
网络·网络协议·http
vortex54 小时前
HTTP 协议及内外网划分详解
网络·网络协议·http·网络安全
莫轻言舞4 小时前
Java Http 接口对接太繁琐?试试 UniHttp 框架吧
网络·网络协议·http
卜及中4 小时前
理解HTTP中的Cookie与Session:机制、安全性与报头响应
网络·网络协议·http
群联云防护小杜5 小时前
CC攻击为什么难防御?
网络·网络协议·安全·web安全·udp
专注VB编程开发20年5 小时前
一篇介绍 Websocket 和 Http 的很好的帖子
websocket·网络协议·http·聊天协议·传输文件
API小知识6 小时前
Python中哪个框架最适合做API?
前端·后端·api