首先让我们解释一下什么是CSRF(Cross-site request forgery),它是一种网络攻击方式,攻击者可以通过伪装成受信任的用户来执行未经授权的操作。为了防止这种攻击,我们需要在应用程序中实现CSRF保护。
Go语言提供了一些库来帮助我们实现CSRF保护。常用的有gorilla/csrf
和net/http
包中的csrf
。接下来分别针对这两个库进行示例说明。
gorilla/csrf库
gorilla/csrf
库是一个非常流行的Go语言CSRF保护库。
实现原理
该库的实现原理是使用“同步令牌”(Synchronizer Token)模式,即在每个表单中嵌入一个唯一的令牌,该令牌在每个请求中进行验证。
令牌生成有两种方式,一种是使用随机生成的字符串作为密钥,另一种是使用加密算法生成密钥。
在代码中使用该库的方式是:
import (
"net/http"
"github.com/gorilla/csrf"
)
func main() {
// 初始化CSRF保护中间件
csrfMiddleware := csrf.Protect([]byte("random-key"))
http.HandleFunc("/foo", csrfMiddleware(fooHandler)) // 在fooHandler中使用CSRF保护
http.ListenAndServe(":8080", nil)
}
func fooHandler(w http.ResponseWriter, r *http.Request) {
// 从请求中获取CSRF令牌
token := csrf.Token(r)
// 在模板中使用令牌
renderTemplate(w, "foo.html", token)
}
上面的代码中,我们使用了gorilla/csrf
库的Protect
函数创建了一个中间件,该中间件会在每个请求中自动验证CSRF令牌。在fooHandler
函数中,我们获取CSRF令牌并将其作为参数传递给模板,通过模板语言在表单中嵌入令牌。
示例说明
下面给出一个简单的示例,演示如何使用gorilla/csrf
库进行CSRF保护。
首先是HTML模板,注意表单中的csrf_token
字段:
<!-- login.html -->
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form action="/login" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<input type="hidden" name="csrf_token" value="{{.Token}}">
<button type="submit">Login</button>
</form>
</body>
</html>
接下来是Go语言代码,使用gorilla/csrf
库对登录路由进行CSRF保护,根据用户提交的表单进行登录验证:
// main.go
package main
import (
"fmt"
"html/template"
"net/http"
"github.com/gorilla/csrf"
)
const (
sessionKey = "loggedIn"
loginForm = `
{{if .Error}}
<p>{{.Error}}</p>
{{end}}
<form action="/login" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<input type="hidden" name="csrf_token" value="{{.Token}}">
<button type="submit">Login</button>
</form>
`
)
var (
templates = template.Must(template.New("").Parse(loginForm))
csrfMiddleware = csrf.Protect([]byte("random-key"))
)
type LoginForm struct {
Error string
Token string
}
func main() {
http.HandleFunc("/", homeHandler)
http.HandleFunc("/login", csrfMiddleware(loginHandler))
fmt.Println("Listening on http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
loggedInCookie, err := r.Cookie(sessionKey)
if err != nil || loggedInCookie.Value != "true" {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
token := csrf.Token(r)
templates.ExecuteTemplate(w, "login-form", LoginForm{Token: token})
} else {
fmt.Fprintf(w, "Welcome, you are logged in.")
}
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
r.ParseForm()
if r.PostFormValue("username") != "admin" || r.PostFormValue("password") != "password" {
token := csrf.Token(r)
templates.ExecuteTemplate(w, "login-form", LoginForm{Error: "Invalid credentials", Token: token})
} else {
loggedInCookie := http.Cookie{Name: sessionKey, Value: "true"}
http.SetCookie(w, &loggedInCookie)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
这个示例应该比较容易理解,主要是在表单中嵌入了CSRF令牌,并在路由中使用了gorilla/csrf
库的Protect
函数创建了一个中间件,对登录路由进行了CSRF保护。
net/http库
net/http
包中的csrf
库是Go语言官方提供的CSRF保护库。
实现原理
该库的实现原理也是使用“同步令牌”(Synchronizer Token)模式,和gorilla/csrf
库类似,在每个表单中嵌入一个唯一的令牌,该令牌在每个请求中进行验证。
令牌生成方式也是使用加密算法生成密钥。
在代码中使用该库的方式是:
import (
"net/http"
"net/http/cookiejar"
)
func main() {
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
// 获取CSRF令牌
resp, _ := client.Get("http://localhost:8080")
data, _ := ioutil.ReadAll(resp.Body)
token := getToken(data)
// 登录并发送POST请求
req, _ := http.NewRequest("POST", "http://localhost:8080/login", strings.NewReader("username=admin&password=password&csrf_token="+token))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, _ = client.Do(req)
data, _ = ioutil.ReadAll(resp.Body)
fmt.Println(string(data))
}
func getToken(data []byte) string {
re := regexp.MustCompile(`<input type="hidden" name="csrf_token" value="(.+?)">`)
match := re.FindSubmatch(data)
if len(match) > 1 {
return string(match[1])
}
return ""
}
上面的代码中,我们使用了net/http
包中的cookiejar
和http.Client
来模拟HTTP客户端,并使用client.Get
方法获取CSRF令牌,在表单中嵌入该令牌,然后发送POST请求进行登录和数据提交。
示例说明
下面给出一个简单的示例,演示如何使用net/http/csrf
库进行CSRF保护。
首先是HTML模板,注意表单中的csrf_token
字段:
<!-- login.html -->
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form action="/login" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<input type="hidden" name="csrf_token" value="{{.Token}}">
<button type="submit">Login</button>
</form>
</body>
</html>
接下来是Go语言代码,使用net/http/csrf
库对登录路由进行CSRF保护,根据用户提交的表单进行登录验证:
// main.go
package main
import (
"fmt"
"html/template"
"net/http"
"regexp"
"github.com/gorilla/csrf"
)
const (
sessionKey = "loggedIn"
loginForm = `
{{if .Error}}
<p>{{.Error}}</p>
{{end}}
<form action="/login" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<input type="hidden" name="csrf_token" value="{{.Token}}">
<button type="submit">Login</button>
</form>
`
)
var (
templates = template.Must(template.New("").Parse(loginForm))
)
type LoginForm struct {
Error string
Token string
}
func main() {
http.HandleFunc("/", homeHandler)
http.HandleFunc("/login", loginHandler)
fmt.Println("Listening on http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
token := csrf.Token(r)
templates.ExecuteTemplate(w, "login-form", LoginForm{Token: token})
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
r.ParseForm()
if r.PostFormValue("username") != "admin" || r.PostFormValue("password") != "password" {
token := csrf.Token(r)
templates.ExecuteTemplate(w, "login-form", LoginForm{Error: "Invalid credentials", Token: token})
} else {
http.SetCookie(w, &http.Cookie{Name: sessionKey, Value: "true"})
http.Redirect(w, r, "/", http.StatusSeeOther)
}
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
这个示例使用net/http/csrf
库和内置的net/http
包,对登录路由进行了CSRF保护,并监听HTTP请求进行处理。和之前的示例类似,同样在表单中嵌入了CSRF令牌,接收表单提交数据,并根据用户提交的数据进行登录验证。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:go语言csrf库使用实现原理示例解析 - Python技术站