mirror of
https://github.com/bjdgyc/anylink.git
synced 2025-09-09 11:55:50 +08:00
修复证书验证没有传入用户组的Bug
This commit is contained in:
@@ -131,6 +131,11 @@ func GenerateClientCert(w http.ResponseWriter, r *http.Request) {
|
|||||||
RespError(w, RespInternalErr, "用户名不能为空")
|
RespError(w, RespInternalErr, "用户名不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
groupname := r.FormValue("group_name")
|
||||||
|
if groupname == "" {
|
||||||
|
RespError(w, RespInternalErr, "用户组不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 检查用户是否存在
|
// 检查用户是否存在
|
||||||
user := &dbdata.User{}
|
user := &dbdata.User{}
|
||||||
@@ -141,7 +146,7 @@ func GenerateClientCert(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 生成客户端证书
|
// 生成客户端证书
|
||||||
certData, err := dbdata.GenerateClientCert(username)
|
certData, err := dbdata.GenerateClientCert(username, groupname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespError(w, RespInternalErr, fmt.Sprintf("证书生成失败: %v", err))
|
RespError(w, RespInternalErr, fmt.Sprintf("证书生成失败: %v", err))
|
||||||
return
|
return
|
||||||
@@ -305,3 +310,46 @@ func GetClientCertList(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
RespSucess(w, data)
|
RespSucess(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserCertInfo 获取用户证书生成所需信息
|
||||||
|
func UserCertInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_ = r.ParseForm()
|
||||||
|
|
||||||
|
// 获取所有启用的用户
|
||||||
|
var users []dbdata.User
|
||||||
|
err := dbdata.Find(&users, 1000, 1)
|
||||||
|
if err != nil && !dbdata.CheckErrNotFound(err) {
|
||||||
|
RespError(w, RespInternalErr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有启用的组
|
||||||
|
var groups []dbdata.Group
|
||||||
|
err = dbdata.Find(&groups, 1000, 1)
|
||||||
|
if err != nil && !dbdata.CheckErrNotFound(err) {
|
||||||
|
RespError(w, RespInternalErr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤启用的用户和组
|
||||||
|
activeUsers := make([]dbdata.User, 0)
|
||||||
|
for _, user := range users {
|
||||||
|
if user.Status == 1 {
|
||||||
|
activeUsers = append(activeUsers, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeGroups := make([]dbdata.Group, 0)
|
||||||
|
for _, group := range groups {
|
||||||
|
if group.Status == 1 {
|
||||||
|
activeGroups = append(activeGroups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"users": activeUsers,
|
||||||
|
"groups": activeGroups,
|
||||||
|
}
|
||||||
|
|
||||||
|
RespSucess(w, data)
|
||||||
|
}
|
||||||
|
@@ -67,6 +67,7 @@ func StartAdmin() {
|
|||||||
// r.HandleFunc("/set/client_cert/enable", EnableClientCert)
|
// r.HandleFunc("/set/client_cert/enable", EnableClientCert)
|
||||||
// r.HandleFunc("/set/client_cert/disable", DisableClientCert)
|
// r.HandleFunc("/set/client_cert/disable", DisableClientCert)
|
||||||
r.HandleFunc("/set/client_cert/delete", DeleteClientCert)
|
r.HandleFunc("/set/client_cert/delete", DeleteClientCert)
|
||||||
|
r.HandleFunc("/set/client_cert/user_cert_info", UserCertInfo)
|
||||||
|
|
||||||
r.HandleFunc("/user/list", UserList)
|
r.HandleFunc("/user/list", UserList)
|
||||||
r.HandleFunc("/user/detail", UserDetail)
|
r.HandleFunc("/user/detail", UserDetail)
|
||||||
|
@@ -21,9 +21,9 @@ import (
|
|||||||
|
|
||||||
// 客户端证书数据结构
|
// 客户端证书数据结构
|
||||||
type ClientCertData struct {
|
type ClientCertData struct {
|
||||||
Id int `json:"id" xorm:"pk autoincr not null"`
|
Id int `json:"id" xorm:"pk autoincr not null"`
|
||||||
Username string `json:"username" xorm:"varchar(60) not null"`
|
Username string `json:"username" xorm:"varchar(60) not null"`
|
||||||
// GroupName string `json:"group_name" xorm:"varchar(60)"`
|
GroupName string `json:"groupname" xorm:"varchar(60)"`
|
||||||
Certificate string `json:"certificate" xorm:"text not null"`
|
Certificate string `json:"certificate" xorm:"text not null"`
|
||||||
PrivateKey string `json:"private_key" xorm:"text not null"`
|
PrivateKey string `json:"private_key" xorm:"text not null"`
|
||||||
SerialNumber string `json:"serial_number" xorm:"varchar(100) not null"`
|
SerialNumber string `json:"serial_number" xorm:"varchar(100) not null"`
|
||||||
@@ -177,7 +177,7 @@ func GenerateClientCA() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 生成客户端证书并保存到数据库
|
// 生成客户端证书并保存到数据库
|
||||||
func GenerateClientCert(username string) (*ClientCertData, error) {
|
func GenerateClientCert(username, groupname string) (*ClientCertData, error) {
|
||||||
// 检查是否已存在证书记录
|
// 检查是否已存在证书记录
|
||||||
_, err := GetClientCert(username)
|
_, err := GetClientCert(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -204,11 +204,12 @@ func GenerateClientCert(username string) (*ClientCertData, error) {
|
|||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
SerialNumber: big.NewInt(time.Now().UnixNano()),
|
SerialNumber: big.NewInt(time.Now().UnixNano()),
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: username,
|
CommonName: username,
|
||||||
Organization: []string{"AnyLink VPN"},
|
OrganizationalUnit: []string{groupname},
|
||||||
Country: []string{"CN"},
|
Organization: []string{"AnyLink VPN"},
|
||||||
Province: []string{"Beijing"},
|
Country: []string{"CN"},
|
||||||
Locality: []string{"Beijing"},
|
Province: []string{"Beijing"},
|
||||||
|
Locality: []string{"Beijing"},
|
||||||
},
|
},
|
||||||
NotBefore: time.Now(),
|
NotBefore: time.Now(),
|
||||||
NotAfter: time.Now().Add(time.Hour * 24 * 365), // 1年有效期
|
NotAfter: time.Now().Add(time.Hour * 24 * 365), // 1年有效期
|
||||||
@@ -232,6 +233,7 @@ func GenerateClientCert(username string) (*ClientCertData, error) {
|
|||||||
// 保存到数据库
|
// 保存到数据库
|
||||||
clientCertData := &ClientCertData{
|
clientCertData := &ClientCertData{
|
||||||
Username: username,
|
Username: username,
|
||||||
|
GroupName: groupname,
|
||||||
Certificate: string(certPEM),
|
Certificate: string(certPEM),
|
||||||
PrivateKey: string(keyPEM),
|
PrivateKey: string(keyPEM),
|
||||||
SerialNumber: template.SerialNumber.String(),
|
SerialNumber: template.SerialNumber.String(),
|
||||||
@@ -319,6 +321,11 @@ func ValidateClientCert(cert *x509.Certificate, userAgent string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if clientCertData.GroupName != cert.Subject.OrganizationalUnit[0] {
|
||||||
|
base.Error("证书验证失败:证书组名与用户组名不匹配")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// 检查证书状态
|
// 检查证书状态
|
||||||
if clientCertData.GetStatus() != CertStatusActive {
|
if clientCertData.GetStatus() != CertStatusActive {
|
||||||
base.Error("证书验证失败:证书状态为", clientCertData.GetStatusText())
|
base.Error("证书验证失败:证书状态为", clientCertData.GetStatusText())
|
||||||
|
@@ -81,13 +81,22 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
||||||
clientCert := r.TLS.PeerCertificates[0]
|
clientCert := r.TLS.PeerCertificates[0]
|
||||||
username := clientCert.Subject.CommonName
|
username := clientCert.Subject.CommonName
|
||||||
|
groupname := clientCert.Subject.OrganizationalUnit[0]
|
||||||
|
if username == "" || groupname == "" {
|
||||||
|
base.Warn("客户端证书缺少用户名或组名")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 验证证书有效性和用户状态
|
// 验证证书有效性和用户状态
|
||||||
if dbdata.ValidateClientCert(clientCert, userAgent) {
|
if dbdata.ValidateClientCert(clientCert, userAgent) {
|
||||||
// 证书认证成功,创建会话
|
// 证书认证成功,创建会话
|
||||||
base.Info("用户通过证书认证:", username)
|
base.Info("用户通过证书认证:", username)
|
||||||
|
|
||||||
|
sessionData.ClientRequest.GroupSelect = groupname
|
||||||
|
sessionData.ClientRequest.Auth.Username = username
|
||||||
ua.Username = username
|
ua.Username = username
|
||||||
|
ua.GroupName = groupname
|
||||||
ua.Info = "用户通过证书认证登录"
|
ua.Info = "用户通过证书认证登录"
|
||||||
ua.Status = dbdata.UserConnected
|
ua.Status = dbdata.UserConnected
|
||||||
dbdata.UserActLogIns.Add(*ua, userAgent)
|
dbdata.UserActLogIns.Add(*ua, userAgent)
|
||||||
|
@@ -130,16 +130,24 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="客户端证书" name="clientCert">
|
<el-tab-pane label="客户端证书" name="clientCert">
|
||||||
<el-form ref="clientCert" :model="clientCert" label-width="80px" size="small" class="tab-one">
|
<el-form ref="clientCert" :model="clientCert" label-width="80px" size="small" class="tab-one">
|
||||||
|
<!-- 生成证书对话框 -->
|
||||||
<el-dialog title="生成客户端证书" :visible.sync="generateCertDialog" width="450px">
|
<el-dialog title="生成客户端证书" :visible.sync="generateCertDialog" width="450px">
|
||||||
<el-form :model="generateForm" label-width="80px">
|
<el-form :model="generateForm" label-width="80px">
|
||||||
<el-form-item label="用户名">
|
<el-form-item label="用户名">
|
||||||
<el-select v-model="generateForm.username" placeholder="请输入或选择用户名" filterable allow-create
|
<el-select v-model="generateForm.username" placeholder="请输入或选择用户名" filterable allow-create
|
||||||
default-first-option style="width: 100%;">
|
default-first-option style="width: 100%;" @change="onUserChange">
|
||||||
<el-option v-for="user in userList" :key="user.username" :label="user.username"
|
<el-option v-for="user in userList" :key="user.username" :label="user.username"
|
||||||
:value="user.username">
|
:value="user.username">
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<!-- 添加用户组选择 -->
|
||||||
|
<el-form-item label="用户组" v-if="userGroups.length > 0">
|
||||||
|
<el-select v-model="generateForm.groupName" placeholder="请选择用户组" style="width: 100%;">
|
||||||
|
<el-option v-for="group in userGroups" :key="group" :label="group" :value="group">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="generateCertDialog = false">取消</el-button>
|
<el-button @click="generateCertDialog = false">取消</el-button>
|
||||||
<el-button type="primary" @click="confirmGenerateCert">确定生成</el-button>
|
<el-button type="primary" @click="confirmGenerateCert">确定生成</el-button>
|
||||||
@@ -157,9 +165,10 @@
|
|||||||
|
|
||||||
<el-table :data="clientCertList" style="width: 100%" border>
|
<el-table :data="clientCertList" style="width: 100%" border>
|
||||||
<el-table-column prop="username" label="用户名"></el-table-column>
|
<el-table-column prop="username" label="用户名"></el-table-column>
|
||||||
|
<el-table-column prop="groupname" label="用户组"></el-table-column>
|
||||||
<el-table-column prop="serial_number" label="序列号"></el-table-column>
|
<el-table-column prop="serial_number" label="序列号"></el-table-column>
|
||||||
<el-table-column prop="not_after" label="过期时间" :formatter="dateFormat"></el-table-column>
|
|
||||||
<el-table-column prop="created_at" label="创建时间" :formatter="dateFormat"></el-table-column>
|
<el-table-column prop="created_at" label="创建时间" :formatter="dateFormat"></el-table-column>
|
||||||
|
<el-table-column prop="not_after" label="过期时间" :formatter="dateFormat"></el-table-column>
|
||||||
<el-table-column prop="status" label="状态">
|
<el-table-column prop="status" label="状态">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag :type="getStatusType(scope.row.status)">
|
<el-tag :type="getStatusType(scope.row.status)">
|
||||||
@@ -357,9 +366,12 @@ export default {
|
|||||||
},
|
},
|
||||||
generateCertDialog: false,
|
generateCertDialog: false,
|
||||||
generateForm: {
|
generateForm: {
|
||||||
username: ''
|
username: '',
|
||||||
|
groupName: ''
|
||||||
},
|
},
|
||||||
userList: [],
|
userList: [],
|
||||||
|
userGroups: [],
|
||||||
|
allGroups: [],
|
||||||
clientCertList: [],
|
clientCertList: [],
|
||||||
pagination: {
|
pagination: {
|
||||||
current: 1,
|
current: 1,
|
||||||
@@ -471,22 +483,37 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onUserChange(username) {
|
||||||
|
this.generateForm.groupName = '';
|
||||||
|
this.userGroups = [];
|
||||||
|
|
||||||
|
if (username) {
|
||||||
|
const selectedUser = this.userList.find(user => user.username === username);
|
||||||
|
if (selectedUser && selectedUser.groups) {
|
||||||
|
this.userGroups = selectedUser.groups;
|
||||||
|
if (this.userGroups.length === 1) {
|
||||||
|
this.generateForm.groupName = this.userGroups[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 生成客户端证书
|
// 生成客户端证书
|
||||||
generateClientCert() {
|
generateClientCert() {
|
||||||
this.generateCertDialog = true;
|
this.generateCertDialog = true;
|
||||||
this.generateForm.username = '';
|
this.generateForm = { username: '', groupName: '' };
|
||||||
axios.get('/user/list', {
|
this.userGroups = [];
|
||||||
params: {
|
|
||||||
page_size: 100,
|
axios.get('/set/client_cert/user_cert_info').then(resp => {
|
||||||
page_index: 1
|
|
||||||
}
|
|
||||||
}).then(resp => {
|
|
||||||
if (resp.data.code === 0) {
|
if (resp.data.code === 0) {
|
||||||
this.userList = resp.data.data.datas || [];
|
this.userList = resp.data.data.users || [];
|
||||||
|
this.allGroups = resp.data.data.groups || [];
|
||||||
|
} else {
|
||||||
|
this.$message.error(resp.data.msg);
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('加载用户列表失败:', error);
|
console.error('加载用户和组信息失败:', error);
|
||||||
this.$message.error('加载用户列表失败');
|
this.$message.error('加载用户和组信息失败');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
confirmGenerateCert() {
|
confirmGenerateCert() {
|
||||||
@@ -494,9 +521,16 @@ export default {
|
|||||||
this.$message.error('请选择或输入用户名');
|
this.$message.error('请选择或输入用户名');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.userGroups.length > 0 && !this.generateForm.groupName) {
|
||||||
|
this.$message.error('请选择用户组');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('username', this.generateForm.username);
|
formData.append('username', this.generateForm.username);
|
||||||
|
if (this.generateForm.groupName) {
|
||||||
|
formData.append('group_name', this.generateForm.groupName);
|
||||||
|
}
|
||||||
|
|
||||||
axios.post('/set/client_cert/generate', formData).then(resp => {
|
axios.post('/set/client_cert/generate', formData).then(resp => {
|
||||||
if (resp.data.code === 0) {
|
if (resp.data.code === 0) {
|
||||||
|
Reference in New Issue
Block a user