【底座】采纳建议 #IJVMPI 第三方登录支持“微信小程序”登录,已增加后端相关功能,前端需暂未使用wx.login,需自行增加

This commit is contained in:
xuyuxiang
2026-07-02 23:39:33 +08:00
parent 138a2a6516
commit f073d50b95
12 changed files with 222 additions and 12 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -24,6 +24,9 @@
<a v-if="formData.SNOWY_THIRD_WECHAT_ALLOW_LOGIN_FLAG" @click="getLoginRenderUrl('WECHAT')">
<img style="width: 32px; height: 32px" src="/src/assets/images/authSource/wechat.png" alt="" />
</a>
<a v-if="formData.SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG" @click="getLoginRenderUrl('WECHAT_MINI')">
<img style="width: 32px; height: 32px" src="/src/assets/images/authSource/wechatmini.png" alt="" />
</a>
<a v-if="formData.SNOWY_THIRD_DINGTALK_ALLOW_LOGIN_FLAG" @click="getLoginRenderUrl('DINGTALK')">
<img style="width: 32px; height: 32px" src="/src/assets/images/authSource/dingtalk.png" alt="" />
</a>
@@ -60,6 +63,7 @@
import { ref, computed } from 'vue'
import configApi from '@/api/dev/configApi'
import thirdApi from '@/api/auth/thirdApi'
import {message} from "ant-design-vue";
const formData = ref({})
const hasAnyThirdLogin = computed(() => {
return Object.values(formData.value).some((v) => v === true)
@@ -80,13 +84,17 @@
}
}
const getLoginRenderUrl = (platform) => {
const param = {
platform: platform,
clientType: 'B'
if(platform === 'WECHAT_MINI') {
message.warning('请在微信小程序环境使用微信小程序登录')
} else {
const param = {
platform: platform,
clientType: 'B'
}
thirdApi.thirdRender(param).then((data) => {
window.location.href = data.authorizeUrl
})
}
thirdApi.thirdRender(param).then((data) => {
window.location.href = data.authorizeUrl
})
}
getConfigSysThirdAllowFlagList()
</script>
@@ -176,7 +176,7 @@
}
]
// 操作栏通过权限判断是否显示
if (hasPerm(['bizNoticeEdit', 'bizNoticeDelete'])) {
if (hasPerm(['bizNoticeEdit', 'bizNoticeDelete', 'bizNoticeDetail'])) {
columns.push({
title: '操作',
dataIndex: 'action',
@@ -84,6 +84,18 @@
</template>
<wechatThirdForm />
</a-tab-pane>
<a-tab-pane key="wechatMiniThird">
<template #tab>
<span>
<img
src="@/assets/images/authSource/wechatmini.png"
style="width: 20px; height: 20px; margin-right: 8px; vertical-align: middle"
/>
微信小程序
</span>
</template>
<wechatMiniThirdForm />
</a-tab-pane>
<a-tab-pane key="dingTalkThird">
<template #tab>
<span>
@@ -203,6 +215,7 @@
import SamlThirdForm from './samlThirdForm.vue'
import IamThirdForm from './iamThirdForm.vue'
import WechatThirdForm from './wechatThirdForm.vue'
import WechatMiniThirdForm from './wechatMiniThirdForm.vue'
import DingTalkThirdForm from './dingTalkThirdForm.vue'
import WorkWechatThirdForm from './workWechatThirdForm.vue'
import FeiShuThirdForm from './feiShuThirdForm.vue'
@@ -32,7 +32,7 @@
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item ame="SNOWY_THIRD_SAML_CERTIFICATE" v-if="formData.SNOWY_THIRD_SAML_ALLOW_LOGIN_FLAG">
<a-form-item name="SNOWY_THIRD_SAML_CERTIFICATE" v-if="formData.SNOWY_THIRD_SAML_ALLOW_LOGIN_FLAG">
<template #label>
<a-tooltip>
<template #title> AuthSamlClient生成自签证书后填入 </template>
@@ -0,0 +1,123 @@
<template>
<a-spin :spinning="loadSpinning">
<a-alert message="微信小程序需要前端处于微信小程序环境,本系统仅提供后端功能,前端需自行开发" type="info" :closable="false" class="mb-2" show-icon />
<a-form
ref="formRef"
:model="formData"
:rules="formRules"
layout="vertical"
:label-col="{ ...layout.labelCol, offset: 0 }"
:wrapper-col="{ ...layout.wrapperCol, offset: 0 }"
>
<a-row :gutter="8">
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="是否允许微信小程序登录:" name="SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG">
<a-switch
v-model:checked="formData.SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG"
checked-children="是"
un-checked-children="否"
placeholder="请选择是否允许微小程序信登录"
/>
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item
label="clientId:"
name="SNOWY_THIRD_WECHAT_MINI_CLIENT_ID"
v-if="formData.SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG"
>
<a-input v-model:value="formData.SNOWY_THIRD_WECHAT_MINI_CLIENT_ID" placeholder="请输入clientId" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item
label="clientSecret:"
name="SNOWY_THIRD_WECHAT_MINI_CLIENT_SECRET"
v-if="formData.SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG"
>
<a-input v-model:value="formData.SNOWY_THIRD_WECHAT_MINI_CLIENT_SECRET" placeholder="请输入clientSecret" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item>
<a-space>
<a-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</a-button>
<a-button @click="() => formRef.resetFields()">重置</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-spin>
</template>
<script setup name="wechatThirdForm">
import { cloneDeep } from 'lodash-es'
import { required } from '@/utils/formRules'
import { message } from 'ant-design-vue'
import configApi from '@/api/dev/configApi'
const formRef = ref()
const formData = ref({})
const submitLoading = ref(false)
const loadSpinning = ref(true)
// 查询此界面的配置项,并转为表单
const param = {
category: 'THIRD_WECHAT_MINI'
}
configApi.configList(param).then((data) => {
loadSpinning.value = false
if (data) {
data.forEach((item) => {
formData.value[item.configKey] = transferBooleanInValue(item.configValue)
})
} else {
message.warning('表单项不存在,请初始化数据库')
}
})
// 转换值
const transferBooleanInValue = (value) => {
if (value === 'true' || value === 'false') {
return value === 'true'
} else {
return value
}
}
// 默认要校验的
const formRules = {
SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG: [required('请选择是否允许微信小程序登录')],
SNOWY_THIRD_WECHAT_MINI_CLIENT_ID: [required('请输入clientId')],
SNOWY_THIRD_WECHAT_MINI_CLIENT_SECRET: [required('请输入clientSecret')]
}
// 验证并提交数据
const onSubmit = () => {
formRef.value
.validate()
.then(() => {
submitLoading.value = true
let submitParam = cloneDeep(formData.value)
const param = Object.entries(submitParam).map((item) => {
return {
configKey: item[0],
configValue: item[1]
}
})
configApi
.configEditForm(param)
.then(() => {})
.finally(() => {
submitLoading.value = false
})
})
.catch(() => {})
}
const layout = {
labelCol: {
span: 24
},
wrapperCol: {
span: 12
}
}
</script>
@@ -65,6 +65,9 @@ public enum AuthPlatformEnum {
/** 微信 */
WECHAT("WECHAT"),
/** 微信小程序 */
WECHAT_MINI("WECHAT_MINI"),
/** 微博 */
WEIBO("WEIBO"),
@@ -94,6 +97,7 @@ public enum AuthPlatformEnum {
YUNZHIJIA.getValue().equals(value) ||
QQ.getValue().equals(value) ||
WECHAT.getValue().equals(value) ||
WECHAT_MINI.getValue().equals(value) ||
WEIBO.getValue().equals(value) ||
DOUYIN.getValue().equals(value) ||
ALIPAY.getValue().equals(value);
@@ -43,7 +43,7 @@ public class AuthClientFactory {
case JWT -> new AuthJwtClient(authSource, authPlatform);
case CAS -> new AuthCasClient(authSource, authPlatform);
case SAML -> new AuthSamlClient(authSource, authPlatform);
case OIDC, IAM, DINGTALK, WORKWECHAT, FEISHU, WELINK, YUNZHIJIA, QQ, WECHAT, WEIBO, DOUYIN, ALIPAY -> new AuthOidcClient(authSource, authPlatform);
case OIDC, IAM, DINGTALK, WORKWECHAT, FEISHU, WELINK, YUNZHIJIA, QQ, WECHAT, WECHAT_MINI, WEIBO, DOUYIN, ALIPAY -> new AuthOidcClient(authSource, authPlatform);
};
}
}
@@ -37,6 +37,7 @@ import vip.xiaonuo.auth.core.protocol.oidc.impl.feishu.AuthOidcFeiShuClient;
import vip.xiaonuo.auth.core.protocol.oidc.impl.iam.AuthOidcIamClient;
import vip.xiaonuo.auth.core.protocol.oidc.impl.qq.AuthOidcQqClient;
import vip.xiaonuo.auth.core.protocol.oidc.impl.wechat.AuthOidcWechatClient;
import vip.xiaonuo.auth.core.protocol.oidc.impl.wechatmini.AuthOidcWechatMiniClient;
import vip.xiaonuo.auth.core.protocol.oidc.impl.weibo.AuthOidcWeiboClient;
import vip.xiaonuo.auth.core.protocol.oidc.impl.welink.AuthOidcWeLinkClient;
import vip.xiaonuo.auth.core.protocol.oidc.impl.workwechat.AuthOidcWorkWechatClient;
@@ -71,9 +72,6 @@ public class AuthOidcClient extends AuthBaseClient<AuthOidcBaseJson> {
if(StrUtil.isBlank(authOidcBaseJson.getClientSecret())) {
throw new CommonException("ClientSecret不能为空");
}
if(StrUtil.isBlank(authOidcBaseJson.getCallbackUrl())) {
throw new CommonException("回调地址不能为空");
}
if(this.getAuthPlatform().equals(AuthPlatformEnum.OIDC.getValue())) {
if(StrUtil.isBlank(authOidcBaseJson.getAuthorizeUrl())) {
throw new CommonException("认证授权url不能为空");
@@ -107,6 +105,7 @@ public class AuthOidcClient extends AuthBaseClient<AuthOidcBaseJson> {
case IAM -> authRequest = new AuthOidcIamClient(getAuthBaseJson()).getAuthRequest();
case QQ -> authRequest = new AuthOidcQqClient(getAuthBaseJson()).getAuthRequest();
case WECHAT -> authRequest = new AuthOidcWechatClient(getAuthBaseJson()).getAuthRequest();
case WECHAT_MINI -> authRequest = new AuthOidcWechatMiniClient(getAuthBaseJson()).getAuthRequest();
case WEIBO -> authRequest = new AuthOidcWeiboClient(getAuthBaseJson()).getAuthRequest();
case DOUYIN -> authRequest = new AuthOidcDouyinClient(getAuthBaseJson()).getAuthRequest();
case ALIPAY -> authRequest = new AuthOidcAlipayClient(getAuthBaseJson()).getAuthRequest();
@@ -0,0 +1,45 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.auth.core.protocol.oidc.impl.wechatmini;
import com.xkcoding.http.HttpUtil;
import com.xkcoding.http.support.hutool.HutoolImpl;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.request.AuthWeChatOpenRequest;
import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
/**
* OIDC认证源微信小程序客户端
*
* @author xuyuxiang
* @date 2024/11/8 15:41
**/
@Slf4j
public record AuthOidcWechatMiniClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
HttpUtil.setHttp(new HutoolImpl());
return new AuthWechatMiniProgramRequest(AuthConfig.builder()
.clientId(clientId)
.clientSecret(clientSecret)
.ignoreCheckRedirectUri(true)
.ignoreCheckState(true)
.build());
}
}
@@ -144,6 +144,11 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
private static final String SNOWY_THIRD_WECHAT_CLIENT_SECRET_KEY = "SNOWY_THIRD_WECHAT_CLIENT_SECRET";
private static final String SNOWY_THIRD_WECHAT_REDIRECT_URL_KEY = "SNOWY_THIRD_WECHAT_REDIRECT_URL";
// 微信小程序
private static final String SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG_KEY = "SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG";
private static final String SNOWY_THIRD_WECHAT_MINI_CLIENT_ID_KEY = "SNOWY_THIRD_WECHAT_MINI_CLIENT_ID";
private static final String SNOWY_THIRD_WECHAT_MINI_CLIENT_SECRET_KEY = "SNOWY_THIRD_WECHAT_MINI_CLIENT_SECRET";
// 钉钉
private static final String SNOWY_THIRD_DINGTALK_ALLOW_LOGIN_FLAG_KEY = "SNOWY_THIRD_DINGTALK_ALLOW_LOGIN_FLAG";
private static final String SNOWY_THIRD_DINGTALK_CLIENT_ID_KEY = "SNOWY_THIRD_DINGTALK_CLIENT_ID";
@@ -515,6 +520,16 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
.set("clientSecret", devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_CLIENT_SECRET_KEY))
.set("callbackUrl", devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_REDIRECT_URL_KEY)));
}
if(platform.equals(AuthPlatformEnum.WECHAT_MINI.getValue())){
// 检查是否允许登录
if(!Boolean.parseBoolean(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG_KEY))) {
throw new CommonException("微信小程序登录已禁用");
}
// 微信小程序登录
authClient = AuthClientFactory.createClient(platform, JSONUtil.createObj()
.set("clientId", devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_MINI_CLIENT_ID_KEY))
.set("clientSecret", devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_MINI_CLIENT_SECRET_KEY)));
}
if(platform.equals(AuthPlatformEnum.DINGTALK.getValue())) {
// 检查是否允许登录
if(!Boolean.parseBoolean(devConfigApi.getValueByKey(SNOWY_THIRD_DINGTALK_ALLOW_LOGIN_FLAG_KEY))) {
@@ -434,6 +434,9 @@ INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755399', 'SNOWY_THIRD_ALIPAY_CLIE
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755400', 'SNOWY_THIRD_ALIPAY_CLIENT_SECRET', 'ALIPAY客户端SECRET', 'THIRD_ALIPAY', 'ALIPAY客户端SECRET', 273, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755401', 'SNOWY_THIRD_ALIPAY_PUBLIC_KEY', 'ALIPAY公钥', 'THIRD_ALIPAY', 'ALIPAY公钥', 274, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755402', 'SNOWY_THIRD_ALIPAY_REDIRECT_URL', '重定向URL', 'THIRD_ALIPAY', '重定向URL', 275, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755403', 'SNOWY_THIRD_WECHAT_MINI_ALLOW_LOGIN_FLAG', 'false', 'THIRD_WECHAT_MINI', '是否允许WECHAT_MINI登录', 276, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755404', 'SNOWY_THIRD_WECHAT_MINI_CLIENT_ID', 'WECHAT_MINI客户端ID', 'THIRD_WECHAT_MINI', 'WECHAT_MINI客户端ID', 277, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_CONFIG` VALUES ('1908870094824755405', 'SNOWY_THIRD_WECHAT_MINI_CLIENT_SECRET', 'WECHAT_MINI客户端SECRET', 'THIRD_WECHAT_MINI', 'WECHAT_MINI客户端SECRET', 278, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
-- ----------------------------
-- Table structure for DEV_DICT