feat: 登陆失败超过5次, 延时60s响应 (#138)

This commit is contained in:
jeessy2
2021-11-14 19:35:55 +08:00
committed by GitHub
parent 05cfbe7388
commit 02d6f9e0a0
3 changed files with 82 additions and 74 deletions

View File

@ -1,72 +1,7 @@
package config
import (
"bytes"
"ddns-go/util"
"encoding/base64"
"log"
"net/http"
"strings"
)
// User 登录用户
type User struct {
Username string
Password string
}
// ViewFunc func
type ViewFunc func(http.ResponseWriter, *http.Request)
// BasicAuth basic auth
func BasicAuth(f ViewFunc) ViewFunc {
return func(w http.ResponseWriter, r *http.Request) {
conf, _ := GetConfigCache()
// 禁止公网访问
if conf.NotAllowWanAccess {
if !util.IsPrivateNetwork(r.RemoteAddr) || !util.IsPrivateNetwork(r.Host) {
w.WriteHeader(http.StatusForbidden)
return
}
}
// 帐号或密码为空。跳过
if conf.Username == "" && conf.Password == "" {
// 执行被装饰的函数
f(w, r)
return
}
// 认证帐号密码
basicAuthPrefix := "Basic "
// 获取 request header
auth := r.Header.Get("Authorization")
// 如果是 http basic auth
if strings.HasPrefix(auth, basicAuthPrefix) {
// 解码认证信息
payload, err := base64.StdEncoding.DecodeString(
auth[len(basicAuthPrefix):],
)
if err == nil {
pair := bytes.SplitN(payload, []byte(":"), 2)
if len(pair) == 2 &&
bytes.Equal(pair[0], []byte(conf.Username)) &&
bytes.Equal(pair[1], []byte(conf.Password)) {
// 执行被装饰的函数
f(w, r)
return
}
}
log.Printf("%s 登陆失败!\n", r.RemoteAddr)
}
// 认证失败,提示 401 Unauthorized
// Restricted 可以改成其他的值
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
// 401 状态码
w.WriteHeader(http.StatusUnauthorized)
log.Printf("%s 请求登陆!\n", r.RemoteAddr)
}
}

18
main.go
View File

@ -80,13 +80,13 @@ func run(firstDelay time.Duration) {
http.Handle("/static/", http.FileServer(http.FS(staticEmbededFiles)))
http.Handle("/favicon.ico", http.FileServer(http.FS(faviconEmbededFile)))
http.HandleFunc("/", config.BasicAuth(web.Writing))
http.HandleFunc("/save", config.BasicAuth(web.Save))
http.HandleFunc("/logs", config.BasicAuth(web.Logs))
http.HandleFunc("/clearLog", config.BasicAuth(web.ClearLog))
http.HandleFunc("/ipv4NetInterface", config.BasicAuth(web.Ipv4NetInterfaces))
http.HandleFunc("/ipv6NetInterface", config.BasicAuth(web.Ipv6NetInterfaces))
http.HandleFunc("/webhookTest", config.BasicAuth(web.WebhookTest))
http.HandleFunc("/", web.BasicAuth(web.Writing))
http.HandleFunc("/save", web.BasicAuth(web.Save))
http.HandleFunc("/logs", web.BasicAuth(web.Logs))
http.HandleFunc("/clearLog", web.BasicAuth(web.ClearLog))
http.HandleFunc("/ipv4NetInterface", web.BasicAuth(web.Ipv4NetInterfaces))
http.HandleFunc("/ipv6NetInterface", web.BasicAuth(web.Ipv6NetInterfaces))
http.HandleFunc("/webhookTest", web.BasicAuth(web.WebhookTest))
log.Println("监听", *listen, "...")
@ -98,7 +98,7 @@ func run(firstDelay time.Duration) {
err := http.ListenAndServe(*listen, nil)
if err != nil {
log.Println("启动端口发生异常, 1分钟后自动关闭DOS窗口", err)
log.Println("启动端口发生异常, 请检查端口是否被占用", err)
time.Sleep(time.Minute)
os.Exit(1)
}
@ -128,7 +128,7 @@ func getService() service.Service {
DisplayName: "ddns-go",
Description: "简单好用的DDNS。自动更新域名解析到公网IP(支持阿里云、腾讯云dnspod、Cloudflare、华为云)",
Arguments: []string{"-l", *listen, "-f", strconv.Itoa(*every), "-c", *configFilePath},
Option: options,
Option: options,
}
prg := &program{}

73
web/basic_auth.go Normal file
View File

@ -0,0 +1,73 @@
package web
import (
"bytes"
"ddns-go/config"
"encoding/base64"
"log"
"net/http"
"strings"
"time"
)
// ViewFunc func
type ViewFunc func(http.ResponseWriter, *http.Request)
type loginDetect struct {
FailTimes int
}
var ld = &loginDetect{}
// BasicAuth basic auth
func BasicAuth(f ViewFunc) ViewFunc {
return func(w http.ResponseWriter, r *http.Request) {
conf, _ := config.GetConfigCache()
// 帐号或密码为空。跳过
if conf.Username == "" && conf.Password == "" {
// 执行被装饰的函数
f(w, r)
return
}
// 认证帐号密码
basicAuthPrefix := "Basic "
// 获取 request header
auth := r.Header.Get("Authorization")
// 如果是 http basic auth
if strings.HasPrefix(auth, basicAuthPrefix) {
// 解码认证信息
payload, err := base64.StdEncoding.DecodeString(
auth[len(basicAuthPrefix):],
)
if err == nil {
pair := bytes.SplitN(payload, []byte(":"), 2)
if len(pair) == 2 &&
bytes.Equal(pair[0], []byte(conf.Username)) &&
bytes.Equal(pair[1], []byte(conf.Password)) {
ld.FailTimes = 0
// 执行被装饰的函数
f(w, r)
return
}
}
ld.FailTimes = ld.FailTimes + 1
if ld.FailTimes > 5 {
log.Printf("%s 登陆失败超过5次! 并延时60s响应\n", r.RemoteAddr)
time.Sleep(60 * time.Second)
ld.FailTimes = 0
}
log.Printf("%s 登陆失败!\n", r.RemoteAddr)
}
// 认证失败,提示 401 Unauthorized
// Restricted 可以改成其他的值
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
// 401 状态码
w.WriteHeader(http.StatusUnauthorized)
log.Printf("%s 请求登陆!\n", r.RemoteAddr)
}
}