关键词:
你是否也存在过这样的需求,想要公开一个接口到网络上。但是还得加点权限,否则被人乱调用就不好了。这个权限验证的过程,最好越简单越好,可能只是对比两个字符串相等就够了。一般情况下我们遇到这种需要,就是在函数实现或者添加一个全局的拦截器就够了。但是还是需要自己来写那部分虽然简单但是很啰嗦的代码。那么存不存在一种方式,让我只管写我的代码就完了,鉴权的事情交给其他人来做呢?
OpenAPI 一般情况下,就是允许企业内部提供对外接口的项目。你只管写你的接口,然后,在我这里注册一下,我来负责你的调用权限判定,如果他没有权限,我就告诉他没有权限,如果他存在权限,我就转调一下你的接口,然后把结果返回给他。其实情景是相似的,我们可以把这段需求抽象,然后做一个配置文件版的开放接口。
想做这件事情,其实Golang是一个非常不错的选择,首先,Golang对于这种转调的操作非常友好,甚至于,Golang语言本身就提供了一个反向代理的实现,我们可以直接使用Golang的原始框架就完全够用。
在简单分析一下我们的需求,其实很简单,监听的某一段Path之后,先判断有没有权限,没有权限,直接回写结果,有权限交给反向代理来实现,轻松方便。既然是这样,我们需要定义一下,路径转发的规则。
比如说我们尝试给这个接口添加一个,当然这只是其中一个接口,我们应该要支持好多个接口
http://api.qingyunke.com/api.php?key=free&appid=0&msg=hello%20world.
在他进入到我们的系统中的时候看上去可能是这样的。
http://localhost:5000/jiqiren/api.php?key=free&appid=0&msg=hello%20world.
所以,在我们的配置里边也应该是支持多个节点配置的。
"upstreams": [ "upstream": "http://api.qingyunke.com", "path": "/jiqieren/", "trim_path": true, "is_auth": true ], ...
upstreams:上游服务器
upstream:上游服务器地址
path:路径,如果以斜线结尾的话代表拦截所有以 /jiqiren/开头的链接
trim_path:剔除路径,因为上游服务器中其实并不包含 /jiqiren/ 这段的,所以要踢掉这块
is_auth:是否是授权链接
其实至此的上游的链接已经配置好了,下面我们来配置一下授权相关的配置。现在我实现的这个版本里边允许同时存在多个授权类型。满足任何一个即可进行接口的调用。我们先简单配置一个bearer的版本。
... "auth_items": "Bearer": "oauth_type": "BearerConfig", "configs": "file": "bearer.json"
Bearer 对应的Model的意思是说,要引用配置文件的类型,对应的文件是 bearer.json
对应的文件内容如下
"GnPIymAqtPEodx2di0cS9o1GP9QEM2N2-Ur_5ggvANwSKRewH2DLmw": "interfaces": [ "/jiqieren/api.php" ], "headers": "TenantId": "100860"
其实就是一个Key对应了他能调用那些接口,还有他给上游服务器传递那些信息。因为Token的其实一般不光是能不能调用,同时他还代表了某一个服务,或者说某一个使用者,对应的,我们可以将这些信息,放到请求头中传递给上游服务器。就可以做到虽然上游服务器,并不知道Token但是上游服务器知道谁能够调用它。
下面我们来说一下这个项目是如何实现的。其实,整个功能简单的描述起来就是一个带了Token解析、鉴权的反向代理。但是本质上他还是一个反向代理,我们可以直接使用Golang自带的反向代理。
核心代码如下。
package main import ( "./Configs" "./Server" "encoding/json" "flag" "fmt" "io/ioutil" "log" "net/http" "net/http/httputil" "net/url" "os" "strings" ) func main() var port int var config string flag.IntVar(&port, "port", 80, "server port") flag.StringVar(&config, "config", "", "mapping config") flag.Parse() if config == "" log.Fatal("not found config") if fileExist(config) == false log.Fatal("not found config file") data, err := ioutil.ReadFile(config) if err != nil log.Fatal(err) var configInstance Configs.Config err = json.Unmarshal(data, &configInstance) if err != nil log.Fatal(err) auths := make(map[string]Server.IAuthInterface) if configInstance.AuthItems != nil for name, configItem := range configInstance.AuthItems auth_item := Server.GetAuthFactoryInstance().CreateAuthInstance(configItem.OAuthType) if auth_item == nil continue auth_item.InitWithConfig(configItem.Configs) auths[strings.ToLower(name)] = auth_item log.Println(name, configItem) for i := 0; i < len(configInstance.Upstreams); i++ up := configInstance.Upstreams[i] u, err := url.Parse(up.Upstream) log.Printf("%s => %s ", up.Application, up.Upstream) if err != nil log.Fatal(err) rp := httputil.NewSingleHostReverseProxy(u) http.HandleFunc(up.Application, func(writer http.ResponseWriter, request *http.Request) o_path := request.URL.Path if up.UpHost != "" request.Host = up.UpHost else request.Host = u.Host if up.TrimApplication request.URL.Path = strings.TrimPrefix(request.URL.Path, up.Application) if up.IsAuth auth_value := request.Header.Get("Authorization") if auth_value == "" writeUnAuthorized(writer) return sp_index := strings.Index(auth_value, " ") auth_type := auth_value[:sp_index] auth_token := auth_value[sp_index+1:] if auth_instance, ok := auths[strings.ToLower(auth_type)]; ok err, headers := auth_instance.GetAuthInfo(auth_token, o_path) if err != nil writeUnAuthorized(writer) else if headers != nil for k, v := range headers request.Header.Add(k, v) rp.ServeHTTP(writer, request) else writeUnsupportedAuthType(writer) else rp.ServeHTTP(writer, request) ) log.Printf("http server start on :%d ", port) http.ListenAndServe(fmt.Sprintf(":%d", port), nil) log.Println("finsh") func writeUnsupportedAuthType(writer http.ResponseWriter) () writer.Header().Add("Content-Type", "Application/json") writer.WriteHeader(http.StatusBadRequest) writer.Write([]byte(""status":"unsupported authorization"")) func writeUnAuthorized(writer http.ResponseWriter) writer.Header().Add("Content-Type", "Application/json") writer.WriteHeader(http.StatusUnauthorized) writer.Write([]byte(""status":"un-authorized"")) func fileExist(filename string) bool _, err := os.Stat(filename) return err == nil || os.IsExist(err)
最核心的代码不足150行,简单点说就是,在反向代理中间加上了鉴权的逻辑。当然鉴权的逻辑,我做了一层抽象,现在是通过配置文件来进行动态修改的。
package Server import ( "log" "strings" ) type IAuthInterface interface GetAuthInfo(token string, url string) (err error, headers map[string]string) InitWithConfig(config map[string]string) type AuthFactory struct var auth_factory_instance AuthFactory func init() auth_factory_instance = AuthFactory func GetAuthFactoryInstance() *AuthFactory return &auth_factory_instance func (this *AuthFactory) CreateAuthInstance(t string) IAuthInterface if strings.ToLower(t) == "bearer" return &BeareAuth if strings.ToLower(t) == "bearerconfig" return &BearerConfigAuth log.Fatalf("%s 是不支持的类型 ", t) return nil
package Server import ( "encoding/json" "errors" "io/ioutil" "log" ) type BearerConfigItem struct Headers map[string]string `json:"headers"` Interfaces []string `json:"interfaces"` type BearerConfigAuth struct Configs map[string]*BearerConfigItem // token =》 config item func (this *BearerConfigAuth) GetAuthInfo(token string, url string) (err error, headers map[string]string) configItem := this.Configs[token] if configItem == nil err = errors.New("not found token") return if IndexOf(configItem.Interfaces, url) == -1 err = errors.New("un-authorized") return headers = make(map[string]string) for k, v := range configItem.Headers headers[k] = v return func (this *BearerConfigAuth) InitWithConfig(config map[string]string) cFile := config["file"] if cFile == "" return data, err := ioutil.ReadFile(cFile) if err != nil log.Panic(err) var m map[string]*BearerConfigItem //this.Configs = make(map[string]*BearerConfigItem) err = json.Unmarshal(data, &m) if err != nil log.Panic(err) this.Configs = m func IndexOf(array []string, item string) int for i := 0; i < len(array); i++ if array[i] == item return i return -1
当然了,其实这个只适合内部简单使用,并不适合对外的真实的OpenAPI,因为Token现在太死了,Token应该是另外一个系统(鉴权中心)里边的处理的。包括企业自建应用的信息创建、Token的兑换、刷新等等。并且,不光是业务逻辑,还有非常强烈的性能要求,毕竟OpenAPI可以说是一个企业公开接口的门户了,跟这种软件打交道,性能也不能差了(我们公司这边我们团队也做了这么一个系统,鉴权接口可以单机1W QPS,响应时间4ms),当然也是要花费不少心思的。
最后,这个项目已经开源了,给大家做个简单的参考。
https://gitee.com/anxin1225/OpenAPI.GO
go语言实现一个简单的简单网关
...网关;还有就是golang的网关,比如tyk。这篇文章主要是讲如何基于golang实现一个简单的网关。转自:troy.wang/docs/golang/posts/golang-gateway/整理:go语言钟文文档:www.topgoer.cn启动两个后端web服务(代码)这里使用命令行工具进行测试具... 查看详情
如何使用 kong api 网关实现 oauth2?
】如何使用kongapi网关实现oauth2?【英文标题】:HowdoIimplementoauth2withkongapigateway?【发布时间】:2020-04-1604:22:50【问题描述】:我想为在laravel上运行的一堆微服务实现一个api网关。在网关前面有一个角度客户端,用户必须使用用户... 查看详情
如何使用api网关做服务编排?
参考技术A服务编排/数据聚合指的是可以通过一个请求来依次调用多个微服务,并对每个服务的返回结果做数据处理,最终整合成一个大的结果返回给前端。例如一个服务是“查询用户预定的酒店”,前端仅需要传一个订单ID,... 查看详情
elasticsearch:api网关apacheapisix集成elasticsearch实现实时日志监控(代码片段)
...绍ApacheAPISIX的elasticsearch-logger插件的相关信息,以及如何通过此插件获取APISIX的实时日志。背景信息ApacheAPISIX 是一个动态、实时、高性能的 API 网关,提供了负载均衡、动态上游、灰度发布、服务熔断、身份认证、可... 查看详情
原创自己动手写一个服务网关(代码片段)
引言什么是网关?为什么需要使用网关?如图所示,在不使用网关的情况下,我们的服务是直接暴露给服务调用方。当调用方增多,势必需要添加定制化访问权限、校验等逻辑。当添加API网关后,再第三方调用端和服务提供方之... 查看详情
如何判断golang接口是否实现?(代码片段)
前言在看一个底层库的的时候,看到了一个比较奇怪的写法,于是乎有了本文。主要探讨两个问题:1.利用编译来判断Golang接口是否实现2.延伸出的make和new的区别正文1.利用编译来判断Golang接口是否实现看了一个底层... 查看详情
golang使用martini和redigo在go中编写的一个小例子api(代码片段)
golang接口的使用(练习一)(代码片段)
在go语言中,一个类只要实现了接口要求的所有函数,我们就说这个类实现了这个接口。golang接口赋值实现方式一:将对象实例赋值给接口packagemainimport"fmt"//定义一个Animal接口,实现飞和跑的功能typeAnimalinterfaceFly()Run()//定义一个Bi... 查看详情
ocelot.jwtauthorize:一个基于网关的jwt验证包(代码片段)
...cnblogs.com/axzxs2001/p/8005084.html,作过说明,这篇博文说明了实现代码,今天我把这个实现作了整理,封装成一个Nuget包,供大家方便调用。WebAPI的验证一 查看详情
golang使用一个二叉树来实现一个插入排序(代码片段)
思路不太好理解,请用断点packagemainimport"fmt"typetreestructvalueintleft,right*treefuncSort(values[]int)varroot*treefor_,v:=rangevaluesroot=add(root,v)appendValues(values[:0],root)funcappendValues(values[]i 查看详情
x的平方根的golang实现(代码片段)
...整数,小数部分将被舍去。首先遇到这种题目肯定要想到使用内置得api来解答://使用api来求解funcmySqrt(xint)intf:=float64(x)ff:=math. 查看详情
golang教程:goroutine信道(代码片段)
在上一篇教程中,我们讨论了如何使用协程实现并发。在这篇教程中,我们将讨论信道以及如何使用信道实现协程间通信。什么是信道信道(Channel)可以被认为是协程之间通信的管道。与水流从管道的一端流向另一端一样,数... 查看详情
使用golang实现目录的监控过程(代码片段)
GO实现文件夹监控收获查看watcher.go,看出实现一个系统event的监控,代码不过625行;执行exec.Cmd(),cmd.Run(),可以获得shell的执行状态;开始阅读github上的开源代码,代码特别精简;说明项目组有一个需求,即当团队人员更新Gitbook到... 查看详情
golang中如何释放websocket和redis网关服务器资源?
】golang中如何释放websocket和redis网关服务器资源?【英文标题】:Howtoreleaseawebsocketandredisgatewayserverresourceingolang?【发布时间】:2019-04-1207:06:36【问题描述】:我有一个网关服务器,它可以使用websocket将消息推送到客户端,一个新... 查看详情
3分钟就会系列使用ocelot+consul搭建微服务吧!(代码片段)
...来的客户。手把手搭建一个网关在此之前你应该去学一学如何搭建服务集群,那么这将有效与你学习API网关服务,传送门,再此基础上再添加一个名为 MicroService.APIGetway的ASP.NETWebApi项目。在该项目中,我们通过Nuget安装Oc... 查看详情
如何使用gateway搭建网关服务及实现动态路由?(代码片段)
...的一部分,是必须要掌握的;本文记录一下我是如何使用Gateway搭建网关服务及实现动态路由的,帮助大家学习如何快速搭建一个网关服务,了解路由相关配置,鉴权的流程及业务处理,有兴趣的一定看到... 查看详情
如何加速golang写业务的开发速度(代码片段)
如何加速golang写业务的开发速度不要忌讳panicgolang写业务代码经常会被吐槽,写业务太慢了,其中最大的吐槽点就是,处理各种error太麻烦了。一个项目中,会有30%或者更多的是在处理error。对于golang的error这个事情,golang的官方... 查看详情
用golang实现一个代理池(代码片段)
...总会遇到爬取速度过快而被封IP的情况,这个时候就需要使用代理了。在https://github.com/henson/ProxyPool的启发下,决定自己实现一个代理池。项目已经开源在github。https://github.com/AceDarkkinght/GoProxyCollector开发环境windows7,Go1.8.4数据来... 查看详情