我正在寻找一小部分限速中间件:
然后我可以围绕身份验证路由或其他可能容易遭受暴力攻击的路由(即使用到期的令牌密码重置URL等)。有人粗暴地强制使用16或24字节令牌的可能性非常低,但进行额外步骤并不会有什么坏处。
我已经查看了https://code.google.com/p/go-wiki/wiki/RateLimiting,但我不确定如何将其与http.Request(s)进行协调。此外,我不确定我们如何跟踪"在任何时间段内来自特定IP的请求。
理想情况下,我最终会得到类似的结果,并指出我在反向代理(nginx)后面,所以我们要检查REMOTE_ADDR
HTTP标头,而不是{ {1}}:
r.RemoteAddr
我很欣赏这里的一些指导。
答案 0 :(得分:11)
您链接的限速示例是一般的。它使用范围,因为它通过通道获取请求。
这是一个与HTTP请求不同的故事,但这里没有什么真正复杂的。请注意,您不会迭代请求通道或任何内容 - 为每个传入请求分别调用HandlerFunc 。
func rateLimit(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
remoteIP := r.Header.Get("REMOTE_ADDR")
if exceededTheLimit(remoteIP) {
w.WriteHeader(429)
// it then returns, not passing the request down the chain
} else {
h.ServeHTTP(w, r);
}
}
}
现在,选择存储速率限制计数器的位置取决于您。一种解决方案是简单地使用将IP映射到其请求计数器的全局映射(不要忘记安全的并发访问)。但是,您必须知道请求的持续时间。
Sergio建议使用Redis。它的键值特性非常适合这种简单的结构,你可以免费使用。
答案 1 :(得分:4)
您可以将数据存储在redis中。这是一个非常有用的命令,甚至在其文档中提到了速率限制应用程序:INCR。 Redis还将处理旧数据的清理(通过旧密钥到期)。
此外,由于redis是速率限制器存储,您可以使用共享此中央存储的多个前端进程。
有人会争辩说,每次进入外部流程都很昂贵。但密码重置页面不是一种绝对要求最佳性能的页面。此外,如果将redis放在同一台机器上,延迟应该非常低。答案 2 :(得分:4)
今天早上我做了一些简单而类似的事情,我认为这可以帮助你解决问题。
package main
import (
"log"
"net/http"
"strings"
"time"
)
func main() {
fs := http.FileServer(http.Dir("./html/"))
http.Handle("/", fs)
log.Println("Listening..")
go clearLastRequestsIPs()
go clearBlockedIPs()
err := http.ListenAndServe(":8080", middleware(nil))
if err != nil {
log.Fatalln(err)
}
}
// Stores last requests IPs
var lastRequestsIPs []string
// Block IP for 6 hours
var blockedIPs []string
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ipAddr := strings.Split(r.RemoteAddr, ":")[0]
if existsBlockedIP(ipAddr) {
http.Error(w, "", http.StatusTooManyRequests)
return
}
// how many requests the current IP made in last 5 mins
requestCounter := 0
for _, ip := range lastRequestsIPs {
if ip == ipAddr {
requestCounter++
}
}
if requestCounter >= 1000 {
blockedIPs = append(blockedIPs, ipAddr)
http.Error(w, "", http.StatusTooManyRequests)
return
}
lastRequestsIPs = append(lastRequestsIPs, ipAddr)
// Don't cut the chain of middlewares
if next == nil {
http.DefaultServeMux.ServeHTTP(w, r)
return
}
next.ServeHTTP(w, r)
})
}
func existsBlockedIP(ipAddr string) bool {
for _, ip := range blockedIPs {
if ip == ipAddr {
return true
}
}
return false
}
func existsLastRequest(ipAddr string) bool {
for _, ip := range lastRequestsIPs {
if ip == ipAddr {
return true
}
}
return false
}
// Clears lastRequestsIPs array every 5 mins
func clearLastRequestsIPs() {
for {
lastRequestsIPs = []string{}
time.Sleep(time.Minute * 5)
}
}
// Clears blockedIPs array every 6 hours
func clearBlockedIPs() {
for {
blockedIPs = []string{}
time.Sleep(time.Hour * 6)
}
}
它仍然不精确,但是,作为速率限制器的简单示例,它会有所帮助。您可以通过添加请求的路径,http方法甚至身份验证作为因素来改进它,以确定流是否是攻击。
答案 3 :(得分:2)
这是我的速率限制中间件实现。它作为全局速率限制器或单个请求的速率限制器非常好用。我在我的应用程序中广泛使用它。
这是你得到的:
首先,实施:
r := router.New()
stats := stats.New()
r.With(middleware.RateLimit(1, time.Minute * 1, stats)).Post("/contact", c.Contact)
在向POST
提出/contact
请求时,中间件将允许一个请求宠物分钟。
这是中间件:
package middleware
import (
"net/http"
"strconv"
"time"
)
// Stats is an interface to an underlying hash table/map data
// structure. Implement it however you'd like.
type Stats interface {
// Reset will reset the map.
Reset()
// Add would add "count" to the map at the key of "identifier",
// and returns an int which is the total count of the value
// at that key.
Add(identifier string, count int) int
}
// RateLimit middleware is a generic rate limiter that can be used in any scenario
// because it allows granular rate limiting for each specific request. Or you can
// set the rate limiter on the entire router group. It's just a HandlerFunc.
func RateLimit(limit int, window time.Duration, stats Stats) func(next http.Handler) http.Handler {
var windowStart time.Time
// Clear the rate limit stats after each window.
ticker := time.NewTicker(window)
go func() {
windowStart = time.Now()
for range ticker.C {
windowStart = time.Now()
stats.Reset()
}
}()
return func(next http.Handler) http.Handler {
h := func(w http.ResponseWriter, r *http.Request) {
value := int(stats.Add(identifyRequest(r), 1))
XRateLimitRemaining := limit - value
if XRateLimitRemaining < 0 {
XRateLimitRemaining = 0
}
w.Header().Add("X-Rate-Limit-Limit", strconv.Itoa(limit))
w.Header().Add("X-Rate-Limit-Remaining", strconv.Itoa(XRateLimitRemaining))
w.Header().Add("X-Rate-Limit-Reset", strconv.Itoa(int(window.Seconds()-time.Since(windowStart).Seconds())+1))
if value >= limit {
w.WriteHeader(429)
// Do something else...
} else {
next.ServeHTTP(w, r)
}
}
return http.HandlerFunc(h)
}
}
// identifyRequest gets an identifier from the request context.
func identifyRequest(r *http.Request) string {
// Identify your request here (get IP address, etc.)
}