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

这是青训营笔记的第六篇文章。

使用 gin 构建 API 接口

RESTful API 规范

我们应该可以发现,我们开发的一个个Web应用服务或者程序,其实就是对服务器的资源的CRUD(创建、检索、更新和删除),所以 RESTful API 的规范建议我们使用特定的HTTP方法来对服务器上的资源进行操作。

在 RESTful API 中,使用的主要是以下五种HTTP方法:

  1. GET,表示读取服务器上的资源
  2. POST,表示在服务器上创建资源
  3. PUT,表示更新或者替换服务器上的资源
  4. DELETE,表示删除服务器上的资源
  5. PATCH,表示更新/修改资源的一部分

这里我通过一些URL示例来更好的说明这些HTTP方法的使用。

HTTP GET https://www.flysnow.org/users
HTTP GET https://www.flysnow.org/users/123

以上是两个GET方法的示例,第一个表示获取所有用户的信息;第二个表示获取id123用户的信息。

HTTP POST https://www.flysnow.org/users

这表示创建一个用户,会通过POST给服务器提供创建这个用户所需的全部信息。注意这里users是个复数

HTTP PUT https://www.flysnow.org/users/123

这表示要更新/替换id123的这个用户,在更新的时候,会通过PUT提供更新这个用户需要的全部用户信息。这里PUTPOST不太一样的是,从URL上看,PUT操作的是单个资源,比如这里id123的这个用户。

HTTP DELETE https://www.flysnow.org/users/123

删除非常简单,也是操作单个资源,这里是删除id123的这个用户。

HTTP PATCH https://www.flysnow.org/users/123

PATCH也更新资源,它和PUT不一样的是,它只能更新这个资源的部分信息,而不是全部(这种也叫替换),是部分更新。所以我们无需提供全部用户信息给服务器,需要更新哪些就提供哪些。

RESTful API 规范关于这部分HTTP方法的定义是非常好的,这让我们可以在编写API的时候更加规范,可读性强,便于协作。RESTful API规范这么好,对于我们强大的Golang Gin这个框架来说,自然不会忘记,Golang Gin为我们提供了一套非常简洁的接口,让我们可以很容易的实现RESTful API的规范。

Gin RESTful API 实现

现在我们通过一个具体的Go代码示例,来演示如上我们所说的RESTful API定义的资源操作,但是这里的实现我们以演示Gin便捷的HTTP Method方法注册为主,会忽略很多具体业务逻辑的代码,并且不会严格的按照RESTful API的 Status Code 进行返回。

type User struct {
    ID   uint64
    Name string
}

首先我们定义一个用户User,用来表示我们需要操作的用户。

func main() {
    users := []User{{ID: 123, Name: "张三"}, {ID: 456, Name: "李四"}}
    r := gin.Default()
    r.GET("/users", func(c *gin.Context) {
        c.JSON(200, users)
    })
    r.Run(":8080")
}

这里我们通过GET方法就可以很容易的注册一HTTP GET请求的处理逻辑,这里是返回所有的用户信息,JSON的格式。

我们运行这段代码,在浏览器里打开http://localhost:8080/users,就可以看到如下信息:

[{"ID":123,"Name":"张三"},{"ID":456,"Name":"李四"}]

Gin不光为我们提供了快捷的GET方法,还有其他方法,可以很容易的让我们实现对应的HTTP Method方法注册。

r.POST("/users", func(context *gin.Context) {
        //创建一个用户
    })
    r.DELETE("/usrs/123", func(context *gin.Context) {
        //删除ID为123的用户
    })
    r.PUT("/usrs/123", func(context *gin.Context) {
        //更新ID为123的用户
    })

    r.PATCH("/usrs/123", func(context *gin.Context) {
        //更新ID为123用户的部分信息
    })

此外还有不常用的HEADOPTIONSTRACECONNECT等方法,从中可以看出,Gin的API非常友好,可以让我们很容易的使用相应的方法来注册我们对某个HTTP Method的处理。

有的朋友们可能会想,我这么一个个注册太麻烦了,比如想一次注册所有的HTTP Method的方法,有没有便捷的方式。别说,这个还真有。

// Any registers a route that matches all the HTTP methods.
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
    group.handle("GET", relativePath, handlers)
    group.handle("POST", relativePath, handlers)
    group.handle("PUT", relativePath, handlers)
    group.handle("PATCH", relativePath, handlers)
    group.handle("HEAD", relativePath, handlers)
    group.handle("OPTIONS", relativePath, handlers)
    group.handle("DELETE", relativePath, handlers)
    group.handle("CONNECT", relativePath, handlers)
    group.handle("TRACE", relativePath, handlers)
    return group.returnObj()
}

Gin提供了Any方法,可以一次性注册以上这些HTTP Method方法。如果你只想注册其中某两个、或者三个方法,Gin就没有这样的便捷方法了,不过Gin为我们提供了通用的Handle方法,我们可以包装一下使用。

func Handle(r *gin.Engine, httpMethods []string, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
    var routes gin.IRoutes
    for _, httpMethod := range httpMethods {
        routes = r.Handle(httpMethod, relativePath, handlers...)
    }
    return routes
}

有了这个函数,我们就可以类似如下这样使用:

Handle(r, []string{"GET", "POST"}, "/", func(c *gin.Context) {
        //同时注册GET、POST请求方法
    })

虽然这种方式比较便利,但是并不太推荐,因为他破坏了Resultful 规范中HTTP Method的约束。

在 gin 框架中使用 JWT

什么是 JWT?

JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token 实现方式,目前多用于前后端分离项目和 OAuth2.0 业务场景下。

安装

我们使用 Go 语言社区中的 jwt 相关库来构建我们的应用,例如:github.com/golang-jwt/…

go get github.com/golang-jwt/jwt/v4

本文将使用这个库来实现我们生成 JWT 和解析 JWT 的功能。

使用

首先我们注册一条路由 /auth,对外提供获取 Token 的渠道:

r.POST("/auth", authHandler)

我们的authHandler定义如下:

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 == "q1mi" && user.Password == "q1mi123" {
		// 生成Token
		tokenString, _ := GenToken(user.Username)
		c.JSON(http.StatusOK, gin.H{
			"code": 2000,
			"msg":  "success",
			"data": gin.H{"token": tokenString},
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": 2002,
		"msg":  "鉴权失败",
	})
	return
}

用户通过上面的接口获取Token之后,后续就会携带着Token再来请求我们的其他接口,这个时候就需要对这些请求的Token进行校验操作了,很显然我们应该实现一个检验Token的中间件,具体实现如下:

// JWTAuthMiddleware 基于JWT的认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {
	return func(c *gin.Context) {
		// 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI
		// 这里假设Token放在Header的Authorization中,并使用Bearer开头
		// 这里的具体实现方式要依据你的实际业务情况决定
		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
		}
		// parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它
		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.Next() // 后续的处理函数可以用过c.Get("username")来获取当前请求的用户信息
	}
}

注册一个/home路由,发个请求验证一下吧。

r.GET("/home", JWTAuthMiddleware(), homeHandler)

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},
	})
}

如果不想自己实现上述功能,你也可以使用Github上别人封装好的包,比如github.com/appleboy/gi…

全部评论

相关推荐

点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务