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

86 阅读5分钟

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

概述

在学习完 Gin 和 Gorm 框架后,我们已经可以完成后端代码的编写。但在前后端分离的开发模式下,后端工程师还需要与前端团队一起协作定义接口。一个完整的接口通常包含四个部分:

  • 方法(Method):GET、POST、PUT等
  • URI:资源的定位路径
  • 请求参数(Request):访问接口时需要传入的数据
  • 返回参数(Response):接口返回给调用方的数据

后端工程师需要根据前端提供的接口文档,把接口的输入输出定义清楚,然后实现接口逻辑。

所以,学习如何设计良好的 API 接口对整个项目的开发非常重要。好的接口可以提高项目的健壮性和灵活性。

在构建接口时,后端工程师需要注意:

  • 接口路径要合理,比如按资源类型分类
  • 输入输出采用统一格式,比如 JSON
  • 对参数进行校验,返回适当的错误提示
  • 编写详细的接口文档,便于调用方理解
  • 要有效地使用 HTTP 方法和状态码
  • 合理使用版本控制

与前端团队充分沟通,理解接口需求,制定良好的接口契约,是后端工程师在前后端分离项目中非常重要的一项工作。这需要持续学习和练习接口设计的方法。

定义 API 接口

API(Application Programming Interface)接口,是服务opened对外提供能力和数据的接口。良好的 API 接口需要满足:

  • 易于理解,接口需要有良好的文档与示例代码
  • 易于使用,接口交互简单、容错率高
  • 安全,接口需要身份验证、防篡改、限流等安全措施

下面是一些设计良好 API 接口的准则:

  • 使用 HTTPS 保证传输安全性
  • 使用 RESTful 设计接口
  • 参数与返回结果采用 JSON 格式
  • 提供完整的接口文档与示例代码
  • 版本化接口,区分开发版和生产版
  • 合理使用 HTTP 方法(GET/POST/PUT/DELETE)
  • 合理使用 HTTP 状态码
  • 接口身份认证(以 API Key 作为 Token)
  • 有效使用缓存提高响应速度

具体在gin中,可以在main函数启动api接口:

package main
​
import (
    "fmt"
    "log"
    "net/http"
)
​
func page(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "Hello!")
}
​
func router() {
    http.HandleFunc("/", page)
    log.Fatal(http.ListenAndServe(":8090", nil))
}
​
func main() {
    router()
}

实施用户认证

API 开放后,就需要限制未经授权的调用。这里介绍几种常用的 API 用户认证方案:

API Key

  • 为每个用户生成一个 API Key,作为标识
  • 调用接口时在请求中添加 API Key
  • 服务端验证 API Key 的有效性

优点:

  • 使用简单
  • 无状态,不需要在服务端保存会话状态

缺点:

  • API Key 很容易泄露
  • 无法有效注销用户

基于 OAuth 2.0 的认证

OAuth 2.0 是行业标准的认证协议,可以不同的 Grant Type 用于不同的场景:

  • Authorization Code - 用于 Web 应用认证用户
  • Implicit - 用于移动应用认证用户
  • Resource Owner Password Credentials - 直接使用用户名密码认证
  • Client Credentials - 用于服务器到服务器的认证

优点:

  • 更加安全,通过访问令牌来代替 API Key
  • 状态化,可以注销用户的访问

缺点:

  • 认证流程更复杂
  • 需要运行认证服务器

JSON Web Token(JWT)

JWT 可以用于在各服务间传递可验证的用户身份信息。

包括以下几个部分:

Header(头部) :包含了令牌的类型(通常是 "JWT")和使用的加密算法,例如 HMAC SHA256 或 RSA。

Payload(负载) :包含了一些声明(claims),如令牌的主题(subject)、到期时间(expiration time)、发布者(issuer)等。这些信息是关于令牌的附加信息。

Signature(签名) :使用头部和负载以及一个密钥(secret)来创建,以确保令牌没有被篡改。

主要流程是:

  1. 用户登录后,服务器生成一个 JWT 字符串,发送给客户端
  2. 客户端保存 JWT,并在调用接口时带上 JWT
  3. 服务端验证 JWT 的有效性,确认用户身份

优点:

  • 更容易实现无状态认证
  • JWT 可以包含扩展信息

缺点:

  • JWT 更易遭篡改,需要签名验证
  • 不容易注销 JWT

综合考虑,我推荐使用 OAuth 2.0 + JWT 的方案,来对 API 调用进行用户认证。这可以使接口既安全又方便使用。

一个使用JWT实现接口认证的简单示例:

// 导入JWT相关包
import (
  "github.com/dgrijalva/jwt-go"
  "time"
)

// 创建JWT中间件
func JWTAuthMiddleware() gin.HandlerFunc {

  return func(c *gin.Context) {
    token := c.Request.Header.Get("Authorization")

    // 解析token
    claims := &Claims{}
    tkn, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
        return jwtKey, nil
    })

    // 校验token
    if err != nil || !tkn.Valid {
        c.AbortWithStatus(401)
        return
    }

    // token校验通过,将claims信息设置到请求的context中
    c.Set("claims", claims)
  }
}

// 签发JWT
func GenerateToken(userID uint) (string, error) {
  claims := Claims{
     UserID: userID,
     StandardClaims: jwt.StandardClaims{
        ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
     }
  }

  // 使用HS256算法进行token签名
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  return token.SignedString(jwtKey)
}

在认证需要的接口中,使用JWTAuthMiddleware中间件即可完成JWT的解析验证和用户信息的获取。

总结

良好的 API 接口设计可以提高服务的开放性和易用性。而用户认证是保证 API 安全使用的重要手段。本文介绍了设计准则和认证实现方案,可以作为开放服务的参考。