[golang gin framework] 36. Gin mall project - RESTful API design guide, allowing cross-domain Cros, providing api interface to achieve front-end and back-end separation, and the use of JWT

1. RESTful API Design Guidelines

See: Interface specification for Restful API

2. Configure the server side in Gin to allow cross-domain

GitHub official address: https://github.com/gin-contrib/cors Configure cross-domain requests in the
main.go file

code show as below:

When using cors, you need to introduce the plug-in , first:
import (
"github.com/gin-contrib/cors"
)
and then run the command under main.go : go mod tidy , you can
package main

import (
    "fmt"
    "github.com/gin-contrib/sessions"
    _ "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-contrib/sessions/redis"
    "github.com/gin-gonic/gin"
    "gopkg.in/ini.v1"
    "goshop/models"
    "goshop/routers"
    "html/template"
    "github.com/gin-contrib/cors"
    "os"
    "path/filepath"
    "strings"
    "time"
)

func main() {
    //初始化路由,会设置默认中间件:engine.Use(Logger(), Recovery()),可以使用gin.New()来设置路由
    r := gin.Default()
    //配置gin允许跨域请求
    //默认配置
    //r.Use(cors.Default())
    r.Use(cors.New(cors.Config{  //自定义配置
        AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},  //允许的方法
        AllowHeaders:     []string{"Origin", "Content-Length", "Content-Type", "Authorization"},  //header允许山高月小
        AllowCredentials: false,
        MaxAge:           12 * time.Hour,  //有效时间
        ExposeHeaders:    []string{"Content-Length"},
        AllowOriginFunc: func(origin string) bool {  //允许的域
            return true  //所有
        },
    }))
    r.run()
}

3. The api interface realizes the separation of front and back ends

Projects in the front-end and back-end separation mode need to use the api interface to realize data interaction. Generally, the interface is designed using the RESTful API mode. When the front-end requests the interface, sometimes cross-domain problems occur, and cross-domain configuration on the server is required (see 2. Configure the server side in Gin to allow cross-domain ), the following example shows:
  1. routing

When configuring routing , you can iterate through projects. There are multiple versions of APIs, and the format is as follows:
package routers

import (
    "goshop/controllers/api"
    "github.com/gin-gonic/gin"
)

//设置api路由
func ApiRoutersInit(r *gin.Engine) {
    //多版本api
    apiRouters := r.Group("/v1")
    {
        //获取导航列表
        apiRouters.GET("/navList", api.NavController{}.Navlist)
        //登录操作
        apiRouters.POST("/doLogin", api.UserController{}.DoLogin)
        //编辑文章操作
        apiRouters.PUT("/editArticle", api.ArticleController{}.EditArticle)
        //删除评论操作
        apiRouters.DELETE("/deleteComment", api.CommentController{}.DeleteComment)
    }

    api2Routers := r.Group("/v2")
    {
        //获取导航列表
        api2Routers.GET("/navList", api.NavController{}.Navlist)
        //登录操作
        api2Routers.POST("/doLogin", api.UserController{}.DoLogin)
        //编辑文章操作
        api2Routers.PUT("/editArticle", api.ArticleController{}.EditArticle)
        //删除评论操作
        api2Routers.DELETE("/deleteComment", api.CommentController{}.DeleteComment)
   }
}
  1. controller code

Different functional modules can create different controllers under controllers/api/, such as:
navigation-related api: create a NavController.go controller under controllers/api/, which stores navigation-related apis (get top navigation, middle navigation. ..)
User-related api: create a UserController.go controller under controllers/api/, which stores user-related api (get user information, log in, logout,...)
Article-related api: create under controllers/api/ ArticleController.go controller, which stores article-related apis (get article list, article details,...)
Comment-related api: Create a CommentController.go controller under controllers/api/, which stores user comment-related apis (get user comments list, adding comments, deleting comments,...)
The following is a case of adding, deleting, modifying and checking:

Navigation related controllers

get navigation list
package api

import (
    "encoding/json"
    "goshop/models"
    "github.com/gin-gonic/gin"
    "net/http"
)

type V1Controller struct{}

//获取导航列表
func (con V1Controller) Navlist(c *gin.Context) {
    navList := []models.Nav{}
    models.DB.Find(&navList)
    c.JSON(http.StatusOK, gin.H{
        "navList": navList,
    })
}

The returned json data is as follows:

{
    "navList": [
        {
            "id": 1,
            "title": "商城1",
            "link": "http://www.xxx.com",
            "position": 2,
            "is_opennew": 2,
            "relation": "36,35",
            "sort": 10,
            "status": 1,
            "add_time": 1592919226,
            "goods_items": null
       },
       ...
    ]
}

User related controller

User login operation:
POST method
Note: There are two request formats for api requests:
1. form-data form format , the server needs to use c.PostForm to obtain data
2. Content-Type: application/json format, the server needs to use c.GetRawData( ) to get the data
as follows:
//api 当前端发送请求类型为:Content-Type: application/json,时,c.PostForm没法获取,需要通过c.GetRawData() 获取
//Content-Type: application/json; 发过来的数据需要通过c.GetRawData() 获取
//用户相关结构体: 在实际项目中,可以在models下创建相关结构体
type UserInfo struct {
    Username string `form:"username" json:"username"`
    Password string `form:"password" json:"password"`
}

//Content-Type: application/json; 发过来的数据需要通过c.GetRawData() 获取
func (con V1Controller) DoLogin(c *gin.Context) {
    var userInfo UserInfo
    b, _ := c.GetRawData() //从 c.Request.Body 读取请求数据
    err := json.Unmarshal(b, &userInfo)
    if err != nil {
        c.JSON(200, gin.H{
            "err": err.Error(),
        })
    } else {
        c.JSON(200, gin.H{
            "userInfo": userInfo,
        })
    }
}

//form-data 表单格式,服务器需使用c.PostForm获取数据
func (con V1Controller) DoLoginPost(c *gin.Context) {
    //实例化user结构体
    userInfo := models.User{}
    //获取请求的数据
    username:= c.PostForm("username")
    if username == "" {
        c.JSON(200, gin.H{
            "err": "用户名不能为空",
        })
    } else {
        c.JSON(200, gin.H{
            "username": username,
        })
    }
}

Article Related Controller

Modify article data:
//文章结构体: 在项目中可以在models下面创建结构体
type Article struct {
    Title   string `form:"title" json:"title"`
    Content string `form:"content" json:"content"`
}

//编辑
//Content-Type: application/json,发过来的数据需要通过c.GetRawData() 获取
func (con V1Controller) EditArticle(c *gin.Context) {
    var article Article
    b, _ := c.GetRawData() //从 c.Request.Body 读取请求数据
    err := json.Unmarshal(b, &article)
    if err != nil {
        c.JSON(200, gin.H{
            "err": err.Error(),
        })
    } else {
        c.JSON(200, gin.H{
            "article": article,
        })
    }
}

Comment related controllers

delete related comments
//删除
func (con V1Controller) DeleteNav(c *gin.Context) {
    id := c.Query("id")
    //执行删除逻辑操作
    c.JSON(200, gin.H{
        "message": "删除数据成功",
        "id":      id,
    })
}

4. JWT interface authority verification

  1. About the security verification of the interface

There are many solutions for interface security verification:
  • Session can be used to implement security verification

  • Sign the parameters of the request interface to implement signature verification of the interface

  • Use JWT to implement interface verification

  • ...

  1. Session-based security verification

Session is stored on the server. If there are few users, it is a simple security verification mechanism, but some configuration is required when cross-domain is involved. If the number of users is very, very large, it will consume a certain amount of server resources. Regarding cookies and sessions, cross-domain can be Reference: Solve the problem of vue requesting gin framework interface cros cross-domain cookie and session invalidation
  1. Encrypted signature verification of request parameters

Involving public keys, private keys, signatures, etc., such as payment-related functional interfaces
  1. JWT

The full name of JWT is JSON Web Token, which is another popular cross-domain authentication solution. It is also a security verification mechanism that is used badly by many people.
  1. Using JWT in Golang to implement security verification of interfaces

The https://github.com/dgrijalva/jwt-go module is used here , and the steps are as follows :

(1). Download import module

Introduce github.com/dgrijalva/jwt-go in import , and then run go mod tidy in the main.go directory
import ( 
    "fmt"
    "strings"
    "time"
    "github.com/gin-gonic/gin"
    "github.com/dgrijalva/jwt-go"
)

(2). Generate Jwt Token

1). Customize a structure

First, you need to customize a structure . This structure needs to inherit the jwt.StandardClaims structure. This structure can also customize the structure properties . The custom properties are used for Jwt value transfer
type MyClaims struct {
    Uid int
    jwt.StandardClaims
}

2). Define the private key key and expiration time of the generated structure

var jwtKey = []byte("123456")
var expireTime = time.Now().Add(24 * time.Hour).Unix()

3). Instantiate a custom structure and create a token

myClaimsObj := MyClaims{
    12, // 生成 token 的时候传值
    jwt.StandardClaims{
        ExpiresAt: expireTime, Issuer: "userinfo", // 签发人
}, }
// 使用指定的签名方法创建签名对象
tokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaimsObj)
// 使用指定的 secret 签名并获得完整的编码后的字符串 token
tokenStr, _ := tokenObj.SignedString(jwtkey)

(3). Verify Jwt Token

1). Get the token value passed by the client in Gin

Note:
1. After the token generated by the server is passed to the client, the client saves it in Authorization 2.
The token verification method generated by the server uses OAuth2.0 , so when the client requests the server, the TYPE of Authorization should be OAuth2 .0
tokenData := c.Request.Header.Get("Authorization")
tokenString := strings.Split(tokenData, " ")[1]

2). The server defines a method to verify the token

func ParseToken(tokenString string) (*jwt.Token, *MyClaims, error) {
    s := &MyClaims{}
    token, err := jwt.ParseWithClaims(tokenString, s, func(token *jwt.Token)(i interface{}, err error) {
    return jwtkey, nil
})

gin.Info(token, s)
return token, s, err
}

3). Verify the complete code

tokenData := c.Ctx.Input.Header("Authorization")
tokenString := strings.Split(tokenData, " ")[1]
if tokenString == "" {
    fmt.Println("权限不足")
} else {
    token, claims, err := ParseToken(tokenString)
if err != nil || !token.Valid {
    fmt.Println("权限不足")
} else {
    fmt.Println("验证通过")
    fmt.Println(claims.Uid)
}
}
  1. Specific use of JWT cases

Take the receiving address as an example after the user logs in (here, for convenience, the request is in GET mode), the specific steps:
1. The client requests the routing login, obtains the token generated by the server and saves it in Authorization 2. The client Request the routing addressList to obtain the user's delivery address; Note: the client must pass Authorization to the server for verification, and TYPE= OAuth2.0

(1). Routing

Add the following routes under routers/apiRouters.go
//登录操作(生成token)
apiRouters.GET("/login", api.UserController{}.Login)
//获取收货地址(校验token)
apiRouters.GET("/addressList", api.UserController{}.AddressList)

(2). The server generates a token

Under the models file, create MyClaims.go, encapsulate a MyClaims structure, creation method: set token, method of obtaining token
package models

import (
    "github.com/dgrijalva/jwt-go"
    "strings"
    "time"
)

//定义key和过期时间
var jwtKey = []byte("www.xxx.comx") //byte类型的切片
var expireTime = time.Now().Add(24 * time.Hour).Unix()

//自定义一个结构体,这个结构体需要继承 jwt.StandardClaims 结构体
type MyClaims struct {
    Uid int //自定义的属性 用于不同接口传值
    jwt.StandardClaims
}

//设置token
func SetToken(uid int) (string, error) {
    //实例化 存储token的结构体
    myClaimsObj := MyClaims{
        uid, //自定义参数: 可自行传值
        jwt.StandardClaims{
            ExpiresAt: expireTime, //过期时间
            Issuer:    "www.xxx.com",
        },
    }

    // 使用指定的签名方法创建签名对象
    tokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaimsObj)
    // 使用指定的 secret 签名并获得完整的编码后的字符串 token
    tokenStr, err := tokenObj.SignedString(jwtKey)
    if err != nil {
        return "", err
    }

    return tokenStr, nil
}

func GetToken(tokenData string, uid int) (int, error) {
    //获取token
    tokenStr := strings.Split(tokenData, " ")[1]
    //校验token
    token, myClaims, err := ParseToken(tokenStr)

    if err != nil || !token.Valid { //校验失败
        return 0, err
    } else {
        return myClaims.Uid, nil
    }
}

//验证token是否合法
func ParseToken(tokenStr string) (*jwt.Token, *MyClaims, error) {
    myClaims := &MyClaims{}
    token, err := jwt.ParseWithClaims(tokenStr, myClaims, func(token *jwt.Token) (i interface{}, err error) {
        return jwtKey, nil
    })
    return token, myClaims, err
}
Create login and addressList methods under controllers/api/UserController.go, and call GetToken and SetToken in the models.Myclaims structure to obtain and verify the token
//登录操作:获取token
func (con V1Controller) Login(c *gin.Context) {
    tokenStr, err := models.SetToken(11)
    if err != nil {
        c.JSON(200, gin.H{
            "message": "生成token失败重试",
            "success": false,
        })
        return
    }
    c.JSON(200, gin.H{
        "message": "获取token成功",
        "token":   tokenStr,
        "success": true,
    })
}

//获取收货地址
func (con V1Controller) AddressList(c *gin.Context) {
    //获取token
    tokenData := c.Request.Header.Get("Authorization")
    if len(tokenData) <= 0 {
        c.JSON(http.StatusOK, gin.H{
            "message": "token传入错误长度不合法",
            "success": false,
        })
    }

    uid, err := models.GetToken(tokenData, 11)
    if err != nil {  //校验失败
        c.JSON(http.StatusOK, gin.H{
            "message": err,
            "success": false,
        })
    }

    //校验成功
    c.JSON(http.StatusOK, gin.H{
        "uid": uid,
        "success": true,
    })
}

(3).PostMan verification

  1. Vue React Angular uses Axios to access Jwt-based interfaces

var token = localStorage.getItem('token');
this.$http.get("http://localhost:8080/api/addressList", {
    headers: {
    'Authorization': 'Bearer ' + token, }
    }).then(function (response) {
        console.log(response);
}).catch(function (error) {
    console.log(error);
})
  1. Some questions about Jwt

  • JWT can be used not only for authentication, but also for exchanging information. Effective use of JWT can reduce server queries

number of databases.

  • The biggest disadvantage of JWT is that since the server does not save the session state, it is impossible to abolish a certain session during use.

A token, or change the permissions of the token, that is, once the JWT is issued, there will always be a

No effect unless the server deploys additional logic.

  • JWT itself contains authentication information, once leaked, anyone can obtain all permissions of the token, in order to reduce

To avoid misappropriation, the validity period of JWT should be set relatively short. For some more important permissions, it should be checked again when using

user authenticates

  • In order to reduce misappropriation, JWT should not be transmitted in clear text using the HTTP protocol, but should be transmitted using the HTTPS protocol

[Previous section] [golang gin framework] 35.Gin mall project - user center production and order list data rendering (page display, order status, filter order search order, order details, and background order management function implementation logic)

[Next section] [golang gin framework] 37. Use of ElasticSearch full-text search engine

Guess you like

Origin blog.csdn.net/zhoupenghui168/article/details/130715687