Go 保护应用的安全详解

在本章中,我们将介绍以下配方:

除了创建应用之外,保护 web 应用是最重要的方面之一,我们将在本章中学习。应用安全性是一个非常广泛的主题,可以通过各种方式实现,这超出了本章的范围。

在本章中,我们将只关注如何将我们的 Go web 应用从 HTTP 协议移动到 HTTPS,这通常被称为HTTP+TLS**(传输层安全性),以及使用JSON web 令牌**【JWTs】保护 Go web 应用 REST 端点,保护我们的应用免受跨站点请求伪造(CSRF)攻击。

要将在 HTTP 上运行的服务器移动到 HTTPS,我们必须做的第一件事是获取 SSL 证书,该证书可以是自签名的,也可以是由受信任的证书颁发机构(如 Comodo、Symantec 或 GoDaddy)签名的证书

要获得由可信证书颁发机构签署的 SSL 证书,我们必须向他们提供一个证书签署请求CSR),该请求主要由密钥对的公钥和一些附加信息组成,而自签名证书是您可以向自己颁发的证书,使用自己的私钥签名。

自签名证书可用于加密数据以及 CA 签名证书,但用户将显示一条警告,表示其计算机或浏览器不信任该证书。因此,您不应该将它们用于生产或公共服务器。

在这个配方中,我们将学习如何创建私钥、证书签名请求和自签名证书。

本配方假设您的机器上安装了openssl。要验证是否已安装,请执行以下命令:

$ openssl
OpenSSL> exit
  1. 通过执行以下命令,使用openssl生成私钥和证书签名请求:
$ openssl req -newkey rsa:2048 -nodes -keyout domain.key -out domain.csr -subj "/C=IN/ST=Mumbai/L=Andheri East/O=Packt/CN=packtpub.com"

这将产生以下输出:

  1. 生成一个证书,并使用我们刚刚通过执行以下命令创建的私钥对其进行签名:
$ openssl req -key domain.key -new -x509 -days 365 -out domain.crt -subj "/C=IN/ST=Mumbai/L=Andheri East/O=Packt/CN=packtpub.com"

一旦命令执行成功,我们可以看到生成了domain.keydomain.csrdomain.crt,其中domain.key是用于对 SSL 证书进行签名的 2048 位 RSA 私钥,domain.crtdomain.csr是由密钥对的公钥和一些附加信息组成的证书签名请求,签名时插入到证书中的。

让我们了解一下为生成证书签名请求而执行的命令:

  • -newkey rsa:2048选项创建一个新的证书请求和一个新的私钥,该私钥应为 2048 位,使用 RSA 算法生成。
  • -nodes选项指定创建的私钥不会使用密码短语进行加密。
  • -keyout domain.key选项指定要将新创建的私钥写入的文件名。
  • -out domain.csr选项指定要写入的输出文件名,或默认为标准输出。
  • -subj选项用指定数据替换输入请求的主题字段,并输出修改后的请求。如果我们没有指定此选项,那么我们必须通过OpenSSL回答 CSR 信息提示来完成此过程。

接下来,我们将了解为生成证书并使用私钥对其签名而执行的命令,如下所示:

openssl req -key domain.key -new -x509 -days 365 -out domain.crt -subj "/C=IN/ST=Mumbai/L=Andheri East/O=Packt/CN=packtpub.com"

-key选项指定从中读取私钥的文件。-x509选项输出自签名证书而不是证书请求。-days 365选项指定证书的认证天数。默认值为 30 天。

一旦 web 应用开发结束,我们很可能会将其部署到服务器上。在部署时,始终建议在 HTTPS 协议而不是 HTTP 上运行 web 应用,特别是对于公开的服务器。在本食谱中,我们将学习如何在围棋中做到这一点。

  1. 创建https-server.go,在这里我们将定义一个只写 Hello World 的处理程序!发送到所有 HTTPS 请求的 HTTP 响应流,如下所示:
package main
import 
(
  "fmt"
  "log"
  "net/http"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8443"
  HTTPS_CERTIFICATE = "domain.crt"
  DOMAIN_PRIVATE_KEY = "domain.key"
)
func helloWorld(w http.ResponseWriter, r *http.Request) 
{
  fmt.Fprintf(w, "Hello World!")
}
func main() 
{
  http.HandleFunc("/", helloWorld)
  err := http.ListenAndServeTLS(CONN_HOST+":"+CONN_PORT,
  HTTPS_CERTIFICATE, DOMAIN_PRIVATE_KEY, nil)
  if err != nil 
  {
    log.Fatal("error starting https server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run https-server.go

一旦我们运行该程序,HTTPS 服务器将在端口8443上开始本地侦听。 

浏览https://localhost:8443/会给我们带来你好世界!作为来自服务器的响应:

此外,从命令行执行带curl--insecure标志的GET请求将跳过证书验证,因为我们使用的是自签名证书:

$ curl -X GET https://localhost:8443/ --insecure
 Hello World!

让我们了解一下我们编写的程序:

  • const (CONN_HOST = "localhost" CONN_PORT = "8443" HTTPS_CERTIFICATE = "domain.crt" DOMAIN_PRIVATE_KEY = "domain.key"):在这里,我们声明了四个常量-CONN_HOST的值为localhostCONN_PORT的值为8443HTTPS_CERTIFICATE的值为domain.crt或自签名证书、DOMAIN_PRIVATE_KEY的值为domain.key或我们在之前配方中创建的私钥。
  • func helloWorld(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }:这是一个 Go 函数,以ResponseWriterRequest为输入参数,将Hello World!写入 HTTP 响应流。

接下来,我们声明了main()程序执行从何处开始。由于这种方法可以做很多事情,让我们逐行理解它:

  • http.HandleFunc("/", helloWorld):在这里,我们使用net/http包的HandleFunc使用 URL 模式/注册helloWorld函数,这意味着helloWorld被执行,每当我们访问 HTTPS URL 模式/时,将(http.ResponseWriter, *http.Request)作为输入传递给它。
  • err := http.ListenAndServeTLS(CONN_HOST+":"+CONN_PORT, HTTPS_CERTIFICATE, DOMAIN_PRIVATE_KEY, nil):在这里,我们调用http.ListenAndServeTLS为 HTTPS 请求提供服务,这些请求在单独的 Goroutine 中处理每个传入连接。ListenAndServeTLS接受四个参数服务器地址、SSL 证书、私钥和处理程序。这里,我们将服务器地址传递为localhost:8443,将自签名证书、私钥和处理程序传递为nil,这意味着我们要求服务器使用DefaultServeMux作为处理程序。
  • if err != nil { log.Fatal("error starting https server : ", err) return}:在这里,我们检查启动服务器是否有问题,如果有,则记录错误并以状态码 1 退出。

在编写 RESTful API 时,在允许用户访问之前对用户进行身份验证是非常常见的。对用户进行身份验证的先决条件是创建 API 路由,我们将在本配方中介绍。

  1. 使用go get命令安装github.com/gorilla/muxgithub.com/gorilla/handlers包,如下所示:
$ go get github.com/gorilla/mux
$ go get github.com/gorilla/handlers
  1. 创建http-rest-api.go,在这里我们将定义三条路由——/status/get-token/employees——以及它们的处理程序,如下所示:
package main
import 
(
  "encoding/json"
  "log"
  "net/http"
  "os"
  "github.com/gorilla/handlers"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
)
type Employee struct 
{
  Id int `json:"id"`
  FirstName string `json:"firstName"`
  LastName string `json:"lastName"`
}
type Employees []Employee
var employees []Employee
func init() 
{
  employees = Employees
  {
    Employee{Id: 1, FirstName: "Foo", LastName: "Bar"},
    Employee{Id: 2, FirstName: "Baz", LastName: "Qux"},
  }
}
func getStatus(w http.ResponseWriter, r *http.Request) 
{
  w.Write([]byte("API is up and running"))
}
func getEmployees(w http.ResponseWriter, r *http.Request) 
{
  json.NewEncoder(w).Encode(employees)
}
func getToken(w http.ResponseWriter, r *http.Request) 
{ 
  w.Write([]byte("Not Implemented"))
}
func main() 
{
  router := mux.NewRouter().StrictSlash(true)
  router.HandleFunc("/status", getStatus).Methods("GET")
  router.HandleFunc("/get-token", getToken).Methods("GET")
  router.HandleFunc("/employees", getEmployees).Methods("GET")
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT,
  handlers.LoggingHandler(os.Stdout, router))
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run http-rest-api.go

一旦我们运行程序,HTTP 服务器将在端口8080上开始本地侦听。

接下来,您可以从命令行执行一个GET请求,如下所示:

$ curl -X GET http://localhost:8080/status
 API is up and running

这将为您提供 RESTAPI 的状态。您可以从命令行执行GET请求,如下所示:

$ curl -X GET http://localhost:8080/employees
 [{"id":1,"firstName":"Foo","lastName":"Bar"},{"id":2,"firstName":"Baz","lastName":"Qux"}]

这将为您提供所有员工的列表。我们可以尝试通过命令行获取访问令牌,如下所示:

$ curl -X GET http://localhost:8080/get-token

我们将从服务器获取未实现的消息。

让我们了解一下我们编写的程序:

  • import ("encoding/json" "log" "net/http" "os" “github.com/gorilla/handlers" "github.com/gorilla/mux"):这里,我们导入github.com/gorilla/mux来创建 Gorilla Mux 路由器,github.com/gorilla/handlers来创建 Gorilla 日志处理程序,用于以 Apache 公共日志格式记录 HTTP 请求。
  • func getStatus(w http.ResponseWriter, r *http.Request) { w.Write([]byte("API is up and running"))}:这是一个处理程序,它只写 API 启动并运行到 HTTP 响应流。
  • func getEmployees(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(employees)}:这是一个将静态员工数组写入 HTTP 响应流的处理程序。
  • func notImplemented(w http.ResponseWriter, r *http.Request) { w.Write([]byte(“Not Implemented")) }:这是一个只将未实现的写入 HTTP 响应流的处理程序。
  • 然后,我们定义了main(),其中我们使用NewRouter()处理程序创建了一个gorilla/mux路由器实例,新路由的尾随斜杠行为为true,向其添加路由并注册处理程序,最后调用http.ListenAndServe来服务 HTTP 请求,这些请求在单独的 Goroutine 中处理每个传入连接。ListenAndServe接受两个参数:服务器地址和处理程序。这里,我们将服务器地址传递为localhost:8080,处理程序传递为 GorillaLoggingHandler,它以 Apache 通用日志格式记录 HTTP 请求。

为了保护 REST API 或服务端点的安全,您必须在 Go 中编写一个生成 JSON web 令牌的处理程序,或JWT

在这个配方中,我们将使用https://github.com/dgrijalva/jwt-go生成JWT,尽管您可以从 Go 中提供的许多第三方库中实现任何库,例如https://github.com/square/go-josehttps://github.com/tarent/loginsrv

  1. 使用go get命令安装github.com/dgrijalva/jwt-gogithub.com/gorilla/muxgithub.com/gorilla/handlers包,如下所示:
$ go get github.com/dgrijalva/jwt-go
$ go get github.com/gorilla/handlers
$ go get github.com/gorilla/mux
  1. 创建create-jwt.go,在这里我们将定义生成JWTgetToken处理程序,如下所示:
package main
import 
(
  "encoding/json"
  "log"
  "net/http"
  "os"
  "time"
  jwt "github.com/dgrijalva/jwt-go"
  "github.com/gorilla/handlers"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  CLAIM_ISSUER = "Packt"
  CLAIM_EXPIRY_IN_HOURS = 24
)
type Employee struct 
{
  Id int `json:"id"`
  FirstName string `json:"firstName"`
  LastName string `json:"lastName"`
}
type Employees []Employee
var employees []Employee
func init() 
{
  employees = Employees
  {
    Employee{Id: 1, FirstName: "Foo", LastName: "Bar"},
    Employee{Id: 2, FirstName: "Baz", LastName: "Qux"},
  }
}
var signature = []byte("secret")
func getToken(w http.ResponseWriter, r *http.Request) 
{
  claims := &jwt.StandardClaims
  {
    ExpiresAt: time.Now().Add(time.Hour *
    CLAIM_EXPIRY_IN_HOURS).Unix(),
    Issuer: CLAIM_ISSUER,
  }
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  tokenString, _ := token.SignedString(signature)
  w.Write([]byte(tokenString))
}
func getStatus(w http.ResponseWriter, r *http.Request) 
{
  w.Write([]byte("API is up and running"))
}
func getEmployees(w http.ResponseWriter, r *http.Request) 
{
  json.NewEncoder(w).Encode(employees)
}
func main() 
{
  muxRouter := mux.NewRouter().StrictSlash(true)
  muxRouter.HandleFunc("/status", getStatus).Methods("GET")
  muxRouter.HandleFunc("/get-token", getToken).Methods("GET")
  muxRouter.HandleFunc("/employees", getEmployees).Methods("GET")
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT,
  handlers.LoggingHandler(os.Stdout, muxRouter))
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run create-jwt.go

一旦我们运行程序,HTTP 服务器将在端口8080上开始本地侦听。 

接下来,我们从命令行执行一个GET请求,如下所示:

$ curl -X GET http://localhost:8080/status
 API is up and running

它将为您提供 API 的状态。接下来,我们从命令行执行一个GET请求,如下所示:

$ curl -X GET http://localhost:8080/employees
 [{"id":1,"firstName":"Foo","lastName":"Bar"},{"id":2,"firstName":"Baz","lastName":"Qux"}]

它会给你一份所有员工的名单。接下来,让我们尝试通过命令行获取 REST API 的访问令牌:

$ curl -X GET http://localhost:8080/get-token

它将为我们提供生成的 JWT 令牌:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTM1MDY4ODEsImlzcyI6IlBhY2t0In0.95vuiR7lpWt4AIBDasBzOffL_Xv78_J9rcrKkeqSW08

接下来,浏览到https://jwt.io/并粘贴编码部分生成的令牌,查看其解码值,如下图所示:

让我们了解一下我们在这个配方中引入的变化:

  • import ( "encoding/json" "log" "net/http" "os" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gorilla/handlers" "github.com/gorilla/mux"):在这里,我们导入了一个额外的包——github.com/dgrijalva/jwt-go——它具有 JWT 的 Go 实现。
  • const ( CONN_HOST = "localhost" CONN_PORT = "8080" CLAIM_ISSUER = "Packt" CLAIM_EXPIRY_IN_HOURS = 24 ):在这里,我们引入了两个额外的常量,一个是CLAIM_ISSUER,用于标识签发 JWT 的委托人,另一个是CLAIM_EXPIRY_IN_HOURS,用于标识 JWT 不得接受处理的到期时间。
  • var signature = []byte("secret"):这是服务器持有的签名。使用此功能,服务器将能够验证现有令牌并对新令牌进行签名。

接下来,我们定义了一个getToken处理程序,我们首先使用JWT StandardClaims处理程序准备了一个 claims 对象,然后使用jwt NewWithClaims处理程序生成一个 JWT 令牌,最后使用服务器签名对其进行签名并将其写入 HTTP 响应流

一旦有了 RESTAPI 端点和 JWT 令牌生成器处理程序,我们就可以轻松地用 JWT 保护端点,我们将在本配方中介绍。

  1. 使用go get命令安装github.com/auth0/go-jwt-middlewaregithub.com/dgrijalva/jwt-gogithub.com/gorilla/muxgithub.com/gorilla/handlers包,如下所示:
$ go get github.com/auth0/go-jwt-middleware
$ go get github.com/dgrijalva/jwt-go
$ go get github.com/gorilla/handlers
$ go get github.com/gorilla/mux
  1. 创建http-rest-api-secured.go,在这里我们将定义 JWT 中间件来检查 HTTP 请求上的 JWT,并用它包装/employees路由,如下所示:
package main
import 
(
  "encoding/json"
  "log"
  "net/http"
  "os"
  "time"
  jwtmiddleware "github.com/auth0/go-jwt-middleware"
  jwt "github.com/dgrijalva/jwt-go"
  "github.com/gorilla/handlers"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  CLAIM_ISSUER = "Packt"
  CLAIM_EXPIRY_IN_HOURS = 24
)
type Employee struct 
{
  Id int `json:"id"`
  FirstName string `json:"firstName"`
  LastName string `json:"lastName"`
}
type Employees []Employee
var employees []Employee
func init() 
{
  employees = Employees
  {
    Employee{Id: 1, FirstName: "Foo", LastName: "Bar"},
    Employee{Id: 2, FirstName: "Baz", LastName: "Qux"},
  }
}
var signature = []byte("secret")
var jwtMiddleware = jwtmiddleware.New
(
  jwtmiddleware.Options
  {
    ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) 
    {
      return signature, nil
    },
    SigningMethod: jwt.SigningMethodHS256,
  }
)
func getToken(w http.ResponseWriter, r *http.Request) 
{
  claims := &jwt.StandardClaims
  {
    ExpiresAt: time.Now().Add(time.Hour *
    CLAIM_EXPIRY_IN_HOURS).Unix(),
    Issuer: CLAIM_ISSUER,
  }
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  tokenString, _ := token.SignedString(signature)
  w.Write([]byte(tokenString))
}
func getStatus(w http.ResponseWriter, r *http.Request) 
{
  w.Write([]byte("API is up and running"))
}
func getEmployees(w http.ResponseWriter, r *http.Request) 
{
  json.NewEncoder(w).Encode(employees)
}
func main() 
{
  muxRouter := mux.NewRouter().StrictSlash(true)
  muxRouter.HandleFunc("/status", getStatus).Methods("GET")
  muxRouter.HandleFunc("/get-token", getToken).Methods("GET")
  muxRouter.Handle("/employees", jwtMiddleware.Handler
  (http.HandlerFunc(getEmployees))).Methods("GET")
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT,
  handlers.LoggingHandler(os.Stdout, muxRouter))
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run http-rest-api-secured.go

一旦我们运行程序,HTTP 服务器将在端口8080上开始本地侦听。 

接下来,我们从命令行执行一个GET请求,如下所示:

$ curl -X GET http://localhost:8080/status
 API is up and running

它将为您提供 API 的状态。接下来,我们从命令行执行一个GET请求,如下所示:

$ curl -X GET http://localhost:8080/employees
 Required authorization token not found

它将向我们显示请求中未找到 JWT 的消息。因此,要获取所有员工的列表,我们必须获取 API 的访问令牌,我们可以通过执行以下命令来获取:

$ curl -X GET http://localhost:8080/get-token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTM1MTI2NTksImlzcyI6IlBhY2t0In0.2r_q_82erdOmt862ofluiMGr3O5x5_c0_sMyW7Pi5XE

现在,调用 employee API,再次将 JWT 作为 HTTPAuthorization请求头传递,如下所示:

$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTM1MTI2NTksImlzcyI6IlBhY2t0In0.2r_q_82erdOmt862ofluiMGr3O5x5_c0_sMyW7Pi5XE" http://localhost:8080/employees

它将为您提供所有员工的列表,如下所示:

[{"id":1,"firstName":"Foo","lastName":"Bar"},{"id":2,"firstName":"Baz","lastName":"Qux"}]

让我们了解一下我们在这个配方中引入的变化:

  1. 使用import ( "encoding/json" "log" "net/http" "os" "time" jwtmiddleware "github.com/auth0/go-jwt-middleware" jwt "github.com/dgrijalva/jwt-go" "github.com/gorilla/handlers" "github.com/gorilla/mux"),我们导入了一个额外的包github.com/auth0/go-jwt-middleware,别名为jwtmiddleware,用于检查 HTTP 请求上的 JWT。
  2. 然后,我们构建了一个新的安全实例jwtmiddleware,将SigningMethod作为HS256传递,ValidationKeyGetter选项作为 Go 函数,返回密钥以验证 JWT。这里,服务器签名用作验证 JWT 的密钥。
  3. 最后,我们在main()中用jwtmiddleware处理程序包装了/employees路由,这意味着对于 URL 模式为/employees的每个请求,我们在提供响应之前检查并验证 JWT。

保护 web 应用不受恶意网站、电子邮件、博客、即时消息或攻击受信任站点的程序的攻击是一种常见做法,用户当前已通过该站点的身份验证,以防止不必要的操作。我们通常称这种跨站点请求伪造。

使用 Gorilla CSRF 包在 Go 中实现跨站点请求伪造相当容易,我们将在本配方中介绍它。

  1. 使用go get命令安装github.com/gorilla/csrfgithub.com/gorilla/mux包,如下所示:
$ go get github.com/gorilla/csrf
$ go get github.com/gorilla/mux
  1. 创建带有名称和电子邮件输入文本字段的sign-up.html,以及在提交 HTML 表单时调用的操作,如下所示:
<html>
  <head>
    <title>Sign Up!</title>
  </head>
  <body>
    <form method="POST" action="/post" accept-charset="UTF-8">
      <input type="text" name="name">
      <input type="text" name="email">
      {{ .csrfField }}
      <input type="submit" value="Sign up!">
    </form>
  </body>
</html>
  1. 创建prevent-csrf.go,其中我们创建一个呈现注册 HTML 表单的signUp处理程序和一个post处理程序,该处理程序在提交 HTML 表单且请求具有有效的 CSRF 令牌时执行,如下所示:
package main
import 
(
  "fmt"
  "html/template"
  "log"
  "net/http"
  "github.com/gorilla/csrf"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8443"
  HTTPS_CERTIFICATE = "domain.crt"
  DOMAIN_PRIVATE_KEY = "domain.key"
)
var AUTH_KEY = []byte("authentication-key")
func signUp(w http.ResponseWriter, r *http.Request) 
{
  parsedTemplate, _ := template.ParseFiles("sign-up.html")
  err := parsedTemplate.Execute
  (
    w, map[string]interface{}
    {
      csrf.TemplateTag: csrf.TemplateField(r),
    }
  )
  if err != nil 
  {
    log.Printf("Error occurred while executing the 
    template : ", err)
    return
  }
}
func post(w http.ResponseWriter, r *http.Request) 
{
  err := r.ParseForm()
  if err != nil 
  {
    log.Print("error occurred while parsing form ", err)
  }
  name := r.FormValue("name")
  fmt.Fprintf(w, "Hi %s", name)
}
func main() 
{
  muxRouter := mux.NewRouter().StrictSlash(true)
  muxRouter.HandleFunc("/signup", signUp)
  muxRouter.HandleFunc("/post", post)
  http.ListenAndServeTLS(CONN_HOST+":"+CONN_PORT, 
  HTTPS_CERTIFICATE, DOMAIN_PRIVATE_KEY, csrf.Protect
  (AUTH_KEY)(muxRouter))
}
  1. 使用以下命令运行程序:
$ go run prevent-csrf.go

一旦我们运行程序,HTTP 服务器将在端口8443上开始本地侦听。

接下来,从命令行执行一个POST请求,如下所示:

$ curl -X POST --data "name=Foo&email=aggarwalarpit.89@gmail.com" https://localhost:8443/post --insecure

它将向您发送禁止-CSRF 令牌无效消息作为服务器的响应,并禁止您提交 HTML 表单,因为服务器未在请求中找到有效的 CSRF 令牌:

因此,要提交表单,首先我们必须注册,它通过执行以下命令生成有效的 CSRF 令牌:

$ curl -i -X GET https://localhost:8443/signup --insecure

这将为您提供一个 HTTPX-CSRF-Token,如以下屏幕截图所示:

现在您必须将其作为 HTTPX-CSRF-Token请求头和 HTTP cookie 一起传递,以提交 HTML 表单,如下所示:

$ curl -X POST --data "name=Foo&email=aggarwalarpit.89@gmail.com" -H "X-CSRF-Token: M9gqV7rRcXERvSJVRSYprcMzwtFmjEHKXRm6C8cDC4EjTLIt4OiNzVrHfYNB12nEx280rrKs8fqOgvfcJgQiFA==" --cookie "_gorilla_csrf=MTUyMzQzMjg0OXxJa1ZLVTFsbGJHODFMMHg0VEdWc0wxZENVRVpCWVZGU1l6bHVMMVZKVEVGM01EVjBUakVyUlVoTFdsVTlJZ289fJI5dumuyObaHVp97GN_CiZBCCpnbO0wlIwgSgvHL7-C;" https://localhost:8443/post --insecure

Hi Foo

让我们了解一下我们编写的程序:

  • const (CONN_HOST = "localhost" CONN_PORT = "8443" HTTPS_CERTIFICATE = "domain.crt" DOMAIN_PRIVATE_KEY = "domain.key"):在这里,我们声明了四个常量-CONN_HOST的值为localhostCONN_PORT的值为8443HTTPS_CERTIFICATE的值为domain.crt或自签名证书、DOMAIN_PRIVATE_KEY的值为domain.key或我们在之前配方中创建的私钥。
  • var AUTH_KEY = []byte("authentication-key"):用于生成 CSRF 令牌的认证密钥。
  • signUp:这是一个解析sign-up.html并提供一个<input>字段的处理程序,该字段填充了一个 CSRF 令牌来替换表单中的{{ .csrfField }}
  • post:这是一个处理程序,它解析提交的表单,获取名称输入字段的值,并将其写入 HTTP 响应流。

最后,我们定义了main(),其中我们使用NewRouter()处理程序创建了一个gorilla/mux路由器实例,新路由的尾部斜杠行为为true,将/signup路由注册为signUp处理程序,将/post路由注册为post处理程序,并将http.ListenAndServeTLS传递处理程序称为csrf.Protect(AUTH_KEY)(muxRouter),确保所有没有有效令牌的POST请求将返回HTTP 403 Forbidden

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

推荐系统三十六式 -〔刑无刀〕

从0开始学架构 -〔李运华〕

Java核心技术面试精讲 -〔杨晓峰〕

机器学习40讲 -〔王天一〕

架构实战案例解析 -〔王庆友〕

Kafka核心源码解读 -〔胡夕〕

大数据经典论文解读 -〔徐文浩〕

反爬虫兵法演绎20讲 -〔DS Hunter〕

后端工程师的高阶面经 -〔邓明〕