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

46 阅读3分钟

本文章分为上下两章,在上篇文章里我们介绍了API的概念、快速构建服务、使用 gin 构建 API 接口、JWT。在下篇文章里我们将使用上篇文章所讲述的技术实现用户认证功能API。

0 场景假设

假设用户输入账号和密码通过User/Login接口登录账号,再通过User/Do接口获取自己的Id姓名账号密码等字段。

1 定义model

以下我们定义一个User结构体用于存放用户信息。

model/user.go

type User struct {
	Id       int
	Name     string
	Account  string
	Password string
}

2 实现json的生成解析校验

用户输入账号和密码通过User/Login接口登录账号需要服务端生成token返回给客户端进行保存,同时服务端也应该要保存token对应的用户。

登录成功后,用户想要通过User/Do接口获取自己的Id姓名账号密码等字段应该要发送token给服务端,服务端通过解析校验token获取到对应的用户再将用户信息返回给客户端。

在此之前我们先要声明以下变量:

  • var secretKey = []byte("your-secret-key"):存储秘钥
  • var tokenStore = make(map[string]model.User):存储token

以下三个函数都在common/jwt.go中

2.1 json生成

根据传来的用户以及秘钥进行加密,再建立秘钥与用户的映射关系

func GenerateToken(user model.User) (string, error) {
	claims := jwt.MapClaims{
		"id":      user.Id,
		"name":    user.Name,
		"account": user.Account,
		"exp":     time.Now().Add(time.Hour * 24).Unix(), // 过期时间设置为一天
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(secretKey)
	if err != nil {
		return "", err
	}

	tokenStore[tokenString] = user

	return tokenString, nil
}

2.2 解析token

通过用户传来的token,查找用户。

func ParseToken(tokenString string) (model.User, error) {
	var user model.User

	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		return secretKey, nil
	})

	if err != nil {
		return user, err
	}

	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		id := int(claims["id"].(float64))
		name := claims["name"].(string)
		account := claims["account"].(string)
		pwd:=claims["password"].(string)

		user.Id = id
		user.Name = name
		user.Account = account
		user.Password=pwd
		return user, nil
	}

	return user, fmt.Errorf("invalid token")
}

2.3 校验token

解析用户的请求并获取校验token。

func JWTLoginMiddleware() func(c *gin.Context) model.User {
	return func(c *gin.Context) model.User {

		loginHeader := c.Request.Header.Get("Login")
		if loginHeader == "" {
			c.JSON(http.StatusOK, gin.H{
				"code": 2003,
				"msg":  "请求头中login为空",
			})
			c.Abort()
			return model.User{Id: -1}
		}
		parts := strings.SplitN(loginHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			c.JSON(http.StatusOK, gin.H{
				"code":        2004,
				"loginHeader": loginHeader,
				"msg":         "请求头中login格式有误",
			})
			c.Abort()
			return model.User{Id: -1}
		}
		mc, err := ParseToken(parts[1])
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"code":     2005,
				"parts[1]": parts[1],
				"msg":      "无效的Token",
			})
			c.Abort()
			return model.User{Id: -1}
		}
		c.Next() 
		return mc
	}
}

3 实现controller

在controller中进行处理Handler

3.1 实现Login

假设数据库中有26672222这两个账户,他们的Id格式为账号+密码名字格式为user+账号。Login的实现如下。

controller/Login.go

func Login(c *gin.Context) {
	// 用户发送用户名和密码过来
	user := model.LoginReq{}
	err := c.ShouldBind(&user)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 2001,
			"msg":  "无效的参数",
		})
		return
	}
	// 校验用户名和密码是否正确
	if user.Account == "2667" && user.Password == "1234" ||
		user.Account == "2222" && user.Password == "1234" {
		id,_:=strconv.Atoi(user.Account+user.Password)
		// 生成Token
		tokenString, _ := common.GenerateToken(model.User{
			Id:    id  ,
			Name:     "user" + user.Account,
			Account:  user.Account,
			Password: user.Password,
		})
		c.JSON(http.StatusOK, gin.H{
			"code":  2000,
			"msg":   "success",
			"token": tokenString,
		})

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

3.2 实现Do

用户通过User/Do接口查询信息,Do的实现如下:

controller/Do.go

func Do(c *gin.Context) {
	mc := common.JWTLoginMiddleware()(c)
	c.JSON(http.StatusOK, mc)
}

4 实现router

定义两个接口/User/Login/User/Do实现登录和查询信息,端口设置为8080。

router.go

// 初始化一个http服务对象
var Engine = gin.Default()

func RegisterHandlers() {
	Engine.POST("/User/Login", controller.Login)
	Engine.POST("/User/Do", controller.Do)
	err := Engine.Run(":8080")
	if err != nil {
		return
	}
}

5 调用

main.go

func main() {
	RegisterHandlers()
}

6 测试

6.1 apifox介绍

Apifox 是接口管理、开发、测试全流程集成工具,定位 Postman + Swagger + Mock + JMeter。通过一套系统、一份数据,解决多个系统之间的数据同步问题。只要定义好接口文档,接口调试、数据 Mock、接口测试就可以直接使用,无需再次定义;接口文档和接口开发调试使用同一个工具,接口调试完成后即可保证和接口文档定义完全一致。高效、及时、准确!

6.2 登录测试

  • 输入以下正确信息得到的结果:

  • 错误账号:

  • 错误密码:

6.3 Do接口测试

  • 输入图1token的结果:

  • 错误token: