wip: 自定义oauth2登录配置

This commit is contained in:
王一之
2023-07-20 20:34:05 +08:00
parent b7450f8869
commit 179b58e557
19 changed files with 409 additions and 9 deletions

1
.gitignore vendored
View File

@@ -17,4 +17,5 @@
*/node_modules/
**/vendor/
.idea
.vscode
out

View File

@@ -106,7 +106,7 @@ function buildDocker() {
imageVersion=$1
cd ${server_folder}
imageName="mayflygo/mayfly-go:${imageVersion}"
docker build -t "${imageName}" .
docker build --platform linux/amd64 -t "${imageName}" .
echo_green "docker镜像构建完成->[${imageName}]"
echo_yellow "-------------------构建docker镜像结束-------------------"
}
@@ -197,4 +197,4 @@ function runBuild() {
rm -rf ${server_folder}/static/static/index.html
}
runBuild
runBuild

View File

@@ -43,3 +43,8 @@ export const configApi = {
export const logApi = {
list: Api.newGet('/syslogs'),
};
export const authApi = {
info: Api.newGet('/sys/auth'),
saveOAuth2: Api.newPut('/sys/auth/oauth2'),
};

View File

@@ -0,0 +1,97 @@
<template>
<el-card>
<template #header>
<el-space>
<span>登录认证</span>
<el-text type="info">管理三方登录认证平台</el-text>
</el-space>
</template>
<el-card>
<template #header>
<el-space>
<span>OAuth2.0</span>
<el-text type="info">自定义oauth2.0 server登录</el-text>
</el-space>
</template>
<el-form ref="oauth2Form" :model="oauth2" label-width="160px" status-icon>
<el-form-item prop="clientID" label="Client ID" required>
<el-input v-model="oauth2.clientID" placeholder="客户端id"></el-input>
</el-form-item>
<el-form-item prop="clientSecret" label="Client secret" required>
<el-input v-model="oauth2.clientSecret" placeholder="客户端密钥"></el-input>
</el-form-item>
<el-form-item prop="authorizationURL" label="Authorization URL" required>
<el-input v-model="oauth2.authorizationURL"
placeholder="https://example.com/oauth/authorize"></el-input>
</el-form-item>
<el-form-item prop="accessTokenURL" label="Access token URL" required>
<el-input v-model="oauth2.accessTokenURL" placeholder="https://example.com/oauth/token"></el-input>
</el-form-item>
<el-form-item prop="resourceURL" label="Resource URL" required>
<el-input v-model="oauth2.resourceURL" placeholder="https://example.com/user"></el-input>
</el-form-item>
<el-form-item prop="redirectURL" label="Redirect URL" required>
<el-input v-model="oauth2.redirectURL" placeholder="http://mayfly地址/"></el-input>
</el-form-item>
<el-form-item prop="userIdentifier" label="User identifier" required>
<el-input v-model="oauth2.userIdentifier" placeholder=""></el-input>
</el-form-item>
<el-form-item prop="scopes" label="Scopes" required>
<el-input v-model="oauth2.scopes" placeholder=""></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit" :loading="btnLoading">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</el-card>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, toRefs } from 'vue';
import { authApi } from '../api';
import { FormInstance } from 'element-plus';
const oauth2Form = ref<FormInstance>();
const state = reactive({
oauth2: {
clientID: '',
clientSecret: '',
authorizationURL: '',
accessTokenURL: '',
resourceURL: '',
redirectURL: '',
userIdentifier: '',
scopes: '',
},
btnLoading: false,
});
const { oauth2, btnLoading } = toRefs(state);
onMounted(async () => {
const resp = await authApi.info.request();
console.log(resp);
if (resp.oauth2) {
state.oauth2 = resp.oauth2;
}
});
const onSubmit = () => {
oauth2Form.value?.validate(async (valid) => {
if (valid) {
state.btnLoading = true;
try {
await authApi.saveOAuth2.request(oauth2.value);
} catch (e) {
}
state.btnLoading = false;
}
})
}
</script>
<style lang="scss"></style>

2
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
static/static
config.yml

View File

@@ -24,6 +24,8 @@ require (
gorm.io/gorm v1.25.2
)
require github.com/go-gormigrate/gormigrate/v2 v2.1.0
require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.9.1 // indirect
@@ -37,6 +39,7 @@ require (
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
@@ -59,9 +62,11 @@ require (
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)

View File

@@ -0,0 +1,74 @@
package api
import (
"encoding/json"
form2 "mayfly-go/internal/sys/api/form"
"mayfly-go/internal/sys/api/vo"
"mayfly-go/internal/sys/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/global"
"mayfly-go/pkg/req"
"time"
)
const (
AuthOAuth2Name string = "OAuth2.0客户端配置"
AuthOAuth2Key string = "AuthOAuth2"
AuthOAuth2Param string = "[{\"name\":\"Client ID\",\"model\":\"clientID\",\"placeholder\":\"客户端id\"}," +
"{\"name\":\"Client Secret\",\"model\":\"clientSecret\",\"placeholder\":\"客户端密钥\"}," +
"{\"name\":\"Authorization URL\",\"model\":\"authorizationURL\",\"placeholder\":\"https://example.com/oauth/authorize\"}," +
"{\"name\":\"Access Token URL\",\"model\":\"accessTokenURL\",\"placeholder\":\"https://example.com/oauth/token\"}," +
"{\"name\":\"Resource URL\",\"model\":\"resourceURL\",\"placeholder\":\"https://example.com/oauth/token\"}," +
"{\"name\":\"Redirect URL\",\"model\":\"redirectURL\",\"placeholder\":\"http://mayfly地址/\"}," +
"{\"name\":\"User identifier\",\"model\":\"userIdentifier\",\"placeholder\":\"\"}," +
"{\"name\":\"Scopes\",\"model\":\"scopes\",\"placeholder\":\"read_user\"}]"
AuthOAuth2Remark string = "自定义oauth2.0 server登录"
)
type Auth struct {
ConfigApp application.Config
}
// GetInfo 获取认证平台信息
func (a *Auth) GetInfo(rc *req.Ctx) {
config := a.ConfigApp.GetConfig(AuthOAuth2Key)
oauth2 := &vo.OAuth2VO{}
if config.Value != "" {
if err := json.Unmarshal([]byte(config.Value), oauth2); err != nil {
global.Log.Warnf("解析自定义oauth2配置失败err%s", err.Error())
biz.ErrIsNil(err, "解析自定义oauth2配置失败")
}
}
rc.ResData = &vo.AuthVO{
OAuth2VO: oauth2,
}
}
func (a *Auth) SaveOAuth2(rc *req.Ctx) {
form := &form2.OAuth2Form{}
form = ginx.BindJsonAndValid(rc.GinCtx, form)
rc.ReqParam = form
// 先获取看看有没有
config := a.ConfigApp.GetConfig(AuthOAuth2Key)
now := time.Now()
if config.Id == 0 {
config.CreatorId = rc.LoginAccount.Id
config.Creator = rc.LoginAccount.Username
config.CreateTime = &now
}
config.ModifierId = rc.LoginAccount.Id
config.Modifier = rc.LoginAccount.Username
config.UpdateTime = &now
config.Name = AuthOAuth2Name
config.Key = AuthOAuth2Key
config.Params = AuthOAuth2Param
b, err := json.Marshal(form)
if err != nil {
biz.ErrIsNil(err, "json marshal error")
return
}
config.Value = string(b)
config.Remark = AuthOAuth2Remark
a.ConfigApp.Save(config)
}

View File

@@ -0,0 +1,12 @@
package form
type OAuth2Form struct {
ClientID string `json:"clientID" binding:"required"`
ClientSecret string `json:"clientSecret" binding:"required"`
AuthorizationURL string `json:"authorizationURL" binding:"required,url"`
AccessTokenURL string `json:"accessTokenURL" binding:"required,url"`
ResourceURL string `json:"resourceURL" binding:"required,url"`
RedirectURL string `json:"redirectURL" binding:"required,url"`
UserIdentifier string `json:"userIdentifier" binding:"required"`
Scopes string `json:"scopes"`
}

View File

@@ -0,0 +1,16 @@
package vo
type OAuth2VO struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
AuthorizationURL string `json:"authorizationURL"`
AccessTokenURL string `json:"accessTokenURL"`
ResourceURL string `json:"resourceURL"`
RedirectURL string `json:"redirectURL"`
UserIdentifier string `json:"userIdentifier"`
Scopes string `json:"scopes"`
}
type AuthVO struct {
*OAuth2VO `json:"oauth2"`
}

View File

@@ -17,7 +17,7 @@ type Config interface {
Save(config *entity.Config)
// 获取指定key的配置信息, 不会返回nil, 若不存在则值都默认值即空字符串
// GetConfig 获取指定key的配置信息, 不会返回nil, 若不存在则值都默认值即空字符串
GetConfig(key string) *entity.Config
}

View File

@@ -5,9 +5,9 @@ import "mayfly-go/pkg/model"
type Resource struct {
model.Model
Pid int `json:"pid"`
UiPath string // 唯一标识路径
Type int8 `json:"type"` // 1菜单路由2资源按钮等
Status int8 `json:"status"` // 1可用-1不可用
UiPath string `json:"ui_path"` // 唯一标识路径
Type int8 `json:"type"` // 1菜单路由2资源按钮等
Status int8 `json:"status"` // 1可用-1不可用
Code string `json:"code"`
Name string `json:"name"`
Weight int `json:"weight"`

View File

@@ -0,0 +1,24 @@
package router
import (
"github.com/gin-gonic/gin"
"mayfly-go/internal/sys/api"
"mayfly-go/internal/sys/application"
"mayfly-go/pkg/req"
)
func InitSysAuthRouter(router *gin.RouterGroup) {
r := &api.Auth{
ConfigApp: application.GetConfigApp(),
}
rg := router.Group("sys/auth")
baseP := req.NewPermission("system:auth:base")
reqs := [...]*req.Conf{
req.NewGet("", r.GetInfo).RequiredPermission(baseP),
req.NewPut("/oauth2", r.SaveOAuth2).RequiredPermission(baseP),
}
req.BatchSetGroup(rg, reqs[:])
}

View File

@@ -18,7 +18,7 @@ func InitSysConfigRouter(router *gin.RouterGroup) {
req.NewGet("", r.Configs).RequiredPermission(baseP),
// 获取指定配置key对应的值
req.NewGet("/value", r.GetConfigValueByKey).DontNeedToken(),
req.NewGet("/value", r.GetConfigValueByKey).RequiredPermission(baseP),
req.NewPost("", r.SaveConfig).Log(req.NewLogSave("保存系统配置信息")).RequiredPermissionCode("config:save"),
}

View File

@@ -10,4 +10,5 @@ func Init(router *gin.RouterGroup) {
InitSystemRouter(router)
InitSyslogRouter(router)
InitSysConfigRouter(router)
InitSysAuthRouter(router)
}

19
server/migrations/2022.go Normal file
View File

@@ -0,0 +1,19 @@
package migrations
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// T2022 TODO 在此之前的数据库表结构初始化, 目前先使用mayfly-go.sql文件初始化数据库结构
func T2022() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "2022",
Migrate: func(tx *gorm.DB) error {
return nil
},
Rollback: func(tx *gorm.DB) error {
return nil
},
}
}

View File

@@ -0,0 +1,93 @@
package migrations
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
"mayfly-go/internal/sys/api"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/model"
"time"
)
// T20230720 三方登录表
func T20230720() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20230319",
Migrate: func(tx *gorm.DB) error {
// 添加路由权限
now := time.Now()
res := &entity.Resource{
Model: model.Model{
Id: 130,
CreateTime: &now,
CreatorId: 1,
Creator: "admin",
UpdateTime: &now,
ModifierId: 1,
Modifier: "admin",
},
Pid: 4,
UiPath: "sys/auth",
Type: 1,
Status: 1,
Code: "system:auth",
Name: "登录认证",
Weight: 10000001,
Meta: "{\"component\":\"system/auth/AuthInfo\"," +
"\"icon\":\"User\",\"isKeepAlive\":true," +
"\"routeName\":\"AuthInfo\"}",
}
if err := tx.Save(res).Error; err != nil {
return err
}
res = &entity.Resource{
Model: model.Model{
Id: 131,
CreateTime: &now,
CreatorId: 1,
Creator: "admin",
UpdateTime: &now,
ModifierId: 1,
Modifier: "admin",
},
Pid: 130,
UiPath: "sys/auth/base",
Type: 2,
Status: 1,
Code: "system:auth:base",
Name: "基本权限",
Weight: 10000000,
Meta: "null",
}
if err := tx.Save(res).Error; err != nil {
return err
}
// 加大params字段长度
if err := tx.Exec("alter table " + (&entity.Config{}).TableName() +
" modify column params varchar(1000)").Error; err != nil {
return err
}
if err := tx.Save(&entity.Config{
Model: model.Model{
CreateTime: &now,
CreatorId: 1,
Creator: "admin",
UpdateTime: &now,
ModifierId: 1,
Modifier: "admin",
},
Name: api.AuthOAuth2Name,
Key: api.AuthOAuth2Key,
Params: api.AuthOAuth2Param,
Value: "{}",
Remark: api.AuthOAuth2Remark,
}).Error; err != nil {
return err
}
return nil
},
Rollback: func(tx *gorm.DB) error {
return nil
},
}
}

45
server/migrations/init.go Normal file
View File

@@ -0,0 +1,45 @@
package migrations
import (
"context"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
"mayfly-go/pkg/rediscli"
"time"
)
// RunMigrations 数据库迁移操作
func RunMigrations(db *gorm.DB) error {
// 添加分布式锁, 防止多个服务同时执行迁移
if rediscli.GetCli() != nil {
if ok, err := rediscli.GetCli().
SetNX(context.Background(), "migrations", "lock", time.Minute).Result(); err != nil {
return err
} else if !ok {
return nil
}
defer rediscli.Del("migrations")
}
return run(db,
T2022,
T20230720,
)
}
func run(db *gorm.DB, fs ...func() *gormigrate.Migration) error {
var ms []*gormigrate.Migration
for _, f := range fs {
ms = append(ms, f())
}
m := gormigrate.New(db, &gormigrate.Options{
TableName: "migrations",
IDColumnName: "id",
IDColumnSize: 200,
UseTransaction: true,
ValidateUnknownMigrations: true,
}, ms)
if err := m.Migrate(); err != nil {
return err
}
return nil
}

View File

@@ -1,8 +1,10 @@
package starter
import (
"mayfly-go/migrations"
"mayfly-go/initialize"
"mayfly-go/pkg/config"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logger"
"mayfly-go/pkg/req"
)
@@ -25,6 +27,10 @@ func RunWebServer() {
// 有配置redis信息则初始化redis。多台机器部署需要使用redis存储验证码、权限、公私钥等
initRedis()
// 数据库升级操作
if err := migrations.RunMigrations(global.Db); err != nil {
logger.Log.Fatalf("数据库升级失败: %v", err)
}
// 初始化其他需要启动时运行的方法
initialize.InitOther()