1. RESTful API 設計ガイドライン
2.クロスドメインを許可するように、Gin でサーバー側を構成します。
GitHub 公式アドレス: https://github.com/gin-contrib/cors
main.go ファイル でクロスドメイン リクエストを構成する
コードは以下のように表示されます。
cors を使用する場合は、プラグインを導入する 必要があります 。まず
import (
"github.com/gin-contrib/cors"
)
してから、 main.go の下でコマンドを実行します : go mod tiny 。
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. APIインターフェースによりフロントエンドとバックエンドの分離を実現
フロントエンドとバックエンド分離モードのプロジェクトでは、データ対話を実現するために API インターフェースを使用する必要があります。一般に、インターフェースは RESTful API モードを使用して設計されます。フロントエンドがインターフェースを要求すると、クロスドメインの問題が発生することがあり ます 。 、サーバー上のクロスドメイン構成が必要である場合 (「 2.クロスドメインを許可するように Gin でサーバー側を構成する 」を参照)、次の例は次のとおりです。
ルーティング
ルーティング を構成する ときは、プロジェクトを反復処理できます。API には複数のバージョンがあり、形式は次のとおりです。
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)
}
}
コントローラコード
さまざまな機能モジュールは、controllers/api/ の下にさまざまなコントローラーを作成できます。たとえば、次のようになります。
ナビゲーション関連の API: ナビゲーション関連の API (トップ ナビゲーション、中央ナビゲーションの取得など) を格納する、controllers/api/ の下に NavController.go コントローラーを作成します。
ユーザー関連 API: controllers/api/ の下に UserController.go コントローラーを作成します。これには、ユーザー関連 API (ユーザー情報の取得、ログイン、ログアウトなど) が格納されます。記事関連 API:controllers
/ api/ ArticleController の下に作成します。 go コントローラー、記事関連の API (記事リスト、記事の詳細の取得など) を保存します。
コメント関連の API: ユーザー コメント関連の API (ユーザー コメント リストの取得) を保存する、controllers/api/ の下に CommentController.go コントローラーを作成します。 、コメントの追加、コメントの削除、...)
追加、削除、変更、確認を行う場合は次のとおりです。
ナビゲーション関連のコントローラー
ナビゲーションリストを取得する
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,
})
}
返される json データは次のとおりです。
{
"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
},
...
]
}
ユーザー関連コントローラー
ユーザーログイン操作:
POST メソッド
注: API リクエストには 2 つのリクエスト形式があります:
1. form-data フォーム形式 、サーバーは データを取得するために c.PostForm
を使用する必要があります2. Content-Type: application/json 形式、サーバーが必要とする次のように c.GetRawData( ) を使用して データを取得します
。
//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,
})
}
}
記事関連のコントローラー
記事データを変更します。
//文章结构体: 在项目中可以在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,
})
}
}
コメント関連のコントローラー
関連するコメントを削除する
//删除
func (con V1Controller) DeleteNav(c *gin.Context) {
id := c.Query("id")
//执行删除逻辑操作
c.JSON(200, gin.H{
"message": "删除数据成功",
"id": id,
})
}
4. JWTインターフェース権限の検証
インターフェースのセキュリティ検証について
インターフェイスのセキュリティ検証には多くのソリューションがあります。
セッションを使用してセキュリティ検証を実装できる
リクエストインターフェイスのパラメータに署名して、インターフェイスの署名検証を実装します。
JWT を使用してインターフェース検証を実装する
...
セッションベースのセキュリティ検証
セッションはサーバーに保存されます。ユーザーが少ない場合は、単純なセキュリティ検証メカニズムですが、クロスドメインが関係する場合は、いくつかの設定が必要です。ユーザー数が非常に多い場合は、一定量を消費します。参考: vue が gin Framework インターフェースを要求するクロスドメイン Cookie とセッションの無効化の問題を解決する
リクエストパラメータの暗号化署名検証
公開キー、秘密キー、署名などを含む、支払い関連の機能インターフェイスなど
JWT
JWT の正式名は JSON Web Token で、これも人気のあるクロスドメイン認証ソリューションです。これは、多くの人によって悪用されているセキュリティ検証メカニズムでもあります。
Golang で JWT を使用してインターフェイスのセキュリティ検証を実装する
ここではhttps://github.com/dgrijalva/jwt-go モジュール が使用されており 、手順は次のとおりです 。
(1). インポートモジュールのダウンロード
import に github.com/dgrijalva/jwt-go を導入し、 main.go ディレクトリで go mod tiny を実行します。
import (
"fmt"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/dgrijalva/jwt-go"
)
(2). Jwtトークンの生成
1). 構造をカスタマイズする
まず、 構造体をカスタマイズする必要があります。この構造体は jwt.StandardClaims構造体を継承する必要があります 。この構造体は 構造体のプロパティもカスタマイズできます。カスタム プロパティは Jwt 値の転送に使用されます。
type MyClaims struct {
Uid int
jwt.StandardClaims
}
2). 生成された構造の秘密鍵キーと有効期限を定義します。
var jwtKey = []byte("123456")
var expireTime = time.Now().Add(24 * time.Hour).Unix()
3). カスタム構造をインスタンス化し、トークンを作成します。
myClaimsObj := MyClaims{
12, // 生成 token 的时候传值
jwt.StandardClaims{
ExpiresAt: expireTime, Issuer: "userinfo", // 签发人
}, }
// 使用指定的签名方法创建签名对象
tokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaimsObj)
// 使用指定的 secret 签名并获得完整的编码后的字符串 token
tokenStr, _ := tokenObj.SignedString(jwtkey)
(3). Jwt トークンの検証
1). クライアントによってGinに渡されたトークン値を取得します。
注:
1. サーバーによって生成されたトークンがクライアントに渡された後、クライアントはそれを 認可 に保存します。 2.
サーバーによって生成されたトークン検証メソッドは OAuth2.0 を使用するため、クライアントがサーバーにリクエストするときに、認可の TYPE OAuth2.0である必要があります
tokenData := c.Request.Header.Get("Authorization")
tokenString := strings.Split(tokenData, " ")[1]
2). サーバーはトークンを検証するメソッドを定義します。
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). 完全なコードを確認します
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)
}
}
JWT ケースの具体的な使用法
ユーザーがログインした後の受信アドレスを例に挙げます (ここでは、便宜上、リクエストは GET モードです)。具体的な手順は次のとおりです。 1. クライアントはルーティング ログインをリクエストし、サーバーによって生成されたトークンを取得して保存します
。 認可 2. クライアントはルーティング addressList を要求してユーザーの配送先アドレスを取得します。注: クライアントは検証のために認可をサーバーに渡し、TYPE= OAuth2.0 を指定する必要があります。
(1). ルーティング
routers/apiRouters.go に次のルートを追加します。
//登录操作(生成token)
apiRouters.GET("/login", api.UserController{}.Login)
//获取收货地址(校验token)
apiRouters.GET("/addressList", api.UserController{}.AddressList)
(2). サーバーがトークンを生成します
モデル ファイルの下に、MyClaims.go を作成し、MyClaims 構造をカプセル化します。作成メソッド: トークンの設定、トークンの取得メソッド
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
}
controllers/api/UserController.go の下に login メソッドと addressList メソッドを作成し、models.Myclaims 構造体の GetToken と SetToken を呼び出してトークンを取得して検証します。
//登录操作:获取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検証
Vue React Angular は Axios を使用して 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);
})
Jwt に関するいくつかの質問
JWT は認証だけでなく、情報の交換にも使用できます。JWT を効果的に使用すると、サーバーのクエリを削減できます
データベースの数。
JWT の最大の欠点は、サーバーがセッション状態を保存しないため、使用中に特定のセッションを破棄できないことです。
トークン、またはトークンの権限を変更します。つまり、JWT が発行されると、常に
サーバーが追加のロジックをデプロイしない限り、効果はありません。
JWT自体には認証情報が含まれており、一度漏洩すると誰でもトークンのすべての権限を取得できてしまうため、
流用を避けるため、JWT の有効期間は比較的短く設定し、重要な権限については使用時に再確認する必要があります。
ユーザー認証
悪用を減らすために、JWT は HTTP プロトコルを使用して平文で送信されるべきではなく、HTTPS プロトコルを使用して送信される必要があります。