[marco golang gin] 36. Proyecto Gin Mall: guía de diseño de API RESTful, que permite Cros entre dominios, proporciona una interfaz API para lograr la separación de front-end y back-end, y el uso de JWT

1. Directrices de diseño de API RESTful

Ver: Especificación de interfaz para API Restful

2. Configure el lado del servidor en Gin para permitir el cruce de dominios

Dirección oficial de GitHub: https://github.com/gin-contrib/cors Configurar solicitudes entre dominios en el archivo
main.go

el código se muestra a continuación:

Al usar cors, debe introducir el complemento , primero:
import (
"github.com/gin-contrib/cors"
)
y luego ejecute el comando en main.go : go mod tidy , puede
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. La interfaz api se da cuenta de la separación de los extremos delantero y trasero

Los proyectos en el modo de separación front-end y back-end necesitan usar la interfaz api para realizar la interacción de datos. Generalmente, la interfaz está diseñada usando el modo RESTful API . Cuando el front-end solicita la interfaz, a veces ocurren problemas entre dominios. , y se requiere configuración de dominios cruzados en el servidor (ver 2. Configurar el lado del servidor en Gin para permitir dominios cruzados ), el siguiente ejemplo muestra:
  1. enrutamiento

Al configurar el enrutamiento , puede iterar a través de los proyectos. Hay varias versiones de las API y el formato es el siguiente:
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. código del controlador

Diferentes módulos funcionales pueden crear diferentes controladores en controllers/api/, como:
api relacionada con la navegación: cree un controlador NavController.go en controllers/api/, que almacena apis relacionadas con la navegación (obtener navegación superior, navegación intermedia...)
API relacionada con el usuario: cree un controlador UserController.go en controllers/api/, que almacena la API relacionada con el usuario (obtener información del usuario, iniciar sesión, cerrar sesión,...)
API relacionada con el artículo: crear en controllers/api/ ArticleController. go, que almacena API relacionadas con artículos (obtener lista de artículos, detalles de artículos,...)
API relacionada con comentarios: cree un controlador CommentController.go en controllers/api/, que almacena API relacionadas con comentarios de usuarios (obtenga lista de comentarios de usuarios , agregar comentarios, eliminar comentarios,...)
El siguiente es un caso de agregar, eliminar, modificar y verificar:

Controladores relacionados con la navegación

obtener lista de navegación
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,
    })
}

Los datos json devueltos son los siguientes:

{
    "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
       },
       ...
    ]
}

Controlador relacionado con el usuario

Operación de inicio de sesión del usuario:
método POST
Nota: Hay dos formatos de solicitud para solicitudes api:
1. formato de formulario de datos de formulario , el servidor necesita usar c.PostForm para obtener datos
2. Tipo de contenido: formato de aplicación/json, el servidor necesita para usar c.GetRawData( ) para obtener los datos
de la siguiente manera:
//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,
        })
    }
}

Controlador relacionado con el artículo

Modificar datos del artículo:
//文章结构体: 在项目中可以在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,
        })
    }
}

Comentar controladores relacionados

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

4. Verificación de autoridad de interfaz JWT

  1. Sobre la verificación de seguridad de la interfaz

Hay muchas soluciones para la verificación de la seguridad de la interfaz:
  • La sesión se puede utilizar para implementar la verificación de seguridad

  • Firme los parámetros de la interfaz de solicitud para implementar la verificación de firma de la interfaz

  • Use JWT para implementar la verificación de interfaz

  • ...

  1. Verificación de seguridad basada en sesión

La sesión se almacena en el servidor. Si hay pocos usuarios, es un mecanismo simple de verificación de seguridad, pero se requiere cierta configuración cuando se trata de dominios cruzados. Si la cantidad de usuarios es muy, muy grande, consumirá una cierta cantidad de los recursos del servidor Con respecto a las cookies y las sesiones, los dominios cruzados pueden ser Referencia: Resuelva el problema de vue que solicita la interfaz del marco de gin entre la cookie de dominio cruzado y la invalidación de la sesión
  1. Verificación de firma cifrada de parámetros de solicitud

Involucrar claves públicas, claves privadas, firmas, etc., como interfaces funcionales relacionadas con pagos
  1. JWT

El nombre completo de JWT es JSON Web Token, que es otra solución popular de autenticación entre dominios. También es un mecanismo de verificación de seguridad que mucha gente usa mal.
  1. Uso de JWT en Golang para implementar la verificación de seguridad de las interfaces

Aquí se usa el módulo https://github.com/dgrijalva/jwt-go , y los pasos son los siguientes :

(1) Descargar módulo de importación

Introduzca github.com/dgrijalva/jwt-go en import y luego ejecute go mod tidy en el directorio main.go
import ( 
    "fmt"
    "strings"
    "time"
    "github.com/gin-gonic/gin"
    "github.com/dgrijalva/jwt-go"
)

(2) Generar token Jwt

1) Personaliza una estructura

Primero, debe personalizar una estructura . Esta estructura debe heredar la estructura jwt.StandardClaims . Esta estructura también puede personalizar las propiedades de la estructura . Las propiedades personalizadas se utilizan para la transferencia de valores Jwt.
type MyClaims struct {
    Uid int
    jwt.StandardClaims
}

2) Definir la clave de clave privada y el tiempo de vencimiento de la estructura generada

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

3) Crea una instancia de una estructura personalizada y crea un token

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

(3) Verificar token Jwt

1) Obtenga el valor del token pasado por el cliente en Gin

Nota:
1. Después de que el token generado por el servidor se pasa al cliente, el cliente lo guarda en Autorización 2.
El método de verificación de token generado por el servidor usa OAuth2.0 , por lo que cuando el cliente solicita el servidor, el TIPO de Autorización debe ser OAuth2 .0
tokenData := c.Request.Header.Get("Authorization")
tokenString := strings.Split(tokenData, " ")[1]

2) El servidor define un método para verificar el 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) Verificar el código completo

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. Uso específico de casos JWT

Tome la dirección de recepción como ejemplo después de que el usuario inicie sesión (aquí, por conveniencia, la solicitud está en modo GET), los pasos específicos:
1. El cliente solicita el inicio de sesión de enrutamiento, obtiene el token generado por el servidor y lo guarda en Autorización 2. El cliente Solicita la lista de direcciones de enrutamiento para obtener la dirección de entrega del usuario Nota: el cliente debe pasar la Autorización al servidor para su verificación y TIPO = OAuth2.0

(1) Enrutamiento

Agregue las siguientes rutas en routers/apiRouters.go
//登录操作(生成token)
apiRouters.GET("/login", api.UserController{}.Login)
//获取收货地址(校验token)
apiRouters.GET("/addressList", api.UserController{}.AddressList)

(2) El servidor genera un token

En el archivo de modelos, cree MyClaims.go, encapsule una estructura de MyClaims, método de creación: establecer token, método de obtención de 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
}
Cree métodos de inicio de sesión y lista de direcciones en controllers/api/UserController.go, y llame a GetToken y SetToken en la estructura models.Myclaims para obtener y verificar el 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).Verificación del cartero

  1. Vue React Angular usa Axios para acceder a interfaces basadas en Jwt

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. Algunas preguntas sobre Jwt

  • JWT se puede utilizar no solo para la autenticación, sino también para el intercambio de información. El uso efectivo de JWT puede reducir las consultas del servidor

número de bases de datos.

  • La mayor desventaja de JWT es que, dado que el servidor no guarda el estado de la sesión, es imposible eliminar una determinada sesión durante el uso.

Un token, o cambiar los permisos del token, es decir, una vez que se emite el JWT, siempre habrá un

Ningún efecto a menos que el servidor implemente lógica adicional.

  • JWT en sí contiene información de autenticación, una vez que se filtró, cualquiera puede obtener todos los permisos del token para reducir

Para evitar la apropiación indebida, el período de validez de JWT debe establecerse relativamente corto. Para algunos permisos más importantes, debe verificarse nuevamente al usar

usuario autentica

  • Para reducir la apropiación indebida, JWT no debe transmitirse en texto claro utilizando el protocolo HTTP, sino que debe transmitirse utilizando el protocolo HTTPS.

[Sección anterior] [marco de golang gin] 35. Proyecto de centro comercial Gin: producción del centro de usuarios y representación de datos de la lista de pedidos (visualización de página, estado del pedido, orden de búsqueda de pedido de filtro, detalles del pedido y lógica de implementación de la función de gestión de pedidos en segundo plano)

[Siguiente sección] [marco golang gin] 37. Uso del motor de búsqueda de texto completo ElasticSearch

Supongo que te gusta

Origin blog.csdn.net/zhoupenghui168/article/details/130715687
Recomendado
Clasificación