mirror of
https://github.com/jeessy2/ddns-go.git
synced 2025-10-20 15:33:46 +08:00
feat: 登陆失败超过5次, 延时60s响应 (#138)
This commit is contained in:
@ -1,72 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"ddns-go/util"
|
|
||||||
"encoding/base64"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// User 登录用户
|
// User 登录用户
|
||||||
type User struct {
|
type User struct {
|
||||||
Username string
|
Username string
|
||||||
Password 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
18
main.go
@ -80,13 +80,13 @@ func run(firstDelay time.Duration) {
|
|||||||
http.Handle("/static/", http.FileServer(http.FS(staticEmbededFiles)))
|
http.Handle("/static/", http.FileServer(http.FS(staticEmbededFiles)))
|
||||||
http.Handle("/favicon.ico", http.FileServer(http.FS(faviconEmbededFile)))
|
http.Handle("/favicon.ico", http.FileServer(http.FS(faviconEmbededFile)))
|
||||||
|
|
||||||
http.HandleFunc("/", config.BasicAuth(web.Writing))
|
http.HandleFunc("/", web.BasicAuth(web.Writing))
|
||||||
http.HandleFunc("/save", config.BasicAuth(web.Save))
|
http.HandleFunc("/save", web.BasicAuth(web.Save))
|
||||||
http.HandleFunc("/logs", config.BasicAuth(web.Logs))
|
http.HandleFunc("/logs", web.BasicAuth(web.Logs))
|
||||||
http.HandleFunc("/clearLog", config.BasicAuth(web.ClearLog))
|
http.HandleFunc("/clearLog", web.BasicAuth(web.ClearLog))
|
||||||
http.HandleFunc("/ipv4NetInterface", config.BasicAuth(web.Ipv4NetInterfaces))
|
http.HandleFunc("/ipv4NetInterface", web.BasicAuth(web.Ipv4NetInterfaces))
|
||||||
http.HandleFunc("/ipv6NetInterface", config.BasicAuth(web.Ipv6NetInterfaces))
|
http.HandleFunc("/ipv6NetInterface", web.BasicAuth(web.Ipv6NetInterfaces))
|
||||||
http.HandleFunc("/webhookTest", config.BasicAuth(web.WebhookTest))
|
http.HandleFunc("/webhookTest", web.BasicAuth(web.WebhookTest))
|
||||||
|
|
||||||
log.Println("监听", *listen, "...")
|
log.Println("监听", *listen, "...")
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ func run(firstDelay time.Duration) {
|
|||||||
err := http.ListenAndServe(*listen, nil)
|
err := http.ListenAndServe(*listen, nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("启动端口发生异常, 1分钟后自动关闭DOS窗口", err)
|
log.Println("启动端口发生异常, 请检查端口是否被占用", err)
|
||||||
time.Sleep(time.Minute)
|
time.Sleep(time.Minute)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ func getService() service.Service {
|
|||||||
DisplayName: "ddns-go",
|
DisplayName: "ddns-go",
|
||||||
Description: "简单好用的DDNS。自动更新域名解析到公网IP(支持阿里云、腾讯云dnspod、Cloudflare、华为云)",
|
Description: "简单好用的DDNS。自动更新域名解析到公网IP(支持阿里云、腾讯云dnspod、Cloudflare、华为云)",
|
||||||
Arguments: []string{"-l", *listen, "-f", strconv.Itoa(*every), "-c", *configFilePath},
|
Arguments: []string{"-l", *listen, "-f", strconv.Itoa(*every), "-c", *configFilePath},
|
||||||
Option: options,
|
Option: options,
|
||||||
}
|
}
|
||||||
|
|
||||||
prg := &program{}
|
prg := &program{}
|
||||||
|
73
web/basic_auth.go
Normal file
73
web/basic_auth.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user