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

73 阅读3分钟

构建API接口

使用标准库net/http

Go语言中net/http标准库提供了HTTP客户端和服务端的实现,可以用于创建HTTP请求、处理HTTP响应、构建Web服务器等。下面使用该库编写一个接收HTTP请求并返回响应的Server端示例。

package main

import (
    "net/http"
    "fmt"
)

func say(w http.ReponseWriter,r *http.Request){
    fmt.Fprintln(w,"hello world")
}

func main(){
    http.HandleFunc("/",say)
    err := http.ListenAndServe(":8080",nil)
    if err != nil{
        fmt.Println(err)
        return
    }
    
}

在这段代码中,http.HandleFunc("/",say)将请求路由为"/"的请求与say函数进行绑定。当客户端发送一个请求,请求的路径为”/"时,服务器会调用say函数来处理该请求。http.ListenAndServe(":8080",nil)用于创建一个HTTP服务器并在本机的8080端口上监听。say函数接收两个参数,http.ReponseWriter类型参数用于向客户端返回响应,*http.Request类型参数用于获取和处理请求。
使用go run main.go运行该函数后,打开电脑上的浏览器在地址栏输入127.0.0.1:8080回车,就能看到页面上出现"hello world"。

使用Gin框架

Gin是一个用于构建高性能Web应用程序的轻量级HTTP框架,它基于Go语言的net/http包进行封装,并提供了许多有用的功能和工具。

安装Gin

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

使用示例

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{
			"content": "hello world",
		})
	})
	r.Run()
}

在这段代码中,首先使用gin.Default()创建一个默认的路由引擎r,r.GET接收两个参数,一个是路由地址,另一个是处理函数,表示当接收指定路由地址的GET请求时,则执行后面的处理函数。该示例中当访问"/hello"时,则会执行后面的匿名函数。c.JSON用于向客户端返回一个json格式的字符串。 r.Run()用于启动HTTP服务,默认是在8080端口监听。
使用go run main.go运行该函数后,打开电脑上的浏览器在地址栏输入127.0.0.1:8080/hello回车,就能看到一串json格式的字符串。

用户认证

在Gin框架中使用JWT

JWT(JSON Web Token)是一种用于在网络应用间传递信息的安全跨域解决方案。它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场景下。

安装

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

自定义Claims

我们需要根据需求决定jwt中需要存储哪些数据。比如需要存储用户ID和用户名。

type MyClaims struct {
	  UserID   int64  `json:"user_id"`
	  UserName string `json:"user_name"`
	  jwt.StandardClaims
}

生成JWT

const TokenExpireDuration = time.Hour * 24
var CustomSecret = []byte("春夏秋冬")
func GenToken(userID int64, userName string) (string, error) {
	claims := MyClaims{
		userID,
		userName,
		jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpireDuration)),
			Issuer:    "my-project",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(CustomSecret)
}

解析JWT

func ParseToken(tokenString string) (*MyClaims, error) {
	var mc = new(MyClaims)
	token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (interface{}, error) {
		return CustomSecret, nil
	})
	if err != nil {
		return nil, err
	}
	if token.Valid {
		return mc, nil
	}
	return nil, errors.New("invalid token")
}

使用JWT

在Gin中,可以指定特定的路由,用于对外提供获取token的通道。

r.POST("/auth", authHandler)
func authHandler(c *gin.Context) {
    if ... {
        tokenString, _ := GenToken(userID,userName)
        c.JSON(http.StatusOK, gin.H{"token": tokenString,}
    }
}

用户通过上面的接口获取token后,后续就会携带token访问其它接口,这时候需要对这些请求中包含的token进行校验,以此决定是否允许被访问。由于访问其它所有接口都需要先进行token校验,因此可以使用中间件。这里假设携带的token放在请求头的Authorization中。

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{"msg":  "请求头中auth为空",}
			c.Abort()
			return
		}
		
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			c.JSON(http.StatusOK, gin.H{"msg":  "请求头中auth格式有误",}
			c.Abort()
			return
		}

		mc, err := jwt.ParseToken(parts[1])
		if err != nil {
			c.JSON(http.StatusOK, gin.H{"msg":  "无效的token",}
			c.Abort()
			return
		}

		// 将当前请求的userID信息保存到请求的上下文中
		c.Set("userID", mc.UserID)
		c.Next()
	}
}