如何将服务开放给用户:构建 API 接口和用户认证的实践
API全称是:Application Programming Interface,即:应用程序接口,是一些预先定义的函数,或指软件系统不同组成部分衔接的约定。开发人员可以使用这些API接口进行编程开发,而又无需访问源码,或理解内部工作机制的细节。
API是项目开发过程中必要的组成部分之一,是客户端应用与服务端应用通信和桥梁。本文将介绍如何用go写简单的API,并将服务开放给用户使用,同时在调用处理函数前增加用户认证机制,防止未授权用户访问接口。
一、API接口设计考虑因素
API设计非常重要,它代表了服务之间交互的方式,会影响服务之间的集成。 一个好的API设计需要考虑以下因素:
-
合理命名接口
为了各部门之间的合作方便,接口的命名需要尽量规范优雅,使得使用者在未看到接口文档时,就可以根据接口的URL大致明白接口的功能。
-
规范化入参和出参的定义,统一风格
尽量保持一个项目内的接口有统一的风格,统一的返回格式。出入参采用统一的命名规范,有明确固定的数据格式。
-
接口功能定义单一清晰
接口要实现的功能应该是比较独立且单一的,避免返回一堆冗余数据,也可以减少各功能的耦合,使业务更加清晰。
-
明确接口支持的协议
接口要明确支持的协议(POST/GET/PUT/DELETE等),且尽量一个接口只支持一种协议。并且在接口被调用时,如果参数传递非接口定义协议,要明确提示返回错误信息。
-
充分考虑接口的可扩展性,避免做大而全的接口
相比做一个大而全的接口,精准且分模块的小接口更加有利于将来可能的接口扩展。
-
接口里尽量不做客户端可以处理的逻辑,减少服务端压力
接口主要是提供给客户端数据的,对于能够在客户端完成的逻辑处理,尽量由客户端来处理,进而减轻后端服务器的压力,让后端接口更加“专心”处理数据服务。
-
记录好接口日志,对其进行清晰的分类和归档
接口日志对于追溯问题和debug都是非常重要的,平时需要养成良好的记录日志的习惯。日志主要有info和error两种,info日志一般用于记录现场,用来追溯问题;error日志一般用于协作我们查找bug,定位代码问题。
-
版本控制
一个对外开放的服务,极大的概率会发生变化。业务变化可能修改 API 参数或响应数据结构,以及资源之间的关系。一般来说,字段的增加不会影响旧的客户端运行。但是当存在一些破坏性修改时,就需要使用新的版本将数据导向到新的资源地址。
为了不影响老版本应用的正常使用,大部分应用后台都会针对性的维护多个接口版本。比较推荐的做法是使用 URI 前缀,例如
/v1/users/表达获取v1版本下的用户列表。 -
身份验证
在一些接口场景中,是要依赖于用户身份的,比如通过token来实现用户身份的验证。
-
在代码结构层面,尽量和其他部分分开
API集中由同一个系统“模块”提供,尽量不要和页面等其他功能混合开发。
-
接口文档
好的接口需要有与之配套的接口文档。如果希望降低接口文档的维护成本等,也可以使用开源的第三方自动化接口文档工具,比如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)
}
}
主要流程为:
- 编写请求处理函数。如上面的
helloHandler以及getUserInfoHandler。 - 注册路由。如上面的
http.HandleFunc("/api/hello", helloHandler)。 - 启动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接口以确保它们能够提供正确的逻辑处理服务,并且需要检查用户认证机制是否正确生效,防止未授权用户访问你的接口。