feat(auth): enhance IP restriction handling with CIDR support

This commit is contained in:
CaIon
2025-12-15 17:24:09 +08:00
parent 1bd0d8de02
commit 947a763a1a
12 changed files with 63 additions and 47 deletions
+29
View File
@@ -2,6 +2,15 @@ package common
import "net" import "net"
func IsIP(s string) bool {
ip := net.ParseIP(s)
return ip != nil
}
func ParseIP(s string) net.IP {
return net.ParseIP(s)
}
func IsPrivateIP(ip net.IP) bool { func IsPrivateIP(ip net.IP) bool {
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
return true return true
@@ -20,3 +29,23 @@ func IsPrivateIP(ip net.IP) bool {
} }
return false return false
} }
func IsIpInCIDRList(ip net.IP, cidrList []string) bool {
for _, cidr := range cidrList {
_, network, err := net.ParseCIDR(cidr)
if err != nil {
// 尝试作为单个IP处理
if whitelistIP := net.ParseIP(cidr); whitelistIP != nil {
if ip.Equal(whitelistIP) {
return true
}
}
continue
}
if network.Contains(ip) {
return true
}
}
return false
}
+1 -17
View File
@@ -186,23 +186,7 @@ func isIPListed(ip net.IP, list []string) bool {
return false return false
} }
for _, whitelistCIDR := range list { return IsIpInCIDRList(ip, list)
_, network, err := net.ParseCIDR(whitelistCIDR)
if err != nil {
// 尝试作为单个IP处理
if whitelistIP := net.ParseIP(whitelistCIDR); whitelistIP != nil {
if ip.Equal(whitelistIP) {
return true
}
}
continue
}
if network.Contains(ip) {
return true
}
}
return false
} }
// IsIPAccessAllowed 检查IP是否允许访问 // IsIPAccessAllowed 检查IP是否允许访问
-5
View File
@@ -217,11 +217,6 @@ func IntMax(a int, b int) int {
} }
} }
func IsIP(s string) bool {
ip := net.ParseIP(s)
return ip != nil
}
func GetUUID() string { func GetUUID() string {
code := uuid.New().String() code := uuid.New().String()
code = strings.Replace(code, "-", "", -1) code = strings.Replace(code, "-", "", -1)
+12 -3
View File
@@ -2,12 +2,14 @@ package middleware
import ( import (
"fmt" "fmt"
"net"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/logger"
"github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/service" "github.com/QuantumNous/new-api/service"
"github.com/QuantumNous/new-api/setting/ratio_setting" "github.com/QuantumNous/new-api/setting/ratio_setting"
@@ -240,13 +242,20 @@ func TokenAuth() func(c *gin.Context) {
return return
} }
allowIpsMap := token.GetIpLimitsMap() allowIpsMap := token.GetIpLimits()
if len(allowIpsMap) != 0 { if len(allowIpsMap) > 0 {
clientIp := c.ClientIP() clientIp := c.ClientIP()
if _, ok := allowIpsMap[clientIp]; !ok { logger.LogDebug(c, "Token has IP restrictions, checking client IP %s", clientIp)
ip := net.ParseIP(clientIp)
if ip == nil {
abortWithOpenAiMessage(c, http.StatusForbidden, "无法解析客户端 IP 地址")
return
}
if common.IsIpInCIDRList(ip, allowIpsMap) == false {
abortWithOpenAiMessage(c, http.StatusForbidden, "您的 IP 不在令牌允许访问的列表中") abortWithOpenAiMessage(c, http.StatusForbidden, "您的 IP 不在令牌允许访问的列表中")
return return
} }
logger.LogDebug(c, "Client IP %s passed the token IP restrictions check", clientIp)
} }
userCache, err := model.GetUserCache(token.UserId) userCache, err := model.GetUserCache(token.UserId)
+7 -8
View File
@@ -6,7 +6,6 @@ import (
"strings" "strings"
"github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/common"
"github.com/bytedance/gopkg/util/gopool" "github.com/bytedance/gopkg/util/gopool"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -35,26 +34,26 @@ func (token *Token) Clean() {
token.Key = "" token.Key = ""
} }
func (token *Token) GetIpLimitsMap() map[string]any { func (token *Token) GetIpLimits() []string {
// delete empty spaces // delete empty spaces
//split with \n //split with \n
ipLimitsMap := make(map[string]any) ipLimits := make([]string, 0)
if token.AllowIps == nil { if token.AllowIps == nil {
return ipLimitsMap return ipLimits
} }
cleanIps := strings.ReplaceAll(*token.AllowIps, " ", "") cleanIps := strings.ReplaceAll(*token.AllowIps, " ", "")
if cleanIps == "" { if cleanIps == "" {
return ipLimitsMap return ipLimits
} }
ips := strings.Split(cleanIps, "\n") ips := strings.Split(cleanIps, "\n")
for _, ip := range ips { for _, ip := range ips {
ip = strings.TrimSpace(ip) ip = strings.TrimSpace(ip)
ip = strings.ReplaceAll(ip, ",", "") ip = strings.ReplaceAll(ip, ",", "")
if common.IsIP(ip) { if ip != "" {
ipLimitsMap[ip] = true ipLimits = append(ipLimits, ip)
} }
} }
return ipLimitsMap return ipLimits
} }
func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) { func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) {
@@ -557,11 +557,11 @@ const EditTokenModal = (props) => {
<Col span={24}> <Col span={24}>
<Form.TextArea <Form.TextArea
field='allow_ips' field='allow_ips'
label={t('IP白名单')} label={t('IP白名单(支持CIDR表达式)')}
placeholder={t('允许的IP,一行一个,不填写则不限制')} placeholder={t('允许的IP,一行一个,不填写则不限制')}
autosize autosize
rows={1} rows={1}
extraText={t('请勿过度信任此功能,IP可能被伪造')} extraText={t('请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用')}
showClear showClear
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
+2 -2
View File
@@ -97,7 +97,7 @@
"Homepage URL 填": "Fill in the Homepage URL", "Homepage URL 填": "Fill in the Homepage URL",
"ID": "ID", "ID": "ID",
"IP": "IP", "IP": "IP",
"IP白名单": "IP whitelist", "IP白名单(支持CIDR表达式)": "IP whitelist (supports CIDR expressions)",
"IP限制": "IP restrictions", "IP限制": "IP restrictions",
"IP黑名单": "IP blacklist", "IP黑名单": "IP blacklist",
"JSON": "JSON", "JSON": "JSON",
@@ -1752,7 +1752,7 @@
"请先阅读并同意用户协议和隐私政策": "Please read and agree to the user agreement and privacy policy first", "请先阅读并同意用户协议和隐私政策": "Please read and agree to the user agreement and privacy policy first",
"请再次输入新密码": "Please enter the new password again", "请再次输入新密码": "Please enter the new password again",
"请前往个人设置 → 安全设置进行配置。": "Please go to Personal Settings → Security Settings to configure.", "请前往个人设置 → 安全设置进行配置。": "Please go to Personal Settings → Security Settings to configure.",
"请勿过度信任此功能,IP可能被伪造": "Do not over-trust this feature, IP can be spoofed", "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "Do not over-trust this feature, IP can be spoofed, please use it in conjunction with gateways such as nginx and CDN",
"请在系统设置页面编辑分组倍率以添加新的分组:": "Please edit Group ratios in system settings to add new groups:", "请在系统设置页面编辑分组倍率以添加新的分组:": "Please edit Group ratios in system settings to add new groups:",
"请填写完整的产品信息": "Please fill in complete product information", "请填写完整的产品信息": "Please fill in complete product information",
"请填写完整的管理员账号信息": "Please fill in the complete administrator account information", "请填写完整的管理员账号信息": "Please fill in the complete administrator account information",
+2 -2
View File
@@ -99,7 +99,7 @@
"Homepage URL 填": "Remplir l'URL de la page d'accueil", "Homepage URL 填": "Remplir l'URL de la page d'accueil",
"ID": "ID", "ID": "ID",
"IP": "IP", "IP": "IP",
"IP白名单": "Liste blanche d'adresses IP", "IP白名单(支持CIDR表达式)": "Liste blanche d'adresses IP (prise en charge des expressions CIDR)",
"IP限制": "Restrictions d'IP", "IP限制": "Restrictions d'IP",
"IP黑名单": "Liste noire d'adresses IP", "IP黑名单": "Liste noire d'adresses IP",
"JSON": "JSON", "JSON": "JSON",
@@ -1762,7 +1762,7 @@
"请先阅读并同意用户协议和隐私政策": "Veuillez d'abord lire et accepter l'accord utilisateur et la politique de confidentialité", "请先阅读并同意用户协议和隐私政策": "Veuillez d'abord lire et accepter l'accord utilisateur et la politique de confidentialité",
"请再次输入新密码": "Veuillez saisir à nouveau le nouveau mot de passe", "请再次输入新密码": "Veuillez saisir à nouveau le nouveau mot de passe",
"请前往个人设置 → 安全设置进行配置。": "Veuillez aller dans Paramètres personnels → Paramètres de sécurité pour configurer.", "请前往个人设置 → 安全设置进行配置。": "Veuillez aller dans Paramètres personnels → Paramètres de sécurité pour configurer.",
"请勿过度信任此功能,IP可能被伪造": "Ne faites pas trop confiance à cette fonctionnalité, l'IP peut être usurpée", "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "Ne faites pas trop confiance à cette fonctionnalité, l'IP peut être usurpée, veuillez l'utiliser en conjonction avec des passerelles telles que nginx et cdn",
"请在系统设置页面编辑分组倍率以添加新的分组:": "Veuillez modifier les ratios de groupe dans les paramètres système pour ajouter de nouveaux groupes :", "请在系统设置页面编辑分组倍率以添加新的分组:": "Veuillez modifier les ratios de groupe dans les paramètres système pour ajouter de nouveaux groupes :",
"请填写完整的产品信息": "Veuillez renseigner l'ensemble des informations produit", "请填写完整的产品信息": "Veuillez renseigner l'ensemble des informations produit",
"请填写完整的管理员账号信息": "Veuillez remplir les informations complètes du compte administrateur", "请填写完整的管理员账号信息": "Veuillez remplir les informations complètes du compte administrateur",
+2 -2
View File
@@ -82,7 +82,7 @@
"Homepage URL 填": "ホームページURLを入力してください", "Homepage URL 填": "ホームページURLを入力してください",
"ID": "ID", "ID": "ID",
"IP": "IP", "IP": "IP",
"IP白名单": "IPホワイトリスト", "IP白名单(支持CIDR表达式)": "IPホワイトリストCIDR表記に対応)",
"IP限制": "IP制限", "IP限制": "IP制限",
"IP黑名单": "IPブラックリスト", "IP黑名单": "IPブラックリスト",
"JSON": "JSON", "JSON": "JSON",
@@ -1669,7 +1669,7 @@
"请先阅读并同意用户协议和隐私政策": "まずユーザー利用規約とプライバシーポリシーをご確認の上、同意してください", "请先阅读并同意用户协议和隐私政策": "まずユーザー利用規約とプライバシーポリシーをご確認の上、同意してください",
"请再次输入新密码": "新しいパスワードを再入力してください", "请再次输入新密码": "新しいパスワードを再入力してください",
"请前往个人设置 → 安全设置进行配置。": "アカウント設定 → セキュリティ設定 にて設定してください。", "请前往个人设置 → 安全设置进行配置。": "アカウント設定 → セキュリティ設定 にて設定してください。",
"请勿过度信任此功能,IP可能被伪造": "IPは偽装される可能性があるため、この機能を過信しないでください", "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "IPは偽装される可能性があるため、この機能を過信しないでください。nginxやCDNなどのゲートウェイと組み合わせて使用してください。",
"请在系统设置页面编辑分组倍率以添加新的分组:": "新規グループを追加するには、システム設定ページでグループ倍率を編集してください:", "请在系统设置页面编辑分组倍率以添加新的分组:": "新規グループを追加するには、システム設定ページでグループ倍率を編集してください:",
"请填写完整的管理员账号信息": "管理者アカウント情報をすべて入力してください", "请填写完整的管理员账号信息": "管理者アカウント情報をすべて入力してください",
"请填写密钥": "APIキーを入力してください", "请填写密钥": "APIキーを入力してください",
+2 -2
View File
@@ -101,7 +101,7 @@
"Homepage URL 填": "URL домашней страницы:", "Homepage URL 填": "URL домашней страницы:",
"ID": "ID", "ID": "ID",
"IP": "IP", "IP": "IP",
"IP白名单": "Белый список IP", "IP白名单(支持CIDR表达式)": "Белый список IP (поддерживает выражения CIDR)",
"IP限制": "Ограничения IP", "IP限制": "Ограничения IP",
"IP黑名单": "Черный список IP", "IP黑名单": "Черный список IP",
"JSON": "JSON", "JSON": "JSON",
@@ -1773,7 +1773,7 @@
"请先阅读并同意用户协议和隐私政策": "Пожалуйста, сначала прочтите и согласитесь с пользовательским соглашением и политикой конфиденциальности", "请先阅读并同意用户协议和隐私政策": "Пожалуйста, сначала прочтите и согласитесь с пользовательским соглашением и политикой конфиденциальности",
"请再次输入新密码": "Пожалуйста, введите новый пароль ещё раз", "请再次输入新密码": "Пожалуйста, введите новый пароль ещё раз",
"请前往个人设置 → 安全设置进行配置。": "Пожалуйста, перейдите в Личные настройки → Настройки безопасности для конфигурации.", "请前往个人设置 → 安全设置进行配置。": "Пожалуйста, перейдите в Личные настройки → Настройки безопасности для конфигурации.",
"请勿过度信任此功能,IP可能被伪造": "Не доверяйте этой функции чрезмерно, IP может быть подделан", "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "Не доверяйте этой функции чрезмерно, IP может быть подделан, используйте её вместе с nginx и CDN и другими шлюзами",
"请在系统设置页面编辑分组倍率以添加新的分组:": "Пожалуйста, отредактируйте коэффициенты групп на странице системных настроек для добавления новой группы:", "请在系统设置页面编辑分组倍率以添加新的分组:": "Пожалуйста, отредактируйте коэффициенты групп на странице системных настроек для добавления новой группы:",
"请填写完整的产品信息": "Пожалуйста, заполните всю информацию о продукте", "请填写完整的产品信息": "Пожалуйста, заполните всю информацию о продукте",
"请填写完整的管理员账号信息": "Пожалуйста, заполните полную информацию об учётной записи администратора", "请填写完整的管理员账号信息": "Пожалуйста, заполните полную информацию об учётной записи администратора",
+2 -2
View File
@@ -82,7 +82,7 @@
"Homepage URL 填": "Điền URL trang chủ", "Homepage URL 填": "Điền URL trang chủ",
"ID": "ID", "ID": "ID",
"IP": "IP", "IP": "IP",
"IP白名单": "Danh sách trắng IP", "IP白名单(支持CIDR表达式)": "Danh sách trắng IP (hỗ trợ biểu thức CIDR)",
"IP限制": "Hạn chế IP", "IP限制": "Hạn chế IP",
"IP黑名单": "Danh sách đen IP", "IP黑名单": "Danh sách đen IP",
"JSON": "JSON", "JSON": "JSON",
@@ -1987,7 +1987,7 @@
"请先阅读并同意用户协议和隐私政策": "Vui lòng đọc và đồng ý với thỏa thuận người dùng và chính sách bảo mật trước", "请先阅读并同意用户协议和隐私政策": "Vui lòng đọc và đồng ý với thỏa thuận người dùng và chính sách bảo mật trước",
"请再次输入新密码": "Vui lòng nhập lại mật khẩu mới", "请再次输入新密码": "Vui lòng nhập lại mật khẩu mới",
"请前往个人设置 → 安全设置进行配置。": "Vui lòng truy cập Cài đặt cá nhân → Cài đặt bảo mật để cấu hình.", "请前往个人设置 → 安全设置进行配置。": "Vui lòng truy cập Cài đặt cá nhân → Cài đặt bảo mật để cấu hình.",
"请勿过度信任此功能,IP可能被伪造": "Đừng quá tin tưởng tính năng này, IP có thể bị giả mạo", "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "Đừng quá tin tưởng tính năng này, IP có thể bị giả mạo, vui lòng sử dụng cùng với nginx và các cổng khác như cdn",
"请在系统设置页面编辑分组倍率以添加新的分组:": "Vui lòng chỉnh sửa tỷ lệ nhóm trên trang cài đặt hệ thống để thêm nhóm mới:", "请在系统设置页面编辑分组倍率以添加新的分组:": "Vui lòng chỉnh sửa tỷ lệ nhóm trên trang cài đặt hệ thống để thêm nhóm mới:",
"请填写完整的管理员账号信息": "Vui lòng điền đầy đủ thông tin tài khoản quản trị viên", "请填写完整的管理员账号信息": "Vui lòng điền đầy đủ thông tin tài khoản quản trị viên",
"请填写密钥": "Vui lòng điền khóa", "请填写密钥": "Vui lòng điền khóa",
+2 -2
View File
@@ -95,7 +95,7 @@
"Homepage URL 填": "Homepage URL 填", "Homepage URL 填": "Homepage URL 填",
"ID": "ID", "ID": "ID",
"IP": "IP", "IP": "IP",
"IP白名单": "IP白名单", "IP白名单(支持CIDR表达式)": "IP白名单(支持CIDR表达式)",
"IP限制": "IP限制", "IP限制": "IP限制",
"IP黑名单": "IP黑名单", "IP黑名单": "IP黑名单",
"JSON": "JSON", "JSON": "JSON",
@@ -1740,7 +1740,7 @@
"请先阅读并同意用户协议和隐私政策": "请先阅读并同意用户协议和隐私政策", "请先阅读并同意用户协议和隐私政策": "请先阅读并同意用户协议和隐私政策",
"请再次输入新密码": "请再次输入新密码", "请再次输入新密码": "请再次输入新密码",
"请前往个人设置 → 安全设置进行配置。": "请前往个人设置 → 安全设置进行配置。", "请前往个人设置 → 安全设置进行配置。": "请前往个人设置 → 安全设置进行配置。",
"请勿过度信任此功能,IP可能被伪造": "请勿过度信任此功能,IP可能被伪造", "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用",
"请在系统设置页面编辑分组倍率以添加新的分组:": "请在系统设置页面编辑分组倍率以添加新的分组:", "请在系统设置页面编辑分组倍率以添加新的分组:": "请在系统设置页面编辑分组倍率以添加新的分组:",
"请填写完整的产品信息": "请填写完整的产品信息", "请填写完整的产品信息": "请填写完整的产品信息",
"请填写完整的管理员账号信息": "请填写完整的管理员账号信息", "请填写完整的管理员账号信息": "请填写完整的管理员账号信息",