如何将我的服务开放给用户 | 青训营

47 阅读6分钟

构建 API 接口

RESTful API

RESTful API(Representational State Transferful Application Programming Interface)是一种设计和构建网络应用程序的架构风格,用于处理资源的访问和操作。它遵循一组约定和规则,使得客户端和服务器之间的通信变得简单、灵活和可伸缩。 以下是RESTful API的主要特点和概念:

  1. 资源(Resources): 在RESTful API中,所有数据都被视为资源。每个资源都有一个唯一的标识符(通常是URL),通过这个标识符来访问和操作资源。

  2. HTTP方法(HTTP Methods): RESTful API使用HTTP方法来表示对资源的不同操作。常用的HTTP方法包括:

  • GET:获取资源的信息。
  • POST:创建新资源。
  • PUT:更新现有资源。
  • DELETE:删除资源。
  1. 状态无关性(Stateless): RESTful API的通信是无状态的,每个请求都是独立的。服务器不会存储客户端的状态,所有必要的信息都包含在请求中。

  2. 统一接口(Uniform Interface): RESTful API采用一组统一的接口约定,包括使用统一的资源标识符(URL)来访问资源,使用标准的HTTP方法来操作资源,以及通过HTTP状态码来传达请求的结果。

  3. 资源的表示(Representation): 客户端可以通过不同的表示形式(如JSON、XML等)获取资源的信息。服务器将资源的状态以适当的表示形式返回给客户端。

  4. 超媒体驱动(HATEOAS): 这是RESTful API的一个特点,它允许服务器在响应中提供相关资源的链接,从而允许客户端动态地探索和访问其他相关资源。

  5. 缓存支持: RESTful API支持HTTP的缓存机制,以提高性能和降低服务器负载。

RESTful API的设计和使用遵循一些最佳实践,例如:

  • 使用有意义的URL,用于表示资源的层次结构和关系。
  • 使用适当的HTTP状态码来表示请求的结果,如成功、错误等。
  • 使用合适的HTTP头部来传递元数据,如身份验证信息、内容类型等。

通过遵循这些原则,RESTful API可以提供灵活、可扩展和易于理解的接口,使客户端和服务器之间的通信变得更加简单和高效。

Gin 框架

项目需要引入 Gin 框架。可以使用以下命令安装 Gin:

go get -u github.com/gin-gonic/gin

构建基础API接口

首先,创建一个简单的 API,使用 GET 获取资源的信息,访问 http://localhost:8080/hello 即可获取到hello world

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()

    r.GET("/hello", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "hello world",
        })
    })

    r.Run(":8080")
}

同时 Gin 还提供了中间件的支持,您可以在请求处理前后执行某些操作,如日志记录、身份验证等。

func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 执行前置操作
        log.Println("Request received")
​
        // 继续处理请求
        c.Next()
​
        // 执行后置操作
        log.Println("Request handled")
    }
}
​
func main() {
    r := gin.Default()
    r.Use(LoggerMiddleware())

    r.GET("/hello", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "hello world",
        })
    })

    r.Run(":8080")
}

用户认证

什么是用户认证

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

常见的用户认证方式

  1. 基本认证:用户通过在请求头中提供用户名和密码进行认证。但是这种方式不够安全,因为密码是明文传输的,容易被截获。
  2. Bearer令牌认证:用户在请求头中使用令牌来认证。令牌通常是由服务器签发的加密字符串,比较安全,不需要传输密码。
  3. OAuth认证:OAuth是一个用于授权的开放标准,允许用户授权第三方应用访问他们的资源。这是常见的社交媒体登录方式。

JWT

JWT(JSON Web Token) 是一种用于安全地在不同实体之间传递信息的开放标准。它通常用于身份验证和授权,以及在应用程序之间安全地传输声明(claims)。JWT是一种紧凑且自包含的格式,以JSON格式表示,可以通过网络传输,并且在受信任的实体之间进行验证。 一个 JWT 通常由三个部分组成,通过点号 . 分隔:

  1. Header(头部) :包含了令牌的类型(通常是 "JWT")和使用的加密算法,例如 HMAC SHA256 或 RSA。
  2. Payload(负载) :包含了一些声明(claims),如令牌的主题(subject)、到期时间(expiration time)、发布者(issuer)等。这些信息是关于令牌的附加信息。
  3. Signature(签名) :使用头部和负载以及一个密钥(secret)来创建,以确保令牌没有被篡改。

安装依赖

为了实现用户认证,使用 jwt-go 包来处理JWT令牌

go get -u github.com/dgrijalva/jwt-go

登录路由

创建登录的路由,用于用户的登录操作和获取token

type UserInfo struct {
   Username string `json:"username"`
   Password string `json:"password"`
}

func authHandler(c *gin.Context) {
   var user UserInfo
   err := c.ShouldBind(&user)
   if err != nil {
      c.JSON(http.StatusOK, gin.H{
         "code": 2001,
         "msg":  "无效的参数",
      })
      return
   }

   if user.Username == "cc" && user.Password == "123456" {
      //生成token
      tokenString, _ := GenToken(user.Username)
      c.JSON(http.StatusOK, gin.H{
         "code": 200,
         "msg":  "success",
         "data": gin.H{"token": tokenString},
      })
      return
   }

   c.JSON(http.StatusOK, gin.H{
      "code": 2002,
      "msg":  "鉴权失败",
   })
   return
}

获取信息路由

用登录路由生成的 token 获取用户信息

func homeHandler(c *gin.Context) {
   username := c.MustGet("username").(string)
   c.JSON(http.StatusOK, gin.H{
      "code": 2000,
      "msg":  "success",
      "data": gin.H{"username": username},
   })
}

生成JWT

在登录成功后,生成一个 JWT 并返回给客户端。这个 JWT 包含用户的 ID 信息,并在有效期内可用于后续请求的认证。

type MyClaims struct {
   Username string `json:"username"`
   jwt.StandardClaims
}

// 定义过期时间
const TokenExpireDuration = time.Hour * 2

//定义secret
var MySecret = []byte("这是一段用于生成token的密钥")

//生成jwt
func GenToken(username string) (string, error) {
   c := MyClaims{
      username,
      jwt.StandardClaims{
         ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
         Issuer:    "my-project",
      },
   }
   //使用指定的签名方法创建签名对象
   token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)

   //使用指定的secret签名并获得完成的编码后的字符串token
   return token.SignedString(MySecret)
}

解析JWT

//解析JWT
func ParseToken(tokenString string) (*MyClaims, error) {
   //解析token
   token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) {
      return MySecret, nil
   })
   if err != nil {
      return nil, err
   }
   if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
      return claims, nil
   }
   return nil, errors.New("invalid token")
}

基于JWT认证中间件

创建了一个中间件函数用于验证 JWT,然后使用这个中间件来保护需要认证的 API 路由

//基于JWT认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {
   return func(c *gin.Context) {
      authHeader := c.Request.Header.Get("Authorization")
      if authHeader == "" {
         c.JSON(http.StatusOK, gin.H{
            "code": 2003,
            "msg":  "请求头中的auth为空",
         })
         c.Abort()
         return
      }
      parts := strings.SplitN(authHeader, " ", 2)

      if !(len(parts) == 2 && parts[0] == "Bearer") {
         c.JSON(http.StatusOK, gin.H{
            "code": 2004,
            "msg":  "请求头中的auth格式错误",
         })
         //阻止调用后续的函数
         c.Abort()
         return
      }
      mc, err := ParseToken(parts[1])
      if err != nil {
         c.JSON(http.StatusOK, gin.H{
            "code": 2005,
            "msg":  "无效的token",
         })
         c.Abort()
         return
      }
      //将当前请求的username信息保存到请求的上下文c上
      c.Set("username", mc.Username)
      //后续的处理函数可以通过c.Get("username")来获取请求的用户信息
      c.Next()
   }

}

保护 API 路由

使用JWTAuthMiddleware中间件来保护需要认证的 API 路由,只有在提供有效 JWT 的情况下才能访问 /home 路由。

func main() {
   r := gin.Default()
   r.POST("/auth", authHandler)
   r.GET("/home", JWTAuthMiddleware(), homeHandler)
   r.Run(":9000")
}