如何将服务开放给用户:构建 API 接口和用户认证的实践 | 青训营

66 阅读7分钟

如何将服务开放给用户:构建 API 接口和用户认证的实践

API全称是:Application Programming Interface,即:应用程序接口,是一些预先定义的函数,或指软件系统不同组成部分衔接的约定。开发人员可以使用这些API接口进行编程开发,而又无需访问源码,或理解内部工作机制的细节。

API是项目开发过程中必要的组成部分之一,是客户端应用与服务端应用通信和桥梁。本文将介绍如何用go写简单的API,并将服务开放给用户使用,同时在调用处理函数前增加用户认证机制,防止未授权用户访问接口。

一、API接口设计考虑因素

API设计非常重要,它代表了服务之间交互的方式,会影响服务之间的集成。 一个好的API设计需要考虑以下因素:

  1. 合理命名接口

    为了各部门之间的合作方便,接口的命名需要尽量规范优雅,使得使用者在未看到接口文档时,就可以根据接口的URL大致明白接口的功能。

  2. 规范化入参和出参的定义,统一风格

    尽量保持一个项目内的接口有统一的风格,统一的返回格式。出入参采用统一的命名规范,有明确固定的数据格式。

  3. 接口功能定义单一清晰

    接口要实现的功能应该是比较独立且单一的,避免返回一堆冗余数据,也可以减少各功能的耦合,使业务更加清晰。

  4. 明确接口支持的协议

    接口要明确支持的协议(POST/GET/PUT/DELETE等),且尽量一个接口只支持一种协议。并且在接口被调用时,如果参数传递非接口定义协议,要明确提示返回错误信息。

  5. 充分考虑接口的可扩展性,避免做大而全的接口

    相比做一个大而全的接口,精准且分模块的小接口更加有利于将来可能的接口扩展。

  6. 接口里尽量不做客户端可以处理的逻辑,减少服务端压力

    接口主要是提供给客户端数据的,对于能够在客户端完成的逻辑处理,尽量由客户端来处理,进而减轻后端服务器的压力,让后端接口更加“专心”处理数据服务。

  7. 记录好接口日志,对其进行清晰的分类和归档

    接口日志对于追溯问题和debug都是非常重要的,平时需要养成良好的记录日志的习惯。日志主要有info和error两种,info日志一般用于记录现场,用来追溯问题;error日志一般用于协作我们查找bug,定位代码问题。

  8. 版本控制

    一个对外开放的服务,极大的概率会发生变化。业务变化可能修改 API 参数或响应数据结构,以及资源之间的关系。一般来说,字段的增加不会影响旧的客户端运行。但是当存在一些破坏性修改时,就需要使用新的版本将数据导向到新的资源地址。

    为了不影响老版本应用的正常使用,大部分应用后台都会针对性的维护多个接口版本。比较推荐的做法是使用 URI 前缀,例如 /v1/users/ 表达获取 v1 版本下的用户列表。

  9. 身份验证

    在一些接口场景中,是要依赖于用户身份的,比如通过token来实现用户身份的验证。

  10. 在代码结构层面,尽量和其他部分分开

    API集中由同一个系统“模块”提供,尽量不要和页面等其他功能混合开发。

  11. 接口文档

    好的接口需要有与之配套的接口文档。如果希望降低接口文档的维护成本等,也可以使用开源的第三方自动化接口文档工具,比如swagger等。

二、Go构建简单的API

在Go语言中,我们可以使用net/http包来轻松构建API接口。

下面是用Go实现一个简单的http server的代码:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	// 注册路由,指定一个请求处理函数
	http.HandleFunc("/api/hello", helloHandler)
	http.HandleFunc("/api/user", getUserInfoHandler)
	// 启动HTTP服务,监听8080端口
	_ = http.ListenAndServe(":8080", nil)
}

// helloHandler是一个简单的HTTP请求处理函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
	// 向客户端(浏览器)返回“Hello World!”
	_, err := w.Write([]byte("Hello World!"))
	if err != nil {
		fmt.Println(err)
	}
}

// getUserInfoHandler是一个获取用户信息的HTTP请求处理函数
func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
	// 获取用户信息的逻辑
	// ...

	// 向客户端(浏览器)返回用户信息
	_, err := w.Write([]byte("User Info"))
	if err != nil {
		fmt.Println(err)
	}
}

主要流程为:

  1. 编写请求处理函数。如上面的helloHandler以及getUserInfoHandler
  2. 注册路由。如上面的http.HandleFunc("/api/hello", helloHandler)
  3. 启动HTTP服务监听端口。如上面的_ = http.ListenAndServe(":8080", nil)

这样就创建好了一个简单的HTTP服务,运行起程序后,当用户在浏览器输入http://localhost:8080/api/hello,后台会路由到请求路径对应的请求处理函数,即`helloHandler`,会得到输出 Hello World!。访问http://localhost:8080/api/user 同理。

上面的示例中我们定义了两个简单的API接口:/api/hello用于打印Hello World!/api/user返回用户信息。当用户访问这些接口时,服务器将会调用相应的处理函数、执行相应的逻辑并返回结果。

三、用户验证

用户认证是确认用户身份的过程,确保只有合法用户可以访问你的服务。在API开放给公众使用时,用户认证尤为重要,防止未经授权的访问和滥用。

下面示例是在刚才的代码基础上增加用户认证的实现:

package main

import (
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"net/http"
)

func main() {
	// 注册路由,指定一个请求处理函数
	http.HandleFunc("/api/hello", helloHandler)
	http.HandleFunc("/api/user", authMiddleware(getUserInfoHandler)) // 使用中间件先鉴权
	// 启动HTTP服务,监听8080端口
	_ = http.ListenAndServe(":8080", nil)
}

// helloHandler是一个简单的HTTP请求处理函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
	// 向客户端(浏览器)返回“Hello World!”
	_, err := w.Write([]byte("Hello World!"))
	if err != nil {
		fmt.Println(err)
	}
}

// getUserInfoHandler是一个获取用户信息的HTTP请求处理函数
func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
	// 获取用户信息的逻辑
	// ...

	// 向客户端(浏览器)返回用户信息
	_, err := w.Write([]byte("User Info"))
	if err != nil {
		fmt.Println(err)
	}
}

// authMiddleware是一个HTTP请求中间件,用于鉴权
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// 鉴权逻辑
		// 检查用户是否已经登录或者提供了有效的访问令牌
		// 如果未经授权,则返回错误响应
		// 否则,继续执行下一个处理函数
		// ...

		// 从请求头中获取令牌
		tokenString := r.Header.Get("Authorization")
		if tokenString == "" {
			http.Error(w, "未提供令牌", http.StatusUnauthorized)
			return
		}

		// 解析令牌
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			return []byte("your-secret-key"), nil
		})

		// 若令牌无效
		if err != nil || !token.Valid {
			http.Error(w, "令牌无效", http.StatusUnauthorized)
			return
		}

		// 调用下一个中间件或者最终的请求处理函数
		next(w, r)
	}
}

可以看到,主要是增加了一个authMiddleware中间件函数,用于在执行对应的处理函数之前先鉴权。比如检查用户是否已经登录或者提供了有效的访问令牌,如果未经授权,则返回错误响应,否则,继续执行下一个处理函数。

这里的鉴权逻辑调用的是jwt-go包来处理JWT令牌,实现了一个简单的JWT令牌认证中间件,用户在请求头中提供JWT令牌进行认证。

jwt (json web token) 被广泛地用于各种登录, 授权场景。简单来说,客户端有了这个jwt令牌, 就可以拿着它去服务端获取一些资源。关于jwt的详细介绍可以看这篇博文

此时再访问http://localhost:8080/api/user 就会提示“未提供令牌”。

四、编写API文档和示例代码

在写好API接口代码后需要编写清晰详细的API文档并提供示例代码,帮助其他开发者更好地理解以及使用你写的接口。可以使用开源的第三方自动化接口文档工具工具如Swagger等来生成API文档。

五、测试接口服务并部署

在将服务开放给用户使用之前,需要测试API接口以确保它们能够提供正确的逻辑处理服务,并且需要检查用户认证机制是否正确生效,防止未授权用户访问你的接口。