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:
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)
}
}
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
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
...
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
Encrypted signature verification of request parameters
Involving public keys, private keys, signatures, etc., such as payment-related functional interfaces
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.
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)
}
}
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
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);
})
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
[Next section] [golang gin framework] 37. Use of ElasticSearch full-text search engine