Angular 连接到 Go Gin 时 8 个 CORS

Angular 8 CORS when connecting to Go Gin

我用 Golang 的 Gin framework and the JWT middleware for it. This is the official example from the readme 构建了一个后端,我使用的是:

main.go

package main

import (
    "log"
    "net/http"
    "os"
    "time"

    "github.com/appleboy/gin-jwt/v2"
    "github.com/gin-gonic/gin"
)

type login struct {
    Username string `form:"username" json:"username" binding:"required"`
    Password string `form:"password" json:"password" binding:"required"`
}

var identityKey = "id"

func helloHandler(c *gin.Context) {
    claims := jwt.ExtractClaims(c)
    user, _ := c.Get(identityKey)
    c.JSON(200, gin.H{
        "userID":   claims[identityKey],
        "userName": user.(*User).UserName,
        "text":     "Hello World.",
    })
}

// User demo
type User struct {
    UserName  string
    FirstName string
    LastName  string
}

func main() {
    port := os.Getenv("PORT")
    r := gin.New()
    r.Use(gin.Logger())
    r.Use(gin.Recovery())

    if port == "" {
        port = "8000"
    }

    // the jwt middleware
    authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
        Realm:       "test zone",
        Key:         []byte("secret key"),
        Timeout:     time.Hour,
        MaxRefresh:  time.Hour,
        IdentityKey: identityKey,
        PayloadFunc: func(data interface{}) jwt.MapClaims {
            if v, ok := data.(*User); ok {
                return jwt.MapClaims{
                    identityKey: v.UserName,
                }
            }
            return jwt.MapClaims{}
        },
        IdentityHandler: func(c *gin.Context) interface{} {
            claims := jwt.ExtractClaims(c)
            return &User{
                UserName: claims[identityKey].(string),
            }
        },
        Authenticator: func(c *gin.Context) (interface{}, error) {
            var loginVals login
            if err := c.ShouldBind(&loginVals); err != nil {
                return "", jwt.ErrMissingLoginValues
            }
            userID := loginVals.Username
            password := loginVals.Password

            if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
                return &User{
                    UserName:  userID,
                    LastName:  "Bo-Yi",
                    FirstName: "Wu",
                }, nil
            }

            return nil, jwt.ErrFailedAuthentication
        },
        Authorizator: func(data interface{}, c *gin.Context) bool {
            if v, ok := data.(*User); ok && v.UserName == "admin" {
                return true
            }

            return false
        },
        Unauthorized: func(c *gin.Context, code int, message string) {
            c.JSON(code, gin.H{
                "code":    code,
                "message": message,
            })
        },
        // TokenLookup is a string in the form of "<source>:<name>" that is used
        // to extract token from the request.
        // Optional. Default value "header:Authorization".
        // Possible values:
        // - "header:<name>"
        // - "query:<name>"
        // - "cookie:<name>"
        // - "param:<name>"
        TokenLookup: "header: Authorization, query: token, cookie: jwt",
        // TokenLookup: "query:token",
        // TokenLookup: "cookie:token",

        // TokenHeadName is a string in the header. Default value is "Bearer"
        TokenHeadName: "Bearer",

        // TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens.
        TimeFunc: time.Now,
    })

    if err != nil {
        log.Fatal("JWT Error:" + err.Error())
    }

    r.POST("/login", authMiddleware.LoginHandler)

    r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
        claims := jwt.ExtractClaims(c)
        log.Printf("NoRoute claims: %#v\n", claims)
        c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
    })

    auth := r.Group("/auth")
    // Refresh time can be longer than token timeout
    auth.GET("/refresh_token", authMiddleware.RefreshHandler)
    auth.Use(authMiddleware.MiddlewareFunc())
    {
        auth.GET("/hello", helloHandler)
    }

    if err := http.ListenAndServe(":"+port, r); err != nil {
        log.Fatal(err)
    }
}

我在 Angular 8 中的身份验证服务如下所示:

auth.service

headers = new HttpHeaders({ "Content-Type": "application/json" });

login(username: string, password: string): Promise<any> {
    const url: string = `${this.BASE_URL}` + "/login";
    const request = this.http
        .post(
            url,
            JSON.stringify({ username: username, password: password }),
            { headers: this.headers }
        )

        .toPromise();

    return request;
}

但这在 Chrome 中给了我一条错误消息:

Access to XMLHttpRequest at 'http://127.0.0.1:8000/api/login' from origin 'http://localhost:4200' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

虽然在控制台 Gin returns 状态代码 204。

我认为这是一个 CORS 问题,所以我实现了 Gin 的 CORS middleware:

r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"http://localhost:8000"},
    AllowMethods:     []string{"PUT", "PATCH"},
    AllowHeaders:     []string{"Origin"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    AllowOriginFunc: func(origin string) bool {
        return origin == "https://github.com"
    },
    MaxAge: 12 * time.Hour,
}))

不幸的是,它仍然没有用。如果我将 POST 方法添加到允许的方法中也不会:

AllowMethods:     []string{"PUT", "PATCH", "POST"}

我最后一次尝试是使用代理,如 Angular documentation:

中所述

proxy.conf.json

{
  "/api": {
    "target": "http://localhost:8000",
    "secure": false
  }
}

angular.json

"options": {
      "browserTarget": "your-application-name:build",
      "proxyConfig": "src/proxy.conf.json"
 }

我重新启动了ng serve,还是报错(我在auth.service和main.go中根据proxy文件中的配置将urls改为/api/login ).

我做错了什么?

AllowHeaders: []string{"Origin"}更改为AllowHeaders: []string{"content-type"}