【底座】采纳 #IJYGL1 建议,完善修复第三方登录“微信小程序”登录存在问题,同时优化state校验逻辑

This commit is contained in:
xuyuxiang
2026-07-06 00:42:57 +08:00
parent b768a53624
commit 081d0907c0
28 changed files with 291 additions and 265 deletions
@@ -171,20 +171,20 @@ public interface SaBaseLoginUserApi {
SaBaseClientLoginUser createClientUserWithEmail(String email);
/**
* 使用账号和密码创建B端用户
* 使用账号、密码和名称创建B端用户
*
* @author yubaoshan
* @date 2026/6/25
**/
SaBaseLoginUser createUserWithAccount(String account, String password);
SaBaseLoginUser createUserWithAccount(String account, String password, String name);
/**
* 使用账号和密码创建C端用户
* 使用账号、密码和名称创建C端用户
*
* @author yubaoshan
* @date 2026/6/26
**/
SaBaseClientLoginUser createClientUserWithAccount(String account, String password);
SaBaseClientLoginUser createClientUserWithAccount(String account, String password, String name);
/**
* 执行注册
@@ -25,62 +25,65 @@ import vip.xiaonuo.common.exception.CommonException;
public enum AuthPlatformEnum {
/** OAUTH */
OAUTH("OAUTH"),
OAUTH("OAUTH", "OAUTH"),
/** OIDC */
OIDC("OIDC"),
OIDC("OIDC", "OIDC"),
/** JWT */
JWT("JWT"),
JWT("JWT", "JWT"),
/** CAS */
CAS("CAS"),
CAS("CAS", "CAS"),
/** SAML */
SAML("SAML"),
SAML("SAML", "SAML"),
// =======以下为OIDC协议的具体实现======= //
/** IAM */
IAM("IAM"),
IAM("IAM", "IAM"),
/** 钉钉 */
DINGTALK("DINGTALK"),
DINGTALK("DINGTALK", "钉钉"),
/** 企业微信 */
WORKWECHAT("WORKWECHAT"),
WORKWECHAT("WORKWECHAT", "企业微信"),
/** 飞书 */
FEISHU("FEISHU"),
FEISHU("FEISHU", "飞书"),
/** WeLink */
WELINK("WELINK"),
WELINK("WELINK", "WeLink"),
/** 云之家 */
YUNZHIJIA("YUNZHIJIA"),
YUNZHIJIA("YUNZHIJIA", "云之家"),
/** QQ */
QQ("QQ"),
QQ("QQ", "QQ"),
/** 微信 */
WECHAT("WECHAT"),
WECHAT("WECHAT", "微信"),
/** 微信小程序 */
WECHAT_MINI("WECHAT_MINI"),
WECHAT_MINI("WECHAT_MINI", "微信小程序"),
/** 微博 */
WEIBO("WEIBO"),
WEIBO("WEIBO", "微博"),
/** 抖音 */
DOUYIN("DOUYIN"),
DOUYIN("DOUYIN", "抖音"),
/** 支付宝 */
ALIPAY("ALIPAY");
ALIPAY("ALIPAY", "支付宝");
private final String value;
AuthPlatformEnum(String value) {
private final String description;
AuthPlatformEnum(String value, String description) {
this.value = value;
this.description = description;
}
public static void validate(String value) {
@@ -101,11 +101,19 @@ public class AuthOauthClient extends AuthBaseClient<AuthOauthBaseJson> {
throw new CommonException("code不能为空");
}
String state = request.getParam("state");
if(ObjectUtil.isEmpty(state)) {
throw new CommonException("state不能为空");
}
AuthRequest authRequest = new AuthOauthCommonClient(getAuthBaseJson()).getAuthRequest();
AuthRequest authRequest = new AuthOauthCommonClient(getAuthBaseJson()).getAuthRequest(ObjectUtil.isEmpty(state));
AuthResponse<AuthUser> authResponse = authRequest.login(AuthCallback.builder().code(code).state(state).build());
// 如果失败了,且是因为 state 校验失败(Illegal state),则尝试降级忽略 state 校验再次登录
if(!authResponse.ok()) {
String errorMsg = authResponse.getMsg();
if(ObjectUtil.isNotEmpty(state) && errorMsg != null && errorMsg.contains("Illegal state")) {
log.warn(">>> OAUTH state校验失败,尝试降级忽略state校验登录,state={}, error={}", state, errorMsg);
authRequest = new AuthOauthCommonClient(getAuthBaseJson()).getAuthRequest(true);
authResponse = authRequest.login(AuthCallback.builder().code(code).state(state).build());
}
}
if(!authResponse.ok()) {
throw new CommonException(authResponse.getMsg());
}
@@ -29,6 +29,10 @@ import vip.xiaonuo.auth.core.protocol.oauth.AuthOauthBaseJson;
public record AuthOauthCommonClient(AuthOauthBaseJson authOauthBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOauthBaseJson.getClientId();
String clientSecret = authOauthBaseJson.getClientSecret();
String callbackUrl = authOauthBaseJson.getCallbackUrl();
@@ -37,6 +41,7 @@ public record AuthOauthCommonClient(AuthOauthBaseJson authOauthBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build(), authOauthBaseJson);
}
}
@@ -20,10 +20,8 @@ import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
@@ -98,23 +96,27 @@ public class AuthOidcClient extends AuthBaseClient<AuthOidcBaseJson> {
}
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String authPlatform = this.getAuthPlatform();
AuthPlatformEnum authPlatformEnum = AuthPlatformEnum.valueOf(authPlatform);
AuthRequest authRequest;
switch (authPlatformEnum) {
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();
case DINGTALK -> authRequest = new AuthOidcDingTalkClient(getAuthBaseJson()).getAuthRequest();
case WORKWECHAT -> authRequest = new AuthOidcWorkWechatClient(getAuthBaseJson()).getAuthRequest();
case FEISHU -> authRequest = new AuthOidcFeiShuClient(getAuthBaseJson()).getAuthRequest();
case WELINK -> authRequest = new AuthOidcWeLinkClient(getAuthBaseJson()).getAuthRequest();
case YUNZHIJIA -> authRequest = new AuthOidcYunZhiJiaClient(getAuthBaseJson()).getAuthRequest();
default -> authRequest = new AuthOidcCommonClient(getAuthBaseJson()).getAuthRequest();
case IAM -> authRequest = new AuthOidcIamClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case QQ -> authRequest = new AuthOidcQqClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case WECHAT -> authRequest = new AuthOidcWechatClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case WECHAT_MINI -> authRequest = new AuthOidcWechatMiniClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case WEIBO -> authRequest = new AuthOidcWeiboClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case DOUYIN -> authRequest = new AuthOidcDouyinClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case ALIPAY -> authRequest = new AuthOidcAlipayClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case DINGTALK -> authRequest = new AuthOidcDingTalkClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case WORKWECHAT -> authRequest = new AuthOidcWorkWechatClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case FEISHU -> authRequest = new AuthOidcFeiShuClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case WELINK -> authRequest = new AuthOidcWeLinkClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
case YUNZHIJIA -> authRequest = new AuthOidcYunZhiJiaClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
default -> authRequest = new AuthOidcCommonClient(getAuthBaseJson()).getAuthRequest(ignoreCheckState);
}
return authRequest;
}
@@ -143,63 +145,22 @@ public class AuthOidcClient extends AuthBaseClient<AuthOidcBaseJson> {
throw new CommonException("code不能为空");
}
String state = request.getParam("state");
if(ObjectUtil.isEmpty(state)) {
throw new CommonException("state不能为空");
}
AuthRequest authRequest = this.getAuthRequest();
AuthRequest authRequest = this.getAuthRequest(ObjectUtil.isEmpty(state));
AuthResponse<AuthUser> authResponse = authRequest.login(AuthCallback.builder().code(code).state(state).build());
// 尝试正常登录流程(包含state校验)
try {
AuthResponse<AuthUser> authResponse = authRequest.login(AuthCallback.builder().code(code).state(state).build());
// 检查响应是否失败
if(!authResponse.ok()) {
String errorMsg = authResponse.getMsg();
// 如果是state校验失败,尝试降级处理
if(errorMsg != null && errorMsg.contains("Illegal state")) {
log.warn(">>> OIDC state校验失败(响应失败),尝试跳过state校验直接获取token,state={}, error={}", state, errorMsg);
return doLoginWithoutStateCheck(code);
}
throw new CommonException(errorMsg);
// 如果失败了,且是因为 state 校验失败(Illegal state),则尝试降级忽略 state 校验再次登录
if(!authResponse.ok()) {
String errorMsg = authResponse.getMsg();
if(ObjectUtil.isNotEmpty(state) && errorMsg != null && errorMsg.contains("Illegal state")) {
log.warn(">>> OIDC state校验失败,尝试降级忽略state校验登录,state={}, error={}", state, errorMsg);
authRequest = this.getAuthRequest(true);
authResponse = authRequest.login(AuthCallback.builder().code(code).state(state).build());
}
return handleAuthResponse(authResponse);
} catch (AuthException e) {
// 如果是异常形式的state校验失败
if(e.getMessage() != null && e.getMessage().contains("Illegal state")) {
log.warn(">>> OIDC state校验失败(异常),尝试跳过state校验直接获取token,state={}, error={}", state, e.getMessage());
return doLoginWithoutStateCheck(code);
}
throw e;
}
}
/**
* 跳过state校验,直接用code换token和用户信息
*/
private AuthResponse<AuthUser> doLoginWithoutStateCheck(String code) {
try {
AuthRequest authRequest = this.getAuthRequest();
// 直接用code换token(不校验state)
AuthCallback authCallback = AuthCallback.builder()
.code(code)
.state("bypass-state-check") // 使用占位state
.build();
// 获取token
AuthToken authToken = authRequest.getAccessToken(authCallback);
// 获取用户信息
AuthUser authUser = authRequest.getUserInfo(authToken);
log.info(">>> OIDC跳过state校验成功获取用户信息,userId={}", authUser.getUuid());
return AuthResponse.<AuthUser>builder()
.code(me.zhyd.oauth.enums.AuthResponseStatus.SUCCESS.getCode())
.data(authUser)
.build();
} catch (Exception e) {
log.error(">>> OIDC跳过state校验后仍然失败", e);
throw new CommonException("OIDC登录失败:{}", e.getMessage());
if(!authResponse.ok()) {
throw new CommonException(authResponse.getMsg());
}
return handleAuthResponse(authResponse);
}
}
@@ -29,6 +29,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcCommonClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -37,6 +41,7 @@ public record AuthOidcCommonClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build(), authOidcBaseJson);
}
}
@@ -30,6 +30,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcAlipayClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -39,6 +43,7 @@ public record AuthOidcAlipayClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build(), publicKey);
}
}
@@ -31,6 +31,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcDingTalkClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -39,6 +43,7 @@ public record AuthOidcDingTalkClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -31,6 +31,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcDouyinClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -39,6 +43,7 @@ public record AuthOidcDouyinClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -31,6 +31,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcFeiShuClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -39,6 +43,7 @@ public record AuthOidcFeiShuClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -33,6 +33,10 @@ import java.util.List;
public record AuthOidcIamClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -43,6 +47,7 @@ public record AuthOidcIamClient(AuthOidcBaseJson authOidcBaseJson) {
.clientSecret(clientSecret)
.scopes(scope)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build(), authOidcBaseJson);
}
}
@@ -31,6 +31,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcQqClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -39,6 +43,7 @@ public record AuthOidcQqClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -31,6 +31,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcWechatClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -39,6 +43,7 @@ public record AuthOidcWechatClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -17,7 +17,6 @@ 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;
@@ -32,6 +31,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcWechatMiniClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(true);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
HttpUtil.setHttp(new HutoolImpl());
@@ -39,7 +42,7 @@ public record AuthOidcWechatMiniClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.ignoreCheckRedirectUri(true)
.ignoreCheckState(true)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -31,6 +31,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcWeiboClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -39,6 +43,7 @@ public record AuthOidcWeiboClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -30,6 +30,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcWeLinkClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -38,6 +42,7 @@ public record AuthOidcWeLinkClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -31,6 +31,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcWorkWechatClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -41,6 +45,7 @@ public record AuthOidcWorkWechatClient(AuthOidcBaseJson authOidcBaseJson) {
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.agentId(agentId)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -30,6 +30,10 @@ import vip.xiaonuo.auth.core.protocol.oidc.AuthOidcBaseJson;
public record AuthOidcYunZhiJiaClient(AuthOidcBaseJson authOidcBaseJson) {
public AuthRequest getAuthRequest() {
return getAuthRequest(false);
}
public AuthRequest getAuthRequest(boolean ignoreCheckState) {
String clientId = authOidcBaseJson.getClientId();
String clientSecret = authOidcBaseJson.getClientSecret();
String callbackUrl = authOidcBaseJson.getCallbackUrl();
@@ -38,6 +42,7 @@ public record AuthOidcYunZhiJiaClient(AuthOidcBaseJson authOidcBaseJson) {
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(callbackUrl)
.ignoreCheckState(ignoreCheckState)
.build());
}
}
@@ -30,5 +30,5 @@ public interface AuthThirdUserService extends IService<AuthThirdUser> {
* @author xuyuxiang
* @date 2022/7/9 14:58
*/
void insertAuthThirdUser(String userId, AuthUser authUser);
void insertAuthThirdUser(String userId, AuthUser authUser, String platform);
}
@@ -21,6 +21,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.druid.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -228,16 +229,20 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
// 校验登录端类型
String clientType = authThirdRenderParam.getClientType();
SaClientTypeEnum.validate(clientType);
// 创建客户端
AuthBaseClient<?> authSourceBaseClient = this.getAuthClient(platform);
// 获取认证地址,需客户端类型
String authorizeUrl = authSourceBaseClient.getAuthorizeUrl(clientType);
// 构造结果
AuthThirdRenderResult authThirdRenderResult = new AuthThirdRenderResult();
// 设置授权地址
authThirdRenderResult.setAuthorizeUrl(authorizeUrl);
// 返回结果
return authThirdRenderResult;
try {
// 创建客户端
AuthBaseClient<?> authSourceBaseClient = this.getAuthClient(platform);
// 获取认证地址,需客户端类型
String authorizeUrl = authSourceBaseClient.getAuthorizeUrl(clientType);
// 构造结果
AuthThirdRenderResult authThirdRenderResult = new AuthThirdRenderResult();
// 设置授权地址
authThirdRenderResult.setAuthorizeUrl(authorizeUrl);
// 返回结果
return authThirdRenderResult;
} catch (Exception e) {
throw new CommonException(e.getMessage());
}
}
@SuppressWarnings("ALL")
@@ -251,39 +256,33 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
AuthBaseClient<?> authSourceBaseClient = this.getAuthClient(platform);
// 获取state
String state = SaHolder.getRequest().getParam(SaOAuth2Consts.Param.state);
// 校验state
if(ObjectUtil.isEmpty(state)) {
state = SaHolder.getRequest().getParam("RelayState");
if(ObjectUtil.isEmpty(state)) {
throw new CommonException("state不能为空");
}
}
// 获取缓存操作类
CommonCacheOperator commonCacheOperator = SpringUtil.getBean(CommonCacheOperator.class);
// 获取缓存值
Object stateCacheValueObj = null;
try {
// 对state进行安全处理,防止特殊字符导致Redis异常
if (state.length() > 500) {
log.warn(">>> SSO state 过长({}字符),跳过Redis校验 state={}", state.length(), state.substring(0, 50) + "...");
} else {
stateCacheValueObj = commonCacheOperator.get(CONFIG_CACHE_KEY + state);
}
} catch (Exception e) {
log.warn(">>> SSO state Redis查询异常,跳过校验 state={}, error={}", state, e.getMessage());
}
// 默认登录端类型
String clientType = SaClientTypeEnum.B.getValue();
// 判断是否为空
if(ObjectUtil.isNotEmpty(stateCacheValueObj)){
// 转换为json对象
JSONObject stateCacheValueJsonObject = JSONUtil.parseObj(stateCacheValueObj);
// 获取登录端类型
clientType = stateCacheValueJsonObject.getStr("clientType");
// 移除缓存
commonCacheOperator.remove(CONFIG_CACHE_KEY + state);
} else {
log.warn(">>> SSO state 校验失败(可能IdP未原样返回或门户发起登录),跳过校验 state={}", state);
// 如果state不为空,尝试从缓存中获取clientType
if(ObjectUtil.isNotEmpty(state)) {
// 获取缓存操作类
CommonCacheOperator commonCacheOperator = SpringUtil.getBean(CommonCacheOperator.class);
// 获取缓存值
Object stateCacheValueObj = null;
try {
stateCacheValueObj = commonCacheOperator.get(CONFIG_CACHE_KEY + state);
} catch (Exception e) {
log.warn(">>> SSO state Redis查询异常,跳过校验 state={}, error={}", state, e.getMessage());
}
// 判断是否为空
if(ObjectUtil.isNotEmpty(stateCacheValueObj)){
// 转换为json对象
JSONObject stateCacheValueJsonObject = JSONUtil.parseObj(stateCacheValueObj);
// 获取登录端类型
clientType = stateCacheValueJsonObject.getStr("clientType");
// 移除缓存
commonCacheOperator.remove(CONFIG_CACHE_KEY + state);
} else {
log.warn(">>> SSO state 校验失败(可能未使用render发起登录),跳过校验 state={}", state);
}
}
// 执行请求
AuthResponse<AuthUser> authResponse = authSourceBaseClient.doLogin();
@@ -292,26 +291,27 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
AuthUser authUser = authResponse.getData();
// 获取第三方用户id
String uuid = authUser.getUuid();
// 获取第三方用户来源
String source = authUser.getSource();
// 根据第三方用户id和用户来源获取用户信息
// 根据第三方用户id和平台标识获取用户信息
AuthThirdUser authThirdUser = this.getOne(new LambdaQueryWrapper<AuthThirdUser>().eq(AuthThirdUser::getThirdId, uuid)
.eq(AuthThirdUser::getCategory, source));
.eq(AuthThirdUser::getCategory, platform));
// 定义系统用户id
String userId;
if(ObjectUtil.isEmpty(authThirdUser)) {
// 如果三方用户不存在,自动创建本地用户并绑定
userId = this.createAndBindUser(authUser, clientType);
userId = this.createAndBindUser(authUser, platform, clientType);
} else {
// 否则直接获取用户id,判断是否存在(有可能没绑定)
userId = authThirdUser.getUserId();
if(ObjectUtil.isEmpty(userId)) {
// 三方用户存在但未绑定本地用户,自动创建并绑定
userId = this.createAndBindUserForExistThird(authUser, authThirdUser, clientType);
userId = this.createAndBindUserForExistThird(authUser, authThirdUser, platform, clientType);
}
}
// 登录设备
String device = platform.equalsIgnoreCase(AuthPlatformEnum.WECHAT_MINI.getValue())?
AuthDeviceTypeEnum.MINI.getValue():AuthDeviceTypeEnum.PC.getValue();
// 已绑定用户,直接用userId登录
return authService.doLoginById(userId, AuthDeviceTypeEnum.PC.getValue(), clientType);
return authService.doLoginById(userId, device, clientType);
} else {
throw new CommonException("第三方登录授权回调失败,原因:{}", authResponse.getMsg());
}
@@ -359,26 +359,6 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
return this.page(CommonPageRequest.defaultPage(), queryWrapper);
}
/**
* 保存三方用户并返回主键
*
* @author xuyuxiang
* @date 2022/7/9 14:58
*/
private String insertAuthThirdUser(AuthUser authUser) {
AuthThirdUser authThirdUser = new AuthThirdUser();
authThirdUser.setThirdId(authUser.getUuid());
authThirdUser.setUserId(null);
authThirdUser.setAvatar(authUser.getAvatar());
authThirdUser.setName(authUser.getUsername());
authThirdUser.setNickname(authUser.getNickname());
authThirdUser.setGender(ObjectUtil.isNotEmpty(authUser.getGender())?authUser.getGender().getDesc():"");
authThirdUser.setCategory(authUser.getSource());
authThirdUser.setExtJson(JSONUtil.toJsonStr(authUser.getRawUserInfo()));
this.save(authThirdUser);
return authThirdUser.getId();
}
/**
* 创建授权请求客户端
*
@@ -665,53 +645,16 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
* 创建本地用户并绑定三方用户(三方用户记录不存在的情况)
*
* @param authUser 第三方用户信息
* @param platform 平台标识
* @param clientType 登录端类型
* @return 创建的本地用户ID
*
* @author yubaoshan
* @date 2025/06/26
*/
private String createAndBindUser(AuthUser authUser, String clientType) {
// 从第三方用户信息中提取字段
String username = authUser.getUsername();
String email = authUser.getEmail();
String phone = authUser.getSource().equalsIgnoreCase("phone") ? authUser.getUsername() : null;
// 定义本地用户
String userId;
// 根据登录端类型创建用户
if(SaClientTypeEnum.B.getValue().equals(clientType)) {
// B端用户创建逻辑
if(StrUtil.isNotBlank(phone)) {
userId = loginUserApi.createUserWithPhone(phone).getId();
} else if(StrUtil.isNotBlank(email)) {
userId = loginUserApi.createUserWithEmail(email).getId();
} else if(StrUtil.isNotBlank(username)) {
String randomPassword = RandomUtil.randomString(16);
userId = loginUserApi.createUserWithAccount(username, randomPassword).getId();
} else {
String uuid = authUser.getUuid();
String account = AuthAccountPrefixEnum.SSO.getValue() + (uuid.length() >= 8 ? uuid.substring(0, 8) : uuid);
String randomPassword = RandomUtil.randomString(16);
userId = loginUserApi.createUserWithAccount(account, randomPassword).getId();
}
} else {
// C端用户创建逻辑
if(StrUtil.isNotBlank(phone)) {
userId = clientLoginUserApi.createClientUserWithPhone(phone).getId();
} else if(StrUtil.isNotBlank(email)) {
userId = clientLoginUserApi.createClientUserWithEmail(email).getId();
} else if(StrUtil.isNotBlank(username)) {
String randomPassword = RandomUtil.randomString(16);
userId = clientLoginUserApi.createClientUserWithAccount(username, randomPassword).getId();
} else {
String uuid = authUser.getUuid();
String account = AuthAccountPrefixEnum.SSO.getValue() + (uuid.length() >= 8 ? uuid.substring(0, 8) : uuid);
String randomPassword = RandomUtil.randomString(16);
userId = clientLoginUserApi.createClientUserWithAccount(account, randomPassword).getId();
}
}
private String createAndBindUser(AuthUser authUser, String platform, String clientType) {
// 自动创建本地用户
String userId = this.autoCreateUser(authUser, platform, clientType);
// 插入三方用户记录并绑定
AuthThirdUser authThirdUser = new AuthThirdUser();
@@ -721,11 +664,11 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
authThirdUser.setName(authUser.getUsername());
authThirdUser.setNickname(authUser.getNickname());
authThirdUser.setGender(authUser.getGender() != null ? authUser.getGender().getDesc() : "未知");
authThirdUser.setCategory(authUser.getSource());
authThirdUser.setCategory(platform);
authThirdUser.setExtJson(JSONUtil.toJsonStr(authUser.getRawUserInfo()));
this.save(authThirdUser);
log.info(">>> SSO登录自动创建用户成功,userId={}, thirdId={}, source={}", userId, authUser.getUuid(), authUser.getSource());
log.info(">>> SSO登录自动创建用户成功,userId={}, thirdId={}, source={}", userId, authUser.getUuid(), platform);
return userId;
}
@@ -735,58 +678,86 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
*
* @param authUser 第三方用户信息
* @param authThirdUser 已存在的三方用户记录
* @param platform 平台标识
* @param clientType 登录端类型
* @return 创建的本地用户ID
*
* @author yubaoshan
* @date 2025/06/26
*/
private String createAndBindUserForExistThird(AuthUser authUser, AuthThirdUser authThirdUser, String clientType) {
// 从第三方用户信息中提取字段
String username = authUser.getUsername();
String email = authUser.getEmail();
String phone = authUser.getSource().equalsIgnoreCase("phone") ? authUser.getUsername() : null;
// 定义本地用户
String userId;
// 根据登录端类型创建用户
if(SaClientTypeEnum.B.getValue().equals(clientType)) {
// B端用户创建逻辑
if(StrUtil.isNotBlank(phone)) {
userId = loginUserApi.createUserWithPhone(phone).getId();
} else if(StrUtil.isNotBlank(email)) {
userId = loginUserApi.createUserWithEmail(email).getId();
} else if(StrUtil.isNotBlank(username)) {
String randomPassword = RandomUtil.randomString(16);
userId = loginUserApi.createUserWithAccount(username, randomPassword).getId();
} else {
String account = AuthAccountPrefixEnum.SSO.getValue() + (authUser.getUuid().length() >= 8 ? authUser.getUuid().substring(0, 8) : authUser.getUuid());
String randomPassword = RandomUtil.randomString(16);
userId = loginUserApi.createUserWithAccount(account, randomPassword).getId();
}
} else {
// C端用户创建逻辑
if(StrUtil.isNotBlank(phone)) {
userId = clientLoginUserApi.createClientUserWithPhone(phone).getId();
} else if(StrUtil.isNotBlank(email)) {
userId = clientLoginUserApi.createClientUserWithEmail(email).getId();
} else if(StrUtil.isNotBlank(username)) {
String randomPassword = RandomUtil.randomString(16);
userId = clientLoginUserApi.createClientUserWithAccount(username, randomPassword).getId();
} else {
String account = AuthAccountPrefixEnum.SSO.getValue() + (authUser.getUuid().length() >= 8 ? authUser.getUuid().substring(0, 8) : authUser.getUuid());
String randomPassword = RandomUtil.randomString(16);
userId = clientLoginUserApi.createClientUserWithAccount(account, randomPassword).getId();
}
}
private String createAndBindUserForExistThird(AuthUser authUser, AuthThirdUser authThirdUser, String platform, String clientType) {
// 自动创建本地用户
String userId = this.autoCreateUser(authUser, platform, clientType);
// 更新三方用户记录,绑定userId
authThirdUser.setUserId(userId);
this.updateById(authThirdUser);
log.info(">>> SSO登录为已存在三方用户创建本地用户并绑定成功,userId={}, thirdId={}, source={}", userId, authUser.getUuid(), authUser.getSource());
log.info(">>> SSO登录为已存在三方用户创建本地用户并绑定成功,userId={}, thirdId={}, source={}", userId, authUser.getUuid(), platform);
return userId;
}
/**
* 自动创建本地用户
*
* @author yubaoshan
* @date 2024/07/05
*/
private String autoCreateUser(AuthUser authUser, String platform, String clientType) {
String email = authUser.getEmail();
String phone = platform.equalsIgnoreCase("phone") ? authUser.getUsername() : null;
String userId;
// 生成账号:来源 + "_" + 完整UUID,保证全局唯一性,避免截取导致的冲突
String account = platform.toLowerCase() + "_" + authUser.getUuid();
// 生成美观的默认名称(昵称)
String name = authUser.getNickname();
if (StrUtil.isBlank(name)) {
name = authUser.getUsername();
}
if (StrUtil.isBlank(name)) {
name = getFriendlyPlatformName(platform) + "用户_" + RandomUtil.randomString(10);
}
if (SaClientTypeEnum.B.getValue().equals(clientType)) {
if (StrUtil.isNotBlank(phone)) {
userId = loginUserApi.createUserWithPhone(phone).getId();
} else if (StrUtil.isNotBlank(email)) {
userId = loginUserApi.createUserWithEmail(email).getId();
} else {
String randomPassword = RandomUtil.randomString(16);
userId = loginUserApi.createUserWithAccount(account, randomPassword, name).getId();
}
} else {
if (StrUtil.isNotBlank(phone)) {
userId = clientLoginUserApi.createClientUserWithPhone(phone).getId();
} else if (StrUtil.isNotBlank(email)) {
userId = clientLoginUserApi.createClientUserWithEmail(email).getId();
} else {
String randomPassword = RandomUtil.randomString(16);
userId = clientLoginUserApi.createClientUserWithAccount(account, randomPassword, name).getId();
}
}
return userId;
}
/**
* 获取友好的平台名称
*
* @author yubaoshan
* @date 2024/07/05
*/
private String getFriendlyPlatformName(String source) {
if (StrUtil.isBlank(source)) {
return "第三方";
}
for (AuthPlatformEnum platformEnum : AuthPlatformEnum.values()) {
if (platformEnum.getValue().equalsIgnoreCase(source)) {
return platformEnum.getDescription();
}
}
return source;
}
}
@@ -31,7 +31,7 @@ import vip.xiaonuo.auth.modular.third.service.AuthThirdUserService;
public class AuthThirdUserServiceImpl extends ServiceImpl<AuthThirdUserMapper, AuthThirdUser> implements AuthThirdUserService {
@Override
public void insertAuthThirdUser(String userId, AuthUser authUser) {
public void insertAuthThirdUser(String userId, AuthUser authUser, String platform) {
AuthThirdUser authThirdUser = new AuthThirdUser();
authThirdUser.setThirdId(authUser.getUuid());
authThirdUser.setUserId(userId);
@@ -39,7 +39,7 @@ public class AuthThirdUserServiceImpl extends ServiceImpl<AuthThirdUserMapper, A
authThirdUser.setName(authUser.getUsername());
authThirdUser.setNickname(authUser.getNickname());
authThirdUser.setGender(ObjectUtil.isEmpty(authUser.getGender()) ? null : authUser.getGender().getDesc());
authThirdUser.setCategory(authUser.getSource());
authThirdUser.setCategory(platform);
authThirdUser.setExtJson(JSONUtil.toJsonStr(authUser.getRawUserInfo()));
this.save(authThirdUser);
}
@@ -210,14 +210,14 @@ public class ClientLoginUserApiProvider implements SaBaseLoginUserApi {
}
@Override
public SaBaseLoginUser createUserWithAccount(String account, String password) {
public SaBaseLoginUser createUserWithAccount(String account, String password, String name) {
// C端用户API不实现B端用户创建
throw new UnsupportedOperationException("C端用户API不支持创建B端用户");
}
@Override
public SaBaseClientLoginUser createClientUserWithAccount(String account, String password) {
ClientUser clientUser = clientUserService.createUserWithAccount(account, password);
public SaBaseClientLoginUser createClientUserWithAccount(String account, String password, String name) {
ClientUser clientUser = clientUserService.createUserWithAccount(account, password, name);
return BeanUtil.copyProperties(clientUser, ClientLoginUser.class);
}
@@ -312,12 +312,12 @@ public interface ClientUserService extends IService<ClientUser> {
ClientUser createUserWithEmail(String email);
/**
* 根据账号密码创建用户
* 根据账号密码和名称创建用户
*
* @author xuyuxiang
* @date 2022/8/25 15:16
**/
ClientUser createUserWithAccount(String account, String password);
ClientUser createUserWithAccount(String account, String password, String name);
/**
* 判断当前用户密码是否过期
@@ -1041,10 +1041,10 @@ public class ClientUserServiceImpl extends ServiceImpl<ClientUserMapper, ClientU
@Transactional(rollbackFor = Exception.class)
@Override
public ClientUser createUserWithAccount(String account, String password) {
public ClientUser createUserWithAccount(String account, String password, String name) {
ClientUserAddParam clientUserAddParam = new ClientUserAddParam();
clientUserAddParam.setAccount(account);
clientUserAddParam.setName(account);
clientUserAddParam.setName(StrUtil.isBlank(name) ? account : name);
clientUserAddParam.setPassword(password);
clientUserAddParam.setGender(CommonGenderEnum.UNKNOWN.getValue());
// 保存用户
@@ -1151,7 +1151,7 @@ public class ClientUserServiceImpl extends ServiceImpl<ClientUserMapper, ClientU
// 校验密码
ClientPasswordUtl.validNewPassword(password);
// 根据账号密码创建用户
this.createUserWithAccount(account, password);
this.createUserWithAccount(account, password, null);
}
@Override
@@ -216,13 +216,13 @@ public class SysLoginUserApiProvider implements SaBaseLoginUserApi {
}
@Override
public SaBaseLoginUser createUserWithAccount(String account, String password) {
SysUser sysUser = sysUserService.createUserWithAccount(account, password);
public SaBaseLoginUser createUserWithAccount(String account, String password, String name) {
SysUser sysUser = sysUserService.createUserWithAccount(account, password, name);
return BeanUtil.copyProperties(sysUser, SysLoginUser.class);
}
@Override
public SaBaseClientLoginUser createClientUserWithAccount(String account, String password) {
public SaBaseClientLoginUser createClientUserWithAccount(String account, String password, String name) {
// B端用户API不实现C端用户创建
throw new UnsupportedOperationException("B端用户API不支持创建C端用户");
}
@@ -602,12 +602,12 @@ public interface SysUserService extends IService<SysUser> {
SysUser createUserWithEmail(String email);
/**
* 根据账号密码创建用户
* 根据账号密码和名称创建用户
*
* @author xuyuxiang
* @date 2022/8/25 15:16
**/
SysUser createUserWithAccount(String account, String password);
SysUser createUserWithAccount(String account, String password, String name);
/**
* 判断当前用户是否需要绑定手机号
@@ -2295,10 +2295,10 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
@Transactional(rollbackFor = Exception.class)
@Override
public SysUser createUserWithAccount(String account, String password) {
public SysUser createUserWithAccount(String account, String password, String name) {
SysUserAddParam sysUserAddParam = new SysUserAddParam();
sysUserAddParam.setAccount(account);
sysUserAddParam.setName(account);
sysUserAddParam.setName(StrUtil.isBlank(name) ? account : name);
sysUserAddParam.setPassword(password);
sysUserAddParam.setOrgId(this.getDefaultNewUserOrgId());
sysUserAddParam.setPositionId(this.getDefaultNewUserPositionId());
@@ -2458,7 +2458,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
// 校验密码
SysPasswordUtil.validNewPassword(password);
// 根据账号密码创建用户
this.createUserWithAccount(account, password);
this.createUserWithAccount(account, password, null);
}
@Override
@@ -603,8 +603,23 @@ INSERT INTO `DEV_DICT` VALUES ('1560342111344234497', '0', '定时任务状态',
INSERT INTO `DEV_DICT` VALUES ('1560342186812346370', '1560342111344234497', '运行', 'RUNNING', 'pink', 'DHhBaibuBk', 'FRM', 20, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1560342250096005121', '1560342111344234497', '停止', 'STOPPED', 'red', 'O6KfK7Cobx', 'FRM', 20, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595062998102017', '0', '三方用户分类', 'THIRD_CATEGORY', 'default', 'OqMe62ViBY', 'FRM', 18, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860545', '1561595062998102017', '山信通IAM', 'IAM', 'pink', 'lZq8s2ehA5', 'FRM', 10, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595322336112641', '1561595062998102017', '微信WECHAT', 'WECHAT_OPEN', 'red', 'pGhKCHQ7hF', 'FRM', 20, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860545', '1561595062998102017', 'OAUTH', 'OAUTH', 'pink', 'lZq8s2ehA5', 'FRM', 10, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860546', '1561595062998102017', 'OIDC', 'OIDC', 'red', 'pGhKCHQ7hF', 'FRM', 20, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860547', '1561595062998102017', 'JWT', 'JWT', 'orange', 'KDLmGWwMkJ', 'FRM', 30, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860548', '1561595062998102017', 'CAS', 'CAS', 'green', 'JMkb4b45Xd', 'FRM', 40, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860549', '1561595062998102017', 'SAML', 'SAML', 'cyan', 'Vewyfq4W15', 'FRM', 50, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860550', '1561595062998102017', 'IAM', 'IAM', 'blue', 'NqMe62ViBY', 'FRM', 60, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860551', '1561595062998102017', '钉钉', 'DINGTALK', 'purple', 'PqMe62ViBY', 'FRM', 70, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860552', '1561595062998102017', '企业微信', 'WORKWECHAT', 'gold', 'QqMe62ViBY', 'FRM', 80, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860553', '1561595062998102017', '飞书', 'FEISHU', 'pink', 'RqMe62ViBY', 'FRM', 90, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860554', '1561595062998102017', 'WeLink', 'WELINK', 'red', 'SqMe62ViBY', 'FRM', 100, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860555', '1561595062998102017', '云之家', 'YUNZHIJIA', 'orange', 'TqMe62ViBY', 'FRM', 110, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860556', '1561595062998102017', 'QQ', 'QQ', 'green', 'UqMe62ViBY', 'FRM', 120, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860557', '1561595062998102017', '微信', 'WECHAT', 'cyan', 'VqMe62ViBY', 'FRM', 130, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860558', '1561595062998102017', '微信小程序', 'WECHAT_MINI', 'blue', 'WqMe62ViBY', 'FRM', 140, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860559', '1561595062998102017', '微博', 'WEIBO', 'purple', 'XqMe62ViBY', 'FRM', 150, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860560', '1561595062998102017', '抖音', 'DOUYIN', 'gold', 'YqMe62ViBY', 'FRM', 160, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1561595252714860561', '1561595062998102017', '支付宝', 'ALIPAY', 'pink', 'ZqMe62ViBY', 'FRM', 170, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1567580351742619650', '0', '系统消息类型', 'MESSAGE_CATEGORY', 'default', 'UPIN4KJqvT', 'FRM', 19, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1567580424270524418', '1567580351742619650', '系统', 'SYS', 'pink', 'l7Mc5zE5ib', 'FRM', 10, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);
INSERT INTO `DEV_DICT` VALUES ('1567580487684206594', '1567580351742619650', '业务', 'BIZ', 'red', 'nYhpV3ep7j', 'FRM', 20, NULL, 'NOT_DELETE', NULL, NULL, NULL, NULL);