mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-12-16 04:36:35 +08:00
refactor: oauth2登录调整
This commit is contained in:
@@ -1,24 +1,25 @@
|
|||||||
window.globalConfig = {
|
window.globalConfig = {
|
||||||
// 默认为空,以访问根目录为api请求地址。若前后端分离部署可单独配置该后端api请求地址
|
// 默认为空,以访问根目录为api请求地址。若前后端分离部署可单独配置该后端api请求地址
|
||||||
"BaseApiUrl": "",
|
BaseApiUrl: '',
|
||||||
"BaseWsUrl": ""
|
BaseWsUrl: '',
|
||||||
}
|
};
|
||||||
|
|
||||||
// index.html添加百秒级时间戳,防止被浏览器缓存
|
// index.html添加百秒级时间戳,防止被浏览器缓存
|
||||||
!function () {
|
// !(function () {
|
||||||
let t = "t=" + new Date().getTime().toString().substring(0, 8)
|
// let t = 't=' + new Date().getTime().toString().substring(0, 8);
|
||||||
let search = location.search;
|
// let search = location.search;
|
||||||
let m = search && search.match(/t=\d*/g)
|
// let m = search && search.match(/t=\d*/g);
|
||||||
|
|
||||||
if (m[0]) {
|
// console.log(location);
|
||||||
if (m[0] !== t) {
|
// if (m[0]) {
|
||||||
location.search = search.replace(m[0], t)
|
// if (m[0] !== t) {
|
||||||
}
|
// location.search = search.replace(m[0], t);
|
||||||
} else {
|
// }
|
||||||
if (search.indexOf('?') > -1) {
|
// } else {
|
||||||
location.search = search + '&' + t
|
// if (search.indexOf('?') > -1) {
|
||||||
} else {
|
// location.search = search + '&' + t;
|
||||||
location.search = t
|
// } else {
|
||||||
}
|
// location.search = t;
|
||||||
}
|
// }
|
||||||
}()
|
// }
|
||||||
|
// })();
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ export default {
|
|||||||
captcha: () => request.get('/sys/captcha'),
|
captcha: () => request.get('/sys/captcha'),
|
||||||
logout: () => request.post('/auth/accounts/logout'),
|
logout: () => request.post('/auth/accounts/logout'),
|
||||||
getPermissions: () => request.get('/sys/accounts/permissions'),
|
getPermissions: () => request.get('/sys/accounts/permissions'),
|
||||||
|
oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const token = getSession('token');
|
const token = getSession('token');
|
||||||
if (to.path === '/login' && !token) {
|
if ((to.path === '/login' || to.path == '/oauth2/callback') && !token) {
|
||||||
next();
|
next();
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -143,6 +143,14 @@ export const staticRoutes: Array<RouteRecordRaw> = [
|
|||||||
title: '没有权限',
|
title: '没有权限',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/oauth2/callback',
|
||||||
|
name: 'oauth2Callback',
|
||||||
|
component: () => import('@/views/oauth/Oauth2Callback.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'oauth2回调',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/machine/terminal',
|
path: '/machine/terminal',
|
||||||
name: 'machineTerminal',
|
name: 'machineTerminal',
|
||||||
|
|||||||
@@ -104,6 +104,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog title="修改基本信息" v-model="baseInfoDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
|
||||||
|
<el-form :model="baseInfoDialog.form" :rules="baseInfoDialog.rules" ref="changePwdFormRef" label-width="auto">
|
||||||
|
<el-form-item prop="username" label="用户名" required>
|
||||||
|
<el-input v-model.trim="baseInfoDialog.form.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="name" label="姓名" required>
|
||||||
|
<el-input v-model.trim="baseInfoDialog.form.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<!-- <el-button @click="cancelChangePwd">取 消</el-button> -->
|
||||||
|
<el-button @click="updateUserInfo()" type="primary" :loading="loading.updateUserConfirm">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -112,7 +130,7 @@ import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
|
|||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { initRouter } from '@/router/index';
|
import { initRouter } from '@/router/index';
|
||||||
import { setSession, setUserInfo2Session, setUseWatermark2Session } from '@/common/utils/storage';
|
import { getSession, setSession, setUserInfo2Session, setUseWatermark2Session } from '@/common/utils/storage';
|
||||||
import { formatAxis } from '@/common/utils/format';
|
import { formatAxis } from '@/common/utils/format';
|
||||||
import openApi from '@/common/openApi';
|
import openApi from '@/common/openApi';
|
||||||
import { RsaEncrypt } from '@/common/rsa';
|
import { RsaEncrypt } from '@/common/rsa';
|
||||||
@@ -120,6 +138,7 @@ import { getAccountLoginSecurity, useWartermark } from '@/common/sysconfig';
|
|||||||
import { letterAvatar } from '@/common/utils/string';
|
import { letterAvatar } from '@/common/utils/string';
|
||||||
import { useUserInfo } from '@/store/userInfo';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
import QrcodeVue from 'qrcode.vue';
|
import QrcodeVue from 'qrcode.vue';
|
||||||
|
import { personApi } from '@/views/personal/api';
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||||
@@ -149,6 +168,7 @@ const state = reactive({
|
|||||||
captcha: '',
|
captcha: '',
|
||||||
cid: '',
|
cid: '',
|
||||||
},
|
},
|
||||||
|
loginRes: {} as any,
|
||||||
changePwdDialog: {
|
changePwdDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
form: {
|
form: {
|
||||||
@@ -178,14 +198,26 @@ const state = reactive({
|
|||||||
code: [{ required: true, message: '请输入OTP授权码', trigger: 'blur' }],
|
code: [{ required: true, message: '请输入OTP授权码', trigger: 'blur' }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
baseInfoDialog: {
|
||||||
|
visible: false,
|
||||||
|
form: {
|
||||||
|
username: '',
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||||
|
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
loading: {
|
loading: {
|
||||||
signIn: false,
|
signIn: false,
|
||||||
changePwd: false,
|
changePwd: false,
|
||||||
otpConfirm: false,
|
otpConfirm: false,
|
||||||
|
updateUserConfirm: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { accountLoginSecurity, showLoginFailTips, captchaImage, loginForm, changePwdDialog, otpDialog, loading } = toRefs(state);
|
const { accountLoginSecurity, showLoginFailTips, captchaImage, loginForm, changePwdDialog, otpDialog, baseInfoDialog, loading } = toRefs(state);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
@@ -268,7 +300,17 @@ const onSignIn = async () => {
|
|||||||
loginResDeal(loginRes);
|
loginResDeal(loginRes);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateUserInfo = async () => {
|
||||||
|
const form = state.baseInfoDialog.form;
|
||||||
|
await personApi.updateAccount.request(state.baseInfoDialog.form);
|
||||||
|
state.baseInfoDialog.visible = false;
|
||||||
|
useUserInfo().userInfo.username = form.username;
|
||||||
|
useUserInfo().userInfo.name = form.name;
|
||||||
|
await toIndex();
|
||||||
|
};
|
||||||
|
|
||||||
const loginResDeal = (loginRes: any) => {
|
const loginResDeal = (loginRes: any) => {
|
||||||
|
state.loginRes = loginRes;
|
||||||
// 用户信息
|
// 用户信息
|
||||||
const userInfos = {
|
const userInfos = {
|
||||||
name: loginRes.name,
|
name: loginRes.name,
|
||||||
@@ -300,16 +342,26 @@ const loginResDeal = (loginRes: any) => {
|
|||||||
}, 400);
|
}, 400);
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
loginResDeal,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 登录成功后的跳转
|
// 登录成功后的跳转
|
||||||
const signInSuccess = async (accessToken: string = '') => {
|
const signInSuccess = async (accessToken: string = '') => {
|
||||||
|
if (!accessToken) {
|
||||||
|
accessToken = getSession('token');
|
||||||
|
}
|
||||||
// 存储 token 到浏览器缓存
|
// 存储 token 到浏览器缓存
|
||||||
setSession('token', accessToken);
|
setSession('token', accessToken);
|
||||||
// 初始化路由
|
// 初始化路由
|
||||||
await initRouter();
|
await initRouter();
|
||||||
|
|
||||||
|
// 判断是否为第一次oauth2登录,是的话需要用户填写姓名和用户名
|
||||||
|
if (state.loginRes.isFirstOauth2Login) {
|
||||||
|
state.baseInfoDialog.form.username = state.loginRes.username;
|
||||||
|
state.baseInfoDialog.visible = true;
|
||||||
|
} else {
|
||||||
|
await toIndex();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toIndex = async () => {
|
||||||
// 初始化登录成功时间问候语
|
// 初始化登录成功时间问候语
|
||||||
let currentTimeInfo = currentTime.value;
|
let currentTimeInfo = currentTime.value;
|
||||||
// 登录成功,跳到转首页
|
// 登录成功,跳到转首页
|
||||||
@@ -356,6 +408,10 @@ const cancelChangePwd = () => {
|
|||||||
state.changePwdDialog.form.username = '';
|
state.changePwdDialog.form.username = '';
|
||||||
getCaptcha();
|
getCaptcha();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
loginResDeal,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -68,19 +68,22 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const oauth2Login = () => {
|
const oauth2Login = () => {
|
||||||
|
const width = 700;
|
||||||
|
const height = 500;
|
||||||
|
var iTop = (window.screen.height - 30 - height) / 2; //获得窗口的垂直位置;
|
||||||
|
var iLeft = (window.screen.width - 10 - width) / 2; //获得窗口的水平位置;
|
||||||
// 小窗口打开oauth2鉴权
|
// 小窗口打开oauth2鉴权
|
||||||
let oauthWindoe = window.open(config.baseApiUrl + '/auth/oauth2/login', 'oauth2', 'width=600,height=600');
|
let oauthWindow = window.open(config.baseApiUrl + '/auth/oauth2/login', 'oauth2', `height=${height},width=${width},top=${iTop},left=${iLeft},location=no`);
|
||||||
if (oauthWindoe) {
|
if (oauthWindow) {
|
||||||
const handler = (e: any) => {
|
const handler = (e: any) => {
|
||||||
if (e.data.action === 'oauthLogin') {
|
if (e.data.action === 'oauthLogin') {
|
||||||
oauthWindoe!.close();
|
|
||||||
window.removeEventListener('message', handler);
|
window.removeEventListener('message', handler);
|
||||||
loginForm.value!.loginResDeal(e.data);
|
loginForm.value!.loginResDeal(e.data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('message', handler);
|
window.addEventListener('message', handler);
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (oauthWindoe!.closed) {
|
if (oauthWindow!.closed) {
|
||||||
window.removeEventListener('message', handler);
|
window.removeEventListener('message', handler);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|||||||
39
mayfly_go_web/src/views/oauth/Oauth2Callback.vue
Normal file
39
mayfly_go_web/src/views/oauth/Oauth2Callback.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import openApi from '@/common/openApi';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const queryParam = route.query;
|
||||||
|
// 使用hash路由,回调code可能会被设置到search
|
||||||
|
// 如 localhost:8888/?code=xxxx/oauth2/callback,导致route.query获取不到值
|
||||||
|
if (location.search) {
|
||||||
|
const searchParams = location.search.split('?')[1];
|
||||||
|
if (searchParams) {
|
||||||
|
for (let searchParam of searchParams.split('&')) {
|
||||||
|
const searchParamSplit = searchParam.split('=');
|
||||||
|
queryParam[searchParamSplit[0]] = searchParamSplit[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res: any = await openApi.oauth2Callback(queryParam);
|
||||||
|
ElMessage.success('授权认证成功');
|
||||||
|
top?.opener.postMessage(res);
|
||||||
|
window.close();
|
||||||
|
} catch (e: any) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.close();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
@@ -5,4 +5,5 @@ export const personApi = {
|
|||||||
updateAccount: Api.newPut('/sys/accounts/self'),
|
updateAccount: Api.newPut('/sys/accounts/self'),
|
||||||
authStatus: Api.newGet('/auth/oauth2/status'),
|
authStatus: Api.newGet('/auth/oauth2/status'),
|
||||||
getMsgs: Api.newGet('/msgs/self'),
|
getMsgs: Api.newGet('/msgs/self'),
|
||||||
|
unbindOauth2: Api.newGet('/auth/oauth2/unbind'),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -136,7 +136,8 @@
|
|||||||
<div class="personal-edit-safe-item-left-value">当前状态:{{ authStatus.bind ? '已绑定' : '未绑定' }}</div>
|
<div class="personal-edit-safe-item-left-value">当前状态:{{ authStatus.bind ? '已绑定' : '未绑定' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="personal-edit-safe-item-right">
|
<div class="personal-edit-safe-item-right">
|
||||||
<el-button link @click="bindOAuth2" :disabled="authStatus.bind" type="primary">立即绑定</el-button>
|
<el-button v-if="!authStatus.bind" link @click="bindOAuth2" type="primary">立即绑定</el-button>
|
||||||
|
<el-button v-else link @click="unbindOAuth2()" type="warning">解绑</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,8 +203,8 @@ const state = reactive({
|
|||||||
password: '',
|
password: '',
|
||||||
},
|
},
|
||||||
authStatus: {
|
authStatus: {
|
||||||
enable: { oauth2: false },
|
enable: false,
|
||||||
bind: { oauth2: false },
|
bind: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -241,12 +242,19 @@ const updateAccount = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const bindOAuth2 = () => {
|
const bindOAuth2 = () => {
|
||||||
|
const width = 700;
|
||||||
|
const height = 500;
|
||||||
|
var iTop = (window.screen.height - 30 - height) / 2; //获得窗口的垂直位置;
|
||||||
|
var iLeft = (window.screen.width - 10 - width) / 2; //获得窗口的水平位置;
|
||||||
// 小窗口打开oauth2鉴权
|
// 小窗口打开oauth2鉴权
|
||||||
let oauthWindow = window.open(config.baseApiUrl + '/auth/oauth2/bind?token=' + getSession('token'), 'oauth2', 'width=600,height=600');
|
let oauthWindow = window.open(
|
||||||
|
config.baseApiUrl + '/auth/oauth2/bind?token=' + getSession('token'),
|
||||||
|
'oauth2',
|
||||||
|
`height=${height},width=${width},top=${iTop},left=${iLeft},location=no`
|
||||||
|
);
|
||||||
if (oauthWindow) {
|
if (oauthWindow) {
|
||||||
const handler = (e: any) => {
|
const handler = (e: any) => {
|
||||||
if (e.data.action === 'oauthBind') {
|
if (e.data.action === 'oauthBind') {
|
||||||
oauthWindow!.close();
|
|
||||||
window.removeEventListener('message', handler);
|
window.removeEventListener('message', handler);
|
||||||
// 处理登录token
|
// 处理登录token
|
||||||
ElMessage.success('绑定成功');
|
ElMessage.success('绑定成功');
|
||||||
@@ -264,6 +272,12 @@ const bindOAuth2 = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const unbindOAuth2 = async () => {
|
||||||
|
await personApi.unbindOauth2.request();
|
||||||
|
ElMessage.success('解绑成功');
|
||||||
|
state.authStatus = await personApi.authStatus.request();
|
||||||
|
};
|
||||||
|
|
||||||
const getMsgs = async () => {
|
const getMsgs = async () => {
|
||||||
const res = await personApi.getMsgs.request(state.msgDialog.query);
|
const res = await personApi.getMsgs.request(state.msgDialog.query);
|
||||||
state.msgDialog.msgs = res;
|
state.msgDialog.msgs = res;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mayfly-go/internal/auth/api/vo"
|
"mayfly-go/internal/auth/api/vo"
|
||||||
@@ -59,7 +58,7 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
|
|||||||
biz.NotEmpty(stateAction, "state已过期, 请重新登录")
|
biz.NotEmpty(stateAction, "state已过期, 请重新登录")
|
||||||
|
|
||||||
token, err := client.Exchange(rc.GinCtx, code)
|
token, err := client.Exchange(rc.GinCtx, code)
|
||||||
biz.ErrIsNilAppendErr(err, "获取token失败: %s")
|
biz.ErrIsNilAppendErr(err, "获取OAuth2 accessToken失败: %s")
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
httpCli := client.Client(rc.GinCtx.Request.Context(), token)
|
httpCli := client.Client(rc.GinCtx.Request.Context(), token)
|
||||||
@@ -104,7 +103,12 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
|
|||||||
err = a.Oauth2App.GetOAuthAccount(&entity.Oauth2Account{
|
err = a.Oauth2App.GetOAuthAccount(&entity.Oauth2Account{
|
||||||
AccountId: accountId,
|
AccountId: accountId,
|
||||||
}, "account_id", "identity")
|
}, "account_id", "identity")
|
||||||
biz.IsTrue(err != nil, "该账号已被绑定")
|
biz.IsTrue(err != nil, "该账号已被其他用户绑定")
|
||||||
|
|
||||||
|
err = a.Oauth2App.GetOAuthAccount(&entity.Oauth2Account{
|
||||||
|
Identity: userId,
|
||||||
|
}, "account_id", "identity")
|
||||||
|
biz.IsTrue(err != nil, "您已绑定其他账号")
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
err = a.Oauth2App.BindOAuthAccount(&entity.Oauth2Account{
|
err = a.Oauth2App.BindOAuthAccount(&entity.Oauth2Account{
|
||||||
@@ -118,13 +122,7 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
|
|||||||
"action": "oauthBind",
|
"action": "oauthBind",
|
||||||
"bind": true,
|
"bind": true,
|
||||||
}
|
}
|
||||||
b, err = json.Marshal(res)
|
rc.ResData = res
|
||||||
biz.ErrIsNil(err, "数据序列化失败")
|
|
||||||
rc.GinCtx.Header("Content-Type", "text/html; charset=utf-8")
|
|
||||||
rc.GinCtx.Writer.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = rc.GinCtx.Writer.WriteString("<html>" +
|
|
||||||
"<script>top.opener.postMessage(" + string(b) + ")</script>" +
|
|
||||||
"</html>")
|
|
||||||
} else {
|
} else {
|
||||||
panic(biz.NewBizErr("state不合法"))
|
panic(biz.NewBizErr("state不合法"))
|
||||||
}
|
}
|
||||||
@@ -132,14 +130,12 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
|
|||||||
|
|
||||||
// 指定登录操作
|
// 指定登录操作
|
||||||
func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *sysentity.ConfigOauth2Login) {
|
func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *sysentity.ConfigOauth2Login) {
|
||||||
clientIp := getIpAndRegion(rc)
|
|
||||||
rc.ReqParam = fmt.Sprintf("oauth2 login username: %s | ip: %s", userId, clientIp)
|
|
||||||
|
|
||||||
// 查询用户是否存在
|
// 查询用户是否存在
|
||||||
oauthAccount := &entity.Oauth2Account{Identity: userId}
|
oauthAccount := &entity.Oauth2Account{Identity: userId}
|
||||||
err := a.Oauth2App.GetOAuthAccount(oauthAccount, "account_id", "identity")
|
err := a.Oauth2App.GetOAuthAccount(oauthAccount, "account_id", "identity")
|
||||||
|
|
||||||
var accountId uint64
|
var accountId uint64
|
||||||
|
isFirst := false
|
||||||
// 不存在,进行注册
|
// 不存在,进行注册
|
||||||
if err != nil {
|
if err != nil {
|
||||||
biz.IsTrue(oauth.AutoRegister, "系统未开启自动注册, 请先让管理员添加对应账号")
|
biz.IsTrue(oauth.AutoRegister, "系统未开启自动注册, 请先让管理员添加对应账号")
|
||||||
@@ -164,6 +160,7 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *sysentity
|
|||||||
})
|
})
|
||||||
biz.ErrIsNilAppendErr(err, "绑定用户失败: %s")
|
biz.ErrIsNilAppendErr(err, "绑定用户失败: %s")
|
||||||
accountId = account.Id
|
accountId = account.Id
|
||||||
|
isFirst = true
|
||||||
} else {
|
} else {
|
||||||
accountId = oauthAccount.AccountId
|
accountId = oauthAccount.AccountId
|
||||||
}
|
}
|
||||||
@@ -175,15 +172,13 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *sysentity
|
|||||||
err = a.AccountApp.GetAccount(account, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
|
err = a.AccountApp.GetAccount(account, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
|
||||||
biz.ErrIsNilAppendErr(err, "获取用户信息失败: %s")
|
biz.ErrIsNilAppendErr(err, "获取用户信息失败: %s")
|
||||||
|
|
||||||
|
clientIp := getIpAndRegion(rc)
|
||||||
|
rc.ReqParam = fmt.Sprintf("oauth2 login username: %s | ip: %s", account.Username, clientIp)
|
||||||
|
|
||||||
res := LastLoginCheck(account, a.ConfigApp.GetConfig(sysentity.ConfigKeyAccountLoginSecurity).ToAccountLoginSecurity(), clientIp)
|
res := LastLoginCheck(account, a.ConfigApp.GetConfig(sysentity.ConfigKeyAccountLoginSecurity).ToAccountLoginSecurity(), clientIp)
|
||||||
res["action"] = "oauthLogin"
|
res["action"] = "oauthLogin"
|
||||||
b, err := json.Marshal(res)
|
res["isFirstOauth2Login"] = isFirst
|
||||||
biz.ErrIsNil(err, "数据序列化失败")
|
rc.ResData = res
|
||||||
rc.GinCtx.Header("Content-Type", "text/html; charset=utf-8")
|
|
||||||
rc.GinCtx.Writer.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = rc.GinCtx.Writer.WriteString("<html>" +
|
|
||||||
"<script>top.opener.postMessage(" + string(b) + ")</script>" +
|
|
||||||
"</html>")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Oauth2Login) getOAuthClient() (*oauth2.Config, *sysentity.ConfigOauth2Login) {
|
func (a *Oauth2Login) getOAuthClient() (*oauth2.Config, *sysentity.ConfigOauth2Login) {
|
||||||
@@ -198,7 +193,7 @@ func (a *Oauth2Login) getOAuthClient() (*oauth2.Config, *sysentity.ConfigOauth2L
|
|||||||
AuthURL: oath2LoginConfig.AuthorizationURL,
|
AuthURL: oath2LoginConfig.AuthorizationURL,
|
||||||
TokenURL: oath2LoginConfig.AccessTokenURL,
|
TokenURL: oath2LoginConfig.AccessTokenURL,
|
||||||
},
|
},
|
||||||
RedirectURL: oath2LoginConfig.RedirectURL + "/api/auth/oauth2/callback",
|
RedirectURL: oath2LoginConfig.RedirectURL + "/#/oauth2/callback",
|
||||||
Scopes: strings.Split(oath2LoginConfig.Scopes, ","),
|
Scopes: strings.Split(oath2LoginConfig.Scopes, ","),
|
||||||
}
|
}
|
||||||
return client, oath2LoginConfig
|
return client, oath2LoginConfig
|
||||||
@@ -217,3 +212,7 @@ func (a *Oauth2Login) Oauth2Status(ctx *req.Ctx) {
|
|||||||
|
|
||||||
ctx.ResData = res
|
ctx.ResData = res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Oauth2Login) Oauth2Unbind(rc *req.Ctx) {
|
||||||
|
a.Oauth2App.Unbind(rc.LoginAccount.Id)
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type Oauth2 interface {
|
|||||||
GetOAuthAccount(condition *entity.Oauth2Account, cols ...string) error
|
GetOAuthAccount(condition *entity.Oauth2Account, cols ...string) error
|
||||||
|
|
||||||
BindOAuthAccount(e *entity.Oauth2Account) error
|
BindOAuthAccount(e *entity.Oauth2Account) error
|
||||||
|
|
||||||
|
Unbind(accountId uint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAuthApp(oauthAccountRepo repository.Oauth2Account) Oauth2 {
|
func newAuthApp(oauthAccountRepo repository.Oauth2Account) Oauth2 {
|
||||||
@@ -36,3 +38,7 @@ func (a *oauth2AppImpl) GetOAuthAccount(condition *entity.Oauth2Account, cols ..
|
|||||||
func (a *oauth2AppImpl) BindOAuthAccount(e *entity.Oauth2Account) error {
|
func (a *oauth2AppImpl) BindOAuthAccount(e *entity.Oauth2Account) error {
|
||||||
return a.oauthAccountRepo.SaveOAuthAccount(e)
|
return a.oauthAccountRepo.SaveOAuthAccount(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *oauth2AppImpl) Unbind(accountId uint64) {
|
||||||
|
a.oauthAccountRepo.DeleteBy(&entity.Oauth2Account{AccountId: accountId})
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ type Oauth2Account interface {
|
|||||||
GetOAuthAccount(condition *entity.Oauth2Account, cols ...string) error
|
GetOAuthAccount(condition *entity.Oauth2Account, cols ...string) error
|
||||||
|
|
||||||
SaveOAuthAccount(e *entity.Oauth2Account) error
|
SaveOAuthAccount(e *entity.Oauth2Account) error
|
||||||
|
|
||||||
|
DeleteBy(e *entity.Oauth2Account)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,3 +22,7 @@ func (a *oauth2AccountRepoImpl) SaveOAuthAccount(e *entity.Oauth2Account) error
|
|||||||
}
|
}
|
||||||
return gormx.UpdateById(e)
|
return gormx.UpdateById(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *oauth2AccountRepoImpl) DeleteBy(e *entity.Oauth2Account) {
|
||||||
|
gormx.DeleteByCondition(e)
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,9 +45,11 @@ func Init(router *gin.RouterGroup) {
|
|||||||
req.NewGet("/oauth2/bind", oauth2Login.OAuth2Bind),
|
req.NewGet("/oauth2/bind", oauth2Login.OAuth2Bind),
|
||||||
|
|
||||||
// oauth2回调地址
|
// oauth2回调地址
|
||||||
req.NewGet("/oauth2/callback", oauth2Login.OAuth2Callback).Log(req.NewLogSave("oauth2回调")).NoRes().DontNeedToken(),
|
req.NewGet("/oauth2/callback", oauth2Login.OAuth2Callback).Log(req.NewLogSave("oauth2回调")).DontNeedToken(),
|
||||||
|
|
||||||
req.NewGet("/oauth2/status", oauth2Login.Oauth2Status),
|
req.NewGet("/oauth2/status", oauth2Login.Oauth2Status),
|
||||||
|
|
||||||
|
req.NewGet("/oauth2/unbind", oauth2Login.Oauth2Unbind).Log(req.NewLogSave("oauth2解绑")),
|
||||||
}
|
}
|
||||||
|
|
||||||
req.BatchSetGroup(rg, reqs[:])
|
req.BatchSetGroup(rg, reqs[:])
|
||||||
|
|||||||
@@ -105,6 +105,13 @@ func (a *Account) UpdateAccount(rc *req.Ctx) {
|
|||||||
biz.IsTrue(utils.CheckAccountPasswordLever(updateAccount.Password), "密码强度必须8位以上且包含字⺟⼤⼩写+数字+特殊符号")
|
biz.IsTrue(utils.CheckAccountPasswordLever(updateAccount.Password), "密码强度必须8位以上且包含字⺟⼤⼩写+数字+特殊符号")
|
||||||
updateAccount.Password = cryptox.PwdHash(updateAccount.Password)
|
updateAccount.Password = cryptox.PwdHash(updateAccount.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldAcc := a.AccountApp.GetById(updateAccount.Id)
|
||||||
|
// 账号创建十分钟内允许修改用户名(兼容oauth2首次登录修改用户名),否则不允许修改
|
||||||
|
if oldAcc.CreateTime.Add(10 * time.Minute).Before(time.Now()) {
|
||||||
|
// 禁止更新用户名,防止误传被更新
|
||||||
|
updateAccount.Username = ""
|
||||||
|
}
|
||||||
a.AccountApp.Update(updateAccount)
|
a.AccountApp.Update(updateAccount)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +140,8 @@ func (a *Account) SaveAccount(rc *req.Ctx) {
|
|||||||
biz.IsTrue(utils.CheckAccountPasswordLever(account.Password), "密码强度必须8位以上且包含字⺟⼤⼩写+数字+特殊符号")
|
biz.IsTrue(utils.CheckAccountPasswordLever(account.Password), "密码强度必须8位以上且包含字⺟⼤⼩写+数字+特殊符号")
|
||||||
account.Password = cryptox.PwdHash(account.Password)
|
account.Password = cryptox.PwdHash(account.Password)
|
||||||
}
|
}
|
||||||
|
// 更新操作不允许修改用户名、防止误传更新
|
||||||
|
account.Username = ""
|
||||||
a.AccountApp.Update(account)
|
a.AccountApp.Update(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ type AccountCreateForm struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AccountUpdateForm struct {
|
type AccountUpdateForm struct {
|
||||||
Password *string `json:"password" binding:"min=6,max=16"`
|
Name string `json:"name" binding:"max=16"` // 姓名
|
||||||
|
Username string `json:"username" binding:"max=20"`
|
||||||
|
Password *string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountChangePasswordForm struct {
|
type AccountChangePasswordForm struct {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
type Account interface {
|
type Account interface {
|
||||||
GetAccount(condition *entity.Account, cols ...string) error
|
GetAccount(condition *entity.Account, cols ...string) error
|
||||||
|
|
||||||
|
GetById(id uint64) *entity.Account
|
||||||
|
|
||||||
GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult[any]
|
GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult[any]
|
||||||
|
|
||||||
Create(account *entity.Account)
|
Create(account *entity.Account)
|
||||||
@@ -38,6 +40,10 @@ func (a *accountAppImpl) GetAccount(condition *entity.Account, cols ...string) e
|
|||||||
return a.accountRepo.GetAccount(condition, cols...)
|
return a.accountRepo.GetAccount(condition, cols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *accountAppImpl) GetById(id uint64) *entity.Account {
|
||||||
|
return a.accountRepo.GetById(id)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *accountAppImpl) GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult[any] {
|
func (a *accountAppImpl) GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult[any] {
|
||||||
return a.accountRepo.GetPageList(condition, pageParam, toEntity)
|
return a.accountRepo.GetPageList(condition, pageParam, toEntity)
|
||||||
}
|
}
|
||||||
@@ -51,8 +57,12 @@ func (a *accountAppImpl) Create(account *entity.Account) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *accountAppImpl) Update(account *entity.Account) {
|
func (a *accountAppImpl) Update(account *entity.Account) {
|
||||||
// 禁止更新用户名,防止误传被更新
|
if account.Username != "" {
|
||||||
account.Username = ""
|
unAcc := &entity.Account{Username: account.Username}
|
||||||
|
err := a.GetAccount(unAcc)
|
||||||
|
biz.IsTrue(err != nil || unAcc.Id == account.Id, "该用户名已存在")
|
||||||
|
}
|
||||||
|
|
||||||
a.accountRepo.Update(account)
|
a.accountRepo.Update(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ type Account interface {
|
|||||||
// 根据条件获取账号信息
|
// 根据条件获取账号信息
|
||||||
GetAccount(condition *entity.Account, cols ...string) error
|
GetAccount(condition *entity.Account, cols ...string) error
|
||||||
|
|
||||||
|
GetById(id uint64) *entity.Account
|
||||||
|
|
||||||
GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult[any]
|
GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult[any]
|
||||||
|
|
||||||
Insert(account *entity.Account)
|
Insert(account *entity.Account)
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ func (a *accountRepoImpl) GetAccount(condition *entity.Account, cols ...string)
|
|||||||
return gormx.GetBy(condition, cols...)
|
return gormx.GetBy(condition, cols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *accountRepoImpl) GetById(id uint64) *entity.Account {
|
||||||
|
ac := new(entity.Account)
|
||||||
|
if err := gormx.GetById(ac, id); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ac
|
||||||
|
}
|
||||||
|
|
||||||
func (m *accountRepoImpl) GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult[any] {
|
func (m *accountRepoImpl) GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) *model.PageResult[any] {
|
||||||
qd := gormx.NewQuery(new(entity.Account)).
|
qd := gormx.NewQuery(new(entity.Account)).
|
||||||
Like("name", condition.Name).
|
Like("name", condition.Name).
|
||||||
|
|||||||
Reference in New Issue
Block a user