你是否也存在过这样的需求,想要公开一个接口到网络上。但是还得加点权限,否则被人乱调用就不好了。这个权限验证的过程,最好越简单越好,可能只是对比两个字符串相等就够了。一般情况下我们遇到这种需要,就是在函数实现或者添加一个全局的拦截器就够了。但是还是需要自己来写那部分虽然简单但是很啰嗦的代码。那么存不存在一种方式,让我只管写我的代码就完了,鉴权的事情交给其他人来做呢?

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.

所以,在我们的配置里边也应该是支持多个节点配置的。

  1. {
  2. "upstreams": [
  3. {
  4. "upstream": "http://api.qingyunke.com",
  5. "path": "/jiqieren/",
  6. "trim_path": true,
  7. "is_auth": true
  8. }
  9. ],
  10. ...
  11. }

upstreams:上游服务器

upstream:上游服务器地址

path:路径,如果以斜线结尾的话代表拦截所有以 /jiqiren/开头的链接

trim_path:剔除路径,因为上游服务器中其实并不包含 /jiqiren/ 这段的,所以要踢掉这块

is_auth:是否是授权链接

其实至此的上游的链接已经配置好了,下面我们来配置一下授权相关的配置。现在我实现的这个版本里边允许同时存在多个授权类型。满足任何一个即可进行接口的调用。我们先简单配置一个bearer的版本。

  1. {
  2. ...
  3. "auth_items": {
  4. "Bearer": {
  5. "oauth_type": "BearerConfig",
  6. "configs": {
  7. "file": "bearer.json"
  8. }
  9. }
  10. }
  11. }

Bearer 对应的Model的意思是说,要引用配置文件的类型,对应的文件是 bearer.json

对应的文件内容如下

  1. {
  2. "GnPIymAqtPEodx2di0cS9o1GP9QEM2N2-Ur_5ggvANwSKRewH2DLmw": {
  3. "interfaces": [
  4. "/jiqieren/api.php"
  5. ],
  6. "headers": {
  7. "TenantId": ""
  8. }
  9. }
  10. }

其实就是一个Key对应了他能调用那些接口,还有他给上游服务器传递那些信息。因为Token的其实一般不光是能不能调用,同时他还代表了某一个服务,或者说某一个使用者,对应的,我们可以将这些信息,放到请求头中传递给上游服务器。就可以做到虽然上游服务器,并不知道Token但是上游服务器知道谁能够调用它。

下面我们来说一下这个项目是如何实现的。其实,整个功能简单的描述起来就是一个带了Token解析、鉴权的反向代理。但是本质上他还是一个反向代理,我们可以直接使用Golang自带的反向代理。

核心代码如下。

  1. package main
  2.  
  3. import (
  4. "./Configs"
  5. "./Server"
  6. "encoding/json"
  7. "flag"
  8. "fmt"
  9. "io/ioutil"
  10. "log"
  11. "net/http"
  12. "net/http/httputil"
  13. "net/url"
  14. "os"
  15. "strings"
  16. )
  17.  
  18. func main() {
  19. var port int
  20. var config string
  21.  
  22. flag.IntVar(&port, "port", , "server port")
  23. flag.StringVar(&config, "config", "", "mapping config")
  24.  
  25. flag.Parse()
  26.  
  27. if config == "" {
  28. log.Fatal("not found config")
  29. }
  30.  
  31. if fileExist(config) == false {
  32. log.Fatal("not found config file")
  33. }
  34.  
  35. data, err := ioutil.ReadFile(config)
  36. if err != nil {
  37. log.Fatal(err)
  38. }
  39.  
  40. var configInstance Configs.Config
  41. err = json.Unmarshal(data, &configInstance)
  42. if err != nil {
  43. log.Fatal(err)
  44. }
  45.  
  46. auths := make(map[string]Server.IAuthInterface)
  47.  
  48. if configInstance.AuthItems != nil {
  49. for name, configItem := range configInstance.AuthItems {
  50. auth_item := Server.GetAuthFactoryInstance().CreateAuthInstance(configItem.OAuthType)
  51.  
  52. if auth_item == nil {
  53. continue
  54. }
  55.  
  56. auth_item.InitWithConfig(configItem.Configs)
  57. auths[strings.ToLower(name)] = auth_item
  58. log.Println(name, configItem)
  59. }
  60. }
  61.  
  62. for i := ; i < len(configInstance.Upstreams); i++ {
  63. up := configInstance.Upstreams[i]
  64. u, err := url.Parse(up.Upstream)
  65.  
  66. log.Printf("{%s} => {%s}\r\n", up.Application, up.Upstream)
  67.  
  68. if err != nil {
  69. log.Fatal(err)
  70. }
  71.  
  72. rp := httputil.NewSingleHostReverseProxy(u)
  73.  
  74. http.HandleFunc(up.Application, func(writer http.ResponseWriter, request *http.Request) {
  75. o_path := request.URL.Path
  76.  
  77. if up.UpHost != "" {
  78. request.Host = up.UpHost
  79. } else {
  80. request.Host = u.Host
  81. }
  82.  
  83. if up.TrimApplication {
  84. request.URL.Path = strings.TrimPrefix(request.URL.Path, up.Application)
  85. }
  86.  
  87. if up.IsAuth {
  88. auth_value := request.Header.Get("Authorization")
  89. if auth_value == "" {
  90. writeUnAuthorized(writer)
  91. return
  92. }
  93.  
  94. sp_index := strings.Index(auth_value, " ")
  95. auth_type := auth_value[:sp_index]
  96. auth_token := auth_value[sp_index+:]
  97.  
  98. if auth_instance, ok := auths[strings.ToLower(auth_type)]; ok {
  99. err, headers := auth_instance.GetAuthInfo(auth_token, o_path)
  100. if err != nil {
  101. writeUnAuthorized(writer)
  102. } else {
  103. if headers != nil {
  104. for k, v := range headers {
  105. request.Header.Add(k, v)
  106. }
  107. }
  108. rp.ServeHTTP(writer, request)
  109. }
  110. } else {
  111. writeUnsupportedAuthType(writer)
  112. }
  113. } else {
  114. rp.ServeHTTP(writer, request)
  115. }
  116. })
  117. }
  118.  
  119. log.Printf("http server start on :%d\r\n", port)
  120. http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
  121. log.Println("finsh")
  122. }
  123.  
  124. func writeUnsupportedAuthType(writer http.ResponseWriter) () {
  125. writer.Header().Add("Content-Type", "Application/json")
  126. writer.WriteHeader(http.StatusBadRequest)
  127. writer.Write([]byte("{\"status\":\"unsupported authorization\"}"))
  128. }
  129.  
  130. func writeUnAuthorized(writer http.ResponseWriter) {
  131. writer.Header().Add("Content-Type", "Application/json")
  132. writer.WriteHeader(http.StatusUnauthorized)
  133. writer.Write([]byte("{\"status\":\"un-authorized\"}"))
  134. }
  135.  
  136. func fileExist(filename string) bool {
  137. _, err := os.Stat(filename)
  138. return err == nil || os.IsExist(err)
  139. }

最核心的代码不足150行,简单点说就是,在反向代理中间加上了鉴权的逻辑。当然鉴权的逻辑,我做了一层抽象,现在是通过配置文件来进行动态修改的。

  1. package Server
  2.  
  3. import (
  4. "log"
  5. "strings"
  6. )
  7.  
  8. type IAuthInterface interface {
  9. GetAuthInfo(token string, url string) (err error, headers map[string]string)
  10. InitWithConfig(config map[string]string)
  11. }
  12.  
  13. type AuthFactory struct {
  14. }
  15.  
  16. var auth_factory_instance AuthFactory
  17.  
  18. func init() {
  19. auth_factory_instance = AuthFactory{}
  20. }
  21.  
  22. func GetAuthFactoryInstance() *AuthFactory {
  23. return &auth_factory_instance
  24. }
  25.  
  26. func (this *AuthFactory) CreateAuthInstance(t string) IAuthInterface {
  27. if strings.ToLower(t) == "bearer" {
  28. return &BeareAuth{}
  29. }
  30.  
  31. if strings.ToLower(t) == "bearerconfig" {
  32. return &BearerConfigAuth{}
  33. }
  34.  
  35. log.Fatalf("%s 是不支持的类型 \r\n", t)
  36. return nil
  37. }
  1. package Server
  2.  
  3. import (
  4. "encoding/json"
  5. "errors"
  6. "io/ioutil"
  7. "log"
  8. )
  9.  
  10. type BearerConfigItem struct {
  11. Headers map[string]string `json:"headers"`
  12. Interfaces []string `json:"interfaces"`
  13. }
  14.  
  15. type BearerConfigAuth struct {
  16. Configs map[string]*BearerConfigItem // token =》 config item
  17. }
  18.  
  19. func (this *BearerConfigAuth) GetAuthInfo(token string, url string) (err error, headers map[string]string) {
  20. configItem := this.Configs[token]
  21. if configItem == nil {
  22. err = errors.New("not found token")
  23. return
  24. }
  25.  
  26. if IndexOf(configItem.Interfaces, url) == - {
  27. err = errors.New("un-authorized")
  28. return
  29. }
  30.  
  31. headers = make(map[string]string)
  32. for k, v := range configItem.Headers {
  33. headers[k] = v
  34. }
  35.  
  36. return
  37. }
  38.  
  39. func (this *BearerConfigAuth) InitWithConfig(config map[string]string) {
  40. cFile := config["file"]
  41. if cFile == "" {
  42. return
  43. }
  44.  
  45. data, err := ioutil.ReadFile(cFile)
  46. if err != nil {
  47. log.Panic(err)
  48. }
  49.  
  50. var m map[string]*BearerConfigItem
  51.  
  52. //this.Configs = make(map[string]*BearerConfigItem)
  53. err = json.Unmarshal(data, &m)
  54. if err != nil {
  55. log.Panic(err)
  56. }
  57.  
  58. this.Configs = m
  59. }
  60.  
  61. func IndexOf(array []string, item string) int {
  62. for i := ; i < len(array); i++ {
  63. if array[i] == item {
  64. return i
  65. }
  66. }
  67.  
  68. return -
  69. }

当然了,其实这个只适合内部简单使用,并不适合对外的真实的OpenAPI,因为Token现在太死了,Token应该是另外一个系统(鉴权中心)里边的处理的。包括企业自建应用的信息创建、Token的兑换、刷新等等。并且,不光是业务逻辑,还有非常强烈的性能要求,毕竟OpenAPI可以说是一个企业公开接口的门户了,跟这种软件打交道,性能也不能差了(我们公司这边我们团队也做了这么一个系统,鉴权接口可以单机1W QPS,响应时间4ms),当然也是要花费不少心思的。

最后,这个项目已经开源了,给大家做个简单的参考。

https://gitee.com/anxin1225/OpenAPI.GO

如何使用Golang实现一个API网关的更多相关文章

  1. 使用 Node.js 搭建一个 API 网关

    原文地址:Building an API Gateway using Node.js 外部客户端访问微服务架构中的服务时,服务端会对认证和传输有一些常见的要求.API 网关提供共享层来处理服务协议之间 ...

  2. C# 开源一个基于 yarp 的 API 网关 Demo,支持绑定 Kubernetes Service

    关于 Neting 刚开始的时候是打算使用微软官方的 Yarp 库,实现一个 API 网关,后面发现坑比较多,弄起来比较麻烦,就放弃了.目前写完了查看 Kubernetes Service 信息.创建 ...

  3. 开源API网关,你选对了么?

    开源API网关,你选对了么? api网关的本质 不用扯那么多,也不用画图,一句话说清楚 api网关:流量总入口,得以集中控制! 就这么简单 api网关协议上最基本要支持HTTP 和 WebSocket ...

  4. 使用API网关构建微服务

    使用传统的异步回调方法编写API组合代码会让你迅速坠入回调地狱.代码会变得混乱.难以理解且容易出错.一个更好的方法是使用响应式方法以一种声明式样式编写API网关代码.响应式抽象概念的例子有Scala中 ...

  5. 微服务架构下的API网关

    顾名思义,是出现在系统边界上的一个面向API的.串行集中式的强管控服务,这里的边界是企业IT系统的边界,主要起到隔离外部访问与内部系统的作用.在微服务概念的流行之前,API网关的实体就已经诞生了,例如 ...

  6. 微服务从设计到部署(二)使用 API 网关

    链接:https://github.com/oopsguy/microservices-from-design-to-deployment-chinese 译者:Oopsguy 本书的七个章节是关于设 ...

  7. Ocelot API网关的实现剖析

    在微软Tech Summit 2017 大会上和大家分享了一门课程<.NET Core 在腾讯财付通的企业级应用开发实践>,其中重点是基于ASP.NET Core打造可扩展的高性能企业级A ...

  8. 谈谈微服务中的 API 网关(API Gateway)

    前言 又是很久没写博客了,最近一段时间换了新工作,比较忙,所以没有抽出来太多的时间写给关注我的粉丝写一些干货了,就有人问我怎么最近没有更新博客了,在这里给大家抱歉. 那么,在本篇文章中,我们就一起来探 ...

  9. 【微服务】之六:轻松搞定SpringCloud微服务-API网关zuul

    通过前面几篇文章的介绍,我们可以轻松搭建起来微服务体系中比较重要的几个基础构建服务.那么,在本篇博文中,我们重点讲解一下,如何将所有微服务的API同意对外暴露,这个就设计API网关的概念. 本系列教程 ...

随机推荐

  1. Spring(DI,AOP) 理解(一)

    感觉自己的spring理解的不好.所以重新开始学习. 这篇文章主要是来理解DI(依赖注入),Aop(切面) 一.DI(依赖注入,这里没有涉及到注释.只是用xml文件和Bean的方法来注册pojo,) ...

  2. Win安装docker

    Windows Docker 安装 win7.win8 系统 win7.win8 等需要利用 docker toolbox 来安装,国内可以使用阿里云的镜像来下载,下载地址:http://mirror ...

  3. 45道SQL数据题详解1

    准备阶段: 创建表: //创建学生表,前面的s表示学生,相应的标签前面加t表示老师 CREATE TABLE students (sno VARCHAR(3) NOT NULL, sname VARC ...

  4. S7通信协议之你不知道的事儿

    在电气学习的路上,西门子PLC应该是我的启蒙PLC,从早期的S7-300/400 PLC搭建Profibus-DP网络开始接触,到后来的S7-200Smart PLC,再到现在的S7-1200/150 ...

  5. SpringBoot系列(十)优雅的处理统一异常处理与统一结果返回

    SpringBoot系列(十)统一异常处理与统一结果返回 往期推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列 ...

  6. 关于log4j、jul、jcl、slf4j等等日志组件的理解

    日志组件: 我们经常在开发项目的时候,需要打印记录项目过程中的一些日志.那我们经常大概会用到 log4j.jul.jcl.slf4j.simple.nop.logback 等等,那我们就详细介绍下这些 ...

  7. 架构设计 | 分布式业务系统中,全局ID生成策略

    本文源码:GitHub·点这里 || GitEE·点这里 一.全局ID简介 在实际的开发中,几乎所有的业务场景产生的数据,都需要一个唯一ID作为核心标识,用来流程化管理.比如常见的: 订单:order ...

  8. 测试Thread中的常用方法

    package com.yhqtv.java; /* *测试Thread中的常用方法: * 1.start():启动当前线程:调用当前线程的run() * 2.run():通常需要重写Thread类的 ...

  9. tp3.2的__construct和_initialize方法

    在tp3.2框架里面,有一个php自带的__construct()构造函数和tp3自带的构造函数_initialize()的实行顺序是先实行 php自带的__construct()构造函数 再实行 t ...

  10. PHP--关于上传文件大小的问题

    参考:https://www.cnblogs.com/jianqingwang/p/5863960.html https://blog.csdn.net/u013168253/article/deta ...