mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 07:20:24 +08:00
wip: 自定义oauth2登录配置
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,4 +17,5 @@
|
||||
*/node_modules/
|
||||
**/vendor/
|
||||
.idea
|
||||
.vscode
|
||||
out
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
};
|
||||
|
||||
97
mayfly_go_web/src/views/system/auth/AuthInfo.vue
Normal file
97
mayfly_go_web/src/views/system/auth/AuthInfo.vue
Normal 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
2
server/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
static/static
|
||||
config.yml
|
||||
@@ -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
|
||||
)
|
||||
|
||||
74
server/internal/sys/api/auth.go
Normal file
74
server/internal/sys/api/auth.go
Normal 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)
|
||||
}
|
||||
12
server/internal/sys/api/form/auth.go
Normal file
12
server/internal/sys/api/form/auth.go
Normal 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"`
|
||||
}
|
||||
16
server/internal/sys/api/vo/auth.go
Normal file
16
server/internal/sys/api/vo/auth.go
Normal 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"`
|
||||
}
|
||||
@@ -17,7 +17,7 @@ type Config interface {
|
||||
|
||||
Save(config *entity.Config)
|
||||
|
||||
// 获取指定key的配置信息, 不会返回nil, 若不存在则值都默认值即空字符串
|
||||
// GetConfig 获取指定key的配置信息, 不会返回nil, 若不存在则值都默认值即空字符串
|
||||
GetConfig(key string) *entity.Config
|
||||
}
|
||||
|
||||
|
||||
@@ -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"`
|
||||
|
||||
24
server/internal/sys/router/auth.go
Normal file
24
server/internal/sys/router/auth.go
Normal 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[:])
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ func Init(router *gin.RouterGroup) {
|
||||
InitSystemRouter(router)
|
||||
InitSyslogRouter(router)
|
||||
InitSysConfigRouter(router)
|
||||
InitSysAuthRouter(router)
|
||||
}
|
||||
|
||||
19
server/migrations/2022.go
Normal file
19
server/migrations/2022.go
Normal 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
|
||||
},
|
||||
}
|
||||
}
|
||||
93
server/migrations/20230720.go
Normal file
93
server/migrations/20230720.go
Normal 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
45
server/migrations/init.go
Normal 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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user