diff --git a/server/admin/api_group.go b/server/admin/api_group.go
index 17fbdb7..dd440e1 100644
--- a/server/admin/api_group.go
+++ b/server/admin/api_group.go
@@ -62,7 +62,9 @@ func GroupDetail(w http.ResponseWriter, r *http.Request) {
RespError(w, RespInternalErr, err)
return
}
-
+ if len(data.Auth) == 0 {
+ data.Auth["type"] = "local"
+ }
RespSucess(w, data)
}
diff --git a/server/dbdata/group.go b/server/dbdata/group.go
index 872a2a0..4effbd2 100644
--- a/server/dbdata/group.go
+++ b/server/dbdata/group.go
@@ -1,6 +1,7 @@
package dbdata
import (
+ "encoding/json"
"errors"
"fmt"
"net"
@@ -32,19 +33,27 @@ type ValData struct {
Note string `json:"note"`
}
+type AuthRadius struct {
+ Addr string `json:"addr"`
+ Secret string `json:"secret"`
+}
+
// type Group struct {
-// Id int `json:"id" xorm:"pk autoincr not null"`
-// Name string `json:"name" xorm:"not null unique"`
-// Note string `json:"note"`
-// AllowLan bool `json:"allow_lan"`
-// ClientDns []ValData `json:"client_dns"`
-// RouteInclude []ValData `json:"route_include"`
-// RouteExclude []ValData `json:"route_exclude"`
-// LinkAcl []GroupLinkAcl `json:"link_acl"`
-// Bandwidth int `json:"bandwidth"` // 带宽限制
-// Status int8 `json:"status"` // 1正常
-// CreatedAt time.Time `json:"created_at"`
-// UpdatedAt time.Time `json:"updated_at"`
+// Id int `json:"id" xorm:"pk autoincr not null"`
+// Name string `json:"name" xorm:"varchar(60) not null unique"`
+// Note string `json:"note" xorm:"varchar(255)"`
+// AllowLan bool `json:"allow_lan" xorm:"Bool"`
+// ClientDns []ValData `json:"client_dns" xorm:"Text"`
+// RouteInclude []ValData `json:"route_include" xorm:"Text"`
+// RouteExclude []ValData `json:"route_exclude" xorm:"Text"`
+// DsExcludeDomains string `json:"ds_exclude_domains" xorm:"Text"`
+// DsIncludeDomains string `json:"ds_include_domains" xorm:"Text"`
+// LinkAcl []GroupLinkAcl `json:"link_acl" xorm:"Text"`
+// Bandwidth int `json:"bandwidth" xorm:"Int"` // 带宽限制
+// Auth map[string]interface{} `json:"auth" xorm:"not null default '{}' varchar(255)"` // 认证方式
+// Status int8 `json:"status" xorm:"Int"` // 1正常
+// CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
+// UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
// }
func GetGroupNames() []string {
@@ -145,6 +154,24 @@ func SetGroup(g *Group) error {
if err != nil {
return errors.New("排除域名有误:" + err.Error())
}
+ // 处理认证方式的逻辑
+ defAuth := map[string]interface{}{
+ "type": "local",
+ }
+ if len(g.Auth) == 0 {
+ g.Auth = defAuth
+ }
+ switch g.Auth["type"] {
+ case "local":
+ g.Auth = defAuth
+ case "radius":
+ err = checkRadiusData(g.Auth)
+ if err != nil {
+ return err
+ }
+ default:
+ return errors.New("#" + fmt.Sprintf("%s", g.Auth["type"]) + "#未知的认证类型")
+ }
g.UpdatedAt = time.Now()
if g.Id > 0 {
@@ -167,6 +194,24 @@ func parseIpNet(s string) (string, *net.IPNet, error) {
return ipMask, ipNet, nil
}
+
+func checkRadiusData(auth map[string]interface{}) error {
+ radisConf := AuthRadius{}
+ bodyBytes, err := json.Marshal(auth["radius"])
+ if err != nil {
+ return errors.New("Radius的密钥/服务器地址填写有误")
+ }
+ json.Unmarshal(bodyBytes, &radisConf)
+ if !ValidateIpPort(radisConf.Addr) {
+ return errors.New("Radius的服务器地址填写有误")
+ }
+ // freeradius官网最大8000字符, 这里限制200
+ if len(radisConf.Secret) < 8 || len(radisConf.Secret) > 200 {
+ return errors.New("Radius的密钥长度需在8~200个字符之间")
+ }
+ return nil
+}
+
func CheckDomainNames(domains string) error {
if domains == "" {
return nil
@@ -187,3 +232,8 @@ func ValidateDomainName(domain string) bool {
RegExp := regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+[A-Za-z]{2,18}$`)
return RegExp.MatchString(domain)
}
+
+func ValidateIpPort(addr string) bool {
+ RegExp := regexp.MustCompile(`^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\:([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])$$`)
+ return RegExp.MatchString(addr)
+}
diff --git a/server/dbdata/tables.go b/server/dbdata/tables.go
index a0ffea2..9dd2b5d 100644
--- a/server/dbdata/tables.go
+++ b/server/dbdata/tables.go
@@ -6,20 +6,21 @@ import (
)
type Group struct {
- Id int `json:"id" xorm:"pk autoincr not null"`
- Name string `json:"name" xorm:"varchar(60) not null unique"`
- Note string `json:"note" xorm:"varchar(255)"`
- AllowLan bool `json:"allow_lan" xorm:"Bool"`
- ClientDns []ValData `json:"client_dns" xorm:"Text"`
- RouteInclude []ValData `json:"route_include" xorm:"Text"`
- RouteExclude []ValData `json:"route_exclude" xorm:"Text"`
- DsExcludeDomains string `json:"ds_exclude_domains" xorm:"Text"`
- DsIncludeDomains string `json:"ds_include_domains" xorm:"Text"`
- LinkAcl []GroupLinkAcl `json:"link_acl" xorm:"Text"`
- Bandwidth int `json:"bandwidth" xorm:"Int"` // 带宽限制
- Status int8 `json:"status" xorm:"Int"` // 1正常
- CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
- UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
+ Id int `json:"id" xorm:"pk autoincr not null"`
+ Name string `json:"name" xorm:"varchar(60) not null unique"`
+ Note string `json:"note" xorm:"varchar(255)"`
+ AllowLan bool `json:"allow_lan" xorm:"Bool"`
+ ClientDns []ValData `json:"client_dns" xorm:"Text"`
+ RouteInclude []ValData `json:"route_include" xorm:"Text"`
+ RouteExclude []ValData `json:"route_exclude" xorm:"Text"`
+ DsExcludeDomains string `json:"ds_exclude_domains" xorm:"Text"`
+ DsIncludeDomains string `json:"ds_include_domains" xorm:"Text"`
+ LinkAcl []GroupLinkAcl `json:"link_acl" xorm:"Text"`
+ Bandwidth int `json:"bandwidth" xorm:"Int"` // 带宽限制
+ Auth map[string]interface{} `json:"auth" xorm:"not null default '{}' varchar(255)"` // 认证方式
+ Status int8 `json:"status" xorm:"Int"` // 1正常
+ CreatedAt time.Time `json:"created_at" xorm:"DateTime created"`
+ UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
}
type User struct {
diff --git a/server/dbdata/user.go b/server/dbdata/user.go
index 5fbb2d7..3db7461 100644
--- a/server/dbdata/user.go
+++ b/server/dbdata/user.go
@@ -1,6 +1,8 @@
package dbdata
import (
+ "context"
+ "encoding/json"
"errors"
"fmt"
"sync"
@@ -8,6 +10,8 @@ import (
"github.com/bjdgyc/anylink/pkg/utils"
"github.com/xlzd/gotp"
+ "layeh.com/radius"
+ "layeh.com/radius/rfc2865"
)
// type User struct {
@@ -68,6 +72,29 @@ func SetUser(v *User) error {
// 验证用户登陆信息
func CheckUser(name, pwd, group string) error {
+ // 获取登入的group数据
+ groupData := &Group{}
+ err := One("Name", group, groupData)
+ if err != nil {
+ return fmt.Errorf("%s %s", name, "No用户组")
+ }
+ // 初始化Auth
+ if len(groupData.Auth) == 0 {
+ groupData.Auth["type"] = "local"
+ }
+ switch groupData.Auth["type"] {
+ case "", "local":
+ return checkLocalUser(name, pwd, group)
+ case "radius":
+ return checkRadiusUser(name, pwd, groupData.Auth)
+ default:
+ return fmt.Errorf("%s %s", name, "无效的认证类型")
+ }
+ return nil
+}
+
+// 验证本地用户登陆信息
+func checkLocalUser(name, pwd, group string) error {
// TODO 严重问题
// return nil
@@ -108,6 +135,35 @@ func CheckUser(name, pwd, group string) error {
return nil
}
+func checkRadiusUser(name string, pwd string, auth map[string]interface{}) error {
+ if _, ok := auth["radius"]; !ok {
+ fmt.Errorf("%s %s", name, "Radius的radius值不存在")
+ }
+ radiusConf := AuthRadius{}
+ bodyBytes, err := json.Marshal(auth["radius"])
+ if err != nil {
+ fmt.Errorf("%s %s", name, "Radius Marshal出现错误")
+ }
+ err = json.Unmarshal(bodyBytes, &radiusConf)
+ if err != nil {
+ fmt.Errorf("%s %s", name, "Radius Unmarshal出现错误")
+ }
+ // radius认证时,设置超时3秒
+ packet := radius.New(radius.CodeAccessRequest, []byte(radiusConf.Secret))
+ rfc2865.UserName_SetString(packet, name)
+ rfc2865.UserPassword_SetString(packet, pwd)
+ ctx, done := context.WithTimeout(context.Background(), 3*time.Second)
+ defer done()
+ response, err := radius.Exchange(ctx, packet, radiusConf.Addr)
+ if err != nil {
+ return fmt.Errorf("%s %s", name, "Radius服务器连接异常, 请检测服务器和端口")
+ }
+ if response.Code != radius.CodeAccessAccept {
+ return fmt.Errorf("%s %s", name, "Radius:用户名或密码错误")
+ }
+ return nil
+}
+
var (
userOtpMux = sync.Mutex{}
userOtp = map[string]time.Time{}
diff --git a/server/go.mod b/server/go.mod
index f46c1b3..4f3d552 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -25,6 +25,7 @@ require (
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
+ layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
xorm.io/xorm v1.2.2
)
diff --git a/web/src/pages/group/List.vue b/web/src/pages/group/List.vue
index fa19975..446584d 100644
--- a/web/src/pages/group/List.vue
+++ b/web/src/pages/group/List.vue
@@ -207,6 +207,25 @@
+
+
+
+ 本地
+ Radius
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -300,7 +319,7 @@
保存
取消
-
+
@@ -318,7 +337,8 @@ export default {
this.$emit('update:route_name', ['用户组信息', '用户组列表'])
},
mounted() {
- this.getData(1)
+ this.getData(1);
+ this.setAuthData();
},
data() {
return {
@@ -335,21 +355,17 @@ export default {
route_include: [{val: 'all', note: '默认全局代理'}],
route_exclude: [],
link_acl: [],
+ auth : {"type":'local'}
},
rules: {
name: [
- {required: true, message: '请输入用户名', trigger: 'blur'},
+ {required: true, message: '请输入组名', trigger: 'blur'},
{max: 30, message: '长度小于 30 个字符', trigger: 'blur'}
],
bandwidth: [
- {required: true, message: '请输入用户姓名', trigger: 'blur'},
- {type: 'number', message: '年龄必须为数字值'}
+ {required: true, message: '请输入带宽限制', trigger: 'blur'},
+ {type: 'number', message: '带宽限制必须为数字值'}
],
- email: [
- {required: true, message: '请输入用户邮箱', trigger: 'blur'},
- {type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
- ],
-
status: [
{required: true}
],
@@ -357,6 +373,14 @@ export default {
}
},
methods: {
+ setAuthData(row) {
+ var defAuthData = {"type":'local',
+ "radius":{"addr":"", "secret":""},
+ }
+ if (this.ruleForm.auth.type == "local" || !row) {
+ this.ruleForm.auth = defAuthData;
+ }
+ },
handleDel(row) {
axios.post('/group/del?id=' + row.id).then(resp => {
const rdata = resp.data;
@@ -378,15 +402,16 @@ export default {
this.activeTab = "general"
this.user_edit_dialog = true
if (!row) {
+ this.setAuthData(row)
return;
}
-
axios.get('/group/detail', {
params: {
id: row.id,
}
}).then(resp => {
- this.ruleForm = resp.data.data
+ this.ruleForm = resp.data.data;
+ this.setAuthData(resp.data.data);
}).catch(error => {
this.$message.error('哦,请求出错');
console.log(error);
@@ -431,7 +456,6 @@ export default {
console.log('error submit!!');
return false;
}
-
axios.post('/group/set', this.ruleForm).then(resp => {
const rdata = resp.data;
if (rdata.code === 0) {