LAPTOP-SB56SG4Q\86185
3 years ago
22 changed files with 1423 additions and 1 deletions
Binary file not shown.
Binary file not shown.
@ -1,3 +1,6 @@ |
|||||||
# open-JSD-7508 |
# open-JSD-7508 |
||||||
|
|
||||||
JSD-7508 OAuth2 + 组织&用户同步 |
JSD-7508 OAuth2 + 组织&用户同步\ |
||||||
|
免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ |
||||||
|
仅作为开发者学习参考使用!禁止用于任何商业用途!\ |
||||||
|
为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。 |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,25 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<plugin> |
||||||
|
<id>com.fr.plugin.j7508.sso.auth</id> |
||||||
|
<name><![CDATA[jsd7508-单点登陆]]></name> |
||||||
|
<active>yes</active> |
||||||
|
<version>1.1.3</version> |
||||||
|
<env-version>10.0</env-version> |
||||||
|
<jartime>2018-07-31</jartime> |
||||||
|
<vendor>fr.open</vendor> |
||||||
|
<description><![CDATA[单点登陆jsd7508]]></description> |
||||||
|
<change-notes><![CDATA[单点登陆jsd7508]]></change-notes> |
||||||
|
<main-package>com.fr.plugin.j7508.sso</main-package> |
||||||
|
<prefer-packages> |
||||||
|
<prefer-package>com.fanruan.api</prefer-package> |
||||||
|
</prefer-packages> |
||||||
|
<lifecycle-monitor class="com.fr.plugin.j7508.sso.PluginMonitor"/> |
||||||
|
<extra-core> |
||||||
|
<LocaleFinder class="com.fr.plugin.j7508.sso.LocaleFinder"/> |
||||||
|
</extra-core> |
||||||
|
<extra-decision> |
||||||
|
<ControllerRegisterProvider class="com.fr.plugin.j7508.sso.request.UserControllerBridge"/> |
||||||
|
<GlobalRequestFilterProvider class="com.fr.plugin.j7508.sso.request.OAuthLogin"/> |
||||||
|
</extra-decision> |
||||||
|
<function-recorder class="com.fr.plugin.j7508.sso.LocaleFinder"/> |
||||||
|
</plugin> |
@ -0,0 +1,37 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2020 |
||||||
|
* Project: starter |
||||||
|
* FileName: LocaleFinder |
||||||
|
* Author: Louis |
||||||
|
* Date: 2020/8/31 22:19 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso; |
||||||
|
|
||||||
|
import com.fr.intelli.record.Focus; |
||||||
|
import com.fr.intelli.record.Original; |
||||||
|
import com.fr.record.analyzer.EnableMetrics; |
||||||
|
import com.fr.stable.fun.impl.AbstractLocaleFinder; |
||||||
|
|
||||||
|
import static com.fr.plugin.j7508.sso.config.SsoConfig.PLUGIN_ID; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <LocaleFinder> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
@EnableMetrics |
||||||
|
public class LocaleFinder extends AbstractLocaleFinder { |
||||||
|
|
||||||
|
@Override |
||||||
|
@Focus(id = PLUGIN_ID, text = "Plugin-J7508-Sso", source = Original.PLUGIN) |
||||||
|
public String find() { |
||||||
|
return "com/fr/plugin/j7508/sso/locale/lang"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int currentAPILevel() { |
||||||
|
return CURRENT_LEVEL; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: PluginMonitor |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/3/30 15:10 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso; |
||||||
|
|
||||||
|
import com.fanruan.api.log.LogKit; |
||||||
|
import com.fr.plugin.context.PluginContext; |
||||||
|
import com.fr.plugin.j7508.sso.config.SsoConfig; |
||||||
|
import com.fr.plugin.j7508.sso.helper.SsoUserScheduleHelper; |
||||||
|
import com.fr.plugin.observer.inner.AbstractPluginLifecycleMonitor; |
||||||
|
|
||||||
|
import static com.fr.plugin.j7508.sso.helper.SsoUserScheduleHelper.SSO_USER_SCHEDULE_SYN_MEMBER_GROUP; |
||||||
|
import static com.fr.plugin.j7508.sso.helper.SsoUserScheduleHelper.SSO_USER_SCHEDULE_SYN_MEMBER_JOB_NAME; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <PluginMonitor> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
public class PluginMonitor extends AbstractPluginLifecycleMonitor { |
||||||
|
public PluginMonitor() { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void afterRun(PluginContext pluginContext) { |
||||||
|
SsoConfig.getInstance(); |
||||||
|
this.reStartSchedule(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void beforeStop(PluginContext pluginContext) { |
||||||
|
SsoUserScheduleHelper.getInstance().stopSchedule(SSO_USER_SCHEDULE_SYN_MEMBER_JOB_NAME, SSO_USER_SCHEDULE_SYN_MEMBER_GROUP); |
||||||
|
} |
||||||
|
|
||||||
|
private void reStartSchedule() { |
||||||
|
try { |
||||||
|
String cronCondition = SsoConfig.getInstance().getCronCondition(); |
||||||
|
SsoUserScheduleHelper.getInstance().startSynMemberSchedule(cronCondition); |
||||||
|
} catch (Exception e) { |
||||||
|
LogKit.error(e.getMessage(), e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: DataResponse |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/3/19 11:46 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.bean; |
||||||
|
|
||||||
|
import com.fanruan.api.util.StringKit; |
||||||
|
import com.fr.decision.webservice.Response; |
||||||
|
import com.fr.third.fasterxml.jackson.annotation.JsonInclude; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <DataResponse> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
@JsonInclude(JsonInclude.Include.NON_DEFAULT) |
||||||
|
public class DataResponse extends Response { |
||||||
|
private static final long serialVersionUID = -6046189959382243612L; |
||||||
|
private String timestamp; |
||||||
|
private String code; |
||||||
|
private String msg; |
||||||
|
|
||||||
|
public DataResponse() { |
||||||
|
} |
||||||
|
|
||||||
|
private static DataResponse create() { |
||||||
|
return new DataResponse(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 相应success结果 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static DataResponse success() { |
||||||
|
return create().code("0").timeStamp(String.valueOf(System.currentTimeMillis())).message("success"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 报错结果 |
||||||
|
* |
||||||
|
* @param code |
||||||
|
* @param message |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public static DataResponse error(String code, String message) { |
||||||
|
return create().code(code).message(message).data(StringKit.EMPTY); |
||||||
|
} |
||||||
|
|
||||||
|
public DataResponse timeStamp(String timeStamp) { |
||||||
|
this.timestamp = timeStamp; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public DataResponse code(String code) { |
||||||
|
this.code = code; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public DataResponse message(String message) { |
||||||
|
this.msg = message; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public DataResponse data(Object data) { |
||||||
|
this.setData(data); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public String getTimestamp() { |
||||||
|
return timestamp; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTimestamp(String timestamp) { |
||||||
|
this.timestamp = timestamp; |
||||||
|
} |
||||||
|
|
||||||
|
public String getCode() { |
||||||
|
return code; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCode(String code) { |
||||||
|
this.code = code; |
||||||
|
} |
||||||
|
|
||||||
|
public String getMsg() { |
||||||
|
return msg; |
||||||
|
} |
||||||
|
|
||||||
|
public void setMsg(String msg) { |
||||||
|
this.msg = msg; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,107 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: SsoUserJobConstructor |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/4/21 15:58 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.bean; |
||||||
|
|
||||||
|
import com.fr.scheduler.job.FineScheduleJob; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <SsoUserJobConstructor> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
public class SsoUserJobConstructor { |
||||||
|
private String cron; |
||||||
|
private String jobName; |
||||||
|
private String jobGroup; |
||||||
|
private String triggerName; |
||||||
|
private String triggerGroup; |
||||||
|
private Class<? extends FineScheduleJob> jobClazz; |
||||||
|
|
||||||
|
public SsoUserJobConstructor() { |
||||||
|
} |
||||||
|
|
||||||
|
public SsoUserJobConstructor cron(String var1) { |
||||||
|
this.setCron(var1); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public String getCron() { |
||||||
|
return this.cron; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCron(String var1) { |
||||||
|
this.cron = var1; |
||||||
|
} |
||||||
|
|
||||||
|
public SsoUserJobConstructor jobName(String var1) { |
||||||
|
this.setJobName(var1); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public String getJobName() { |
||||||
|
return this.jobName; |
||||||
|
} |
||||||
|
|
||||||
|
public void setJobName(String var1) { |
||||||
|
this.jobName = var1; |
||||||
|
} |
||||||
|
|
||||||
|
public SsoUserJobConstructor jobGroup(String var1) { |
||||||
|
this.setJobGroup(var1); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public String getJobGroup() { |
||||||
|
return this.jobGroup; |
||||||
|
} |
||||||
|
|
||||||
|
public void setJobGroup(String var1) { |
||||||
|
this.jobGroup = var1; |
||||||
|
} |
||||||
|
|
||||||
|
public SsoUserJobConstructor triggerName(String var1) { |
||||||
|
this.setTriggerName(var1); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public String getTriggerName() { |
||||||
|
return this.triggerName; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTriggerName(String var1) { |
||||||
|
this.triggerName = var1; |
||||||
|
} |
||||||
|
|
||||||
|
public SsoUserJobConstructor triggerGroup(String var1) { |
||||||
|
this.setTriggerGroup(var1); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public String getTriggerGroup() { |
||||||
|
return this.triggerGroup; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTriggerGroup(String var1) { |
||||||
|
this.triggerGroup = var1; |
||||||
|
} |
||||||
|
|
||||||
|
public SsoUserJobConstructor jobClazz(Class<? extends FineScheduleJob> var1) { |
||||||
|
this.setJobClazz(var1); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Class<? extends FineScheduleJob> getJobClazz() { |
||||||
|
return this.jobClazz; |
||||||
|
} |
||||||
|
|
||||||
|
public void setJobClazz(Class<? extends FineScheduleJob> var1) { |
||||||
|
this.jobClazz = var1; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,122 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: OneAccessConfig |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/3/30 9:38 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.config; |
||||||
|
|
||||||
|
import com.fanruan.api.util.StringKit; |
||||||
|
import com.fr.config.*; |
||||||
|
import com.fr.config.holder.Conf; |
||||||
|
import com.fr.config.holder.factory.Holders; |
||||||
|
import com.fr.intelli.record.Focus; |
||||||
|
import com.fr.intelli.record.Original; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <SsoConfig> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
@Visualization(category = "Plugin-J7508-Sso_Group") |
||||||
|
public class SsoConfig extends DefaultConfiguration { |
||||||
|
public static final String PLUGIN_ID = "com.fr.plugin.j7508.sso.auth"; |
||||||
|
public static final String BASE_URI = "xxxx"; |
||||||
|
public static final String ESB_URI = "xxxx"; |
||||||
|
// 每天中午十二点触发
|
||||||
|
public static final String CRON_CONDITION = "0 0 12 * * ?"; |
||||||
|
|
||||||
|
private static volatile SsoConfig config = null; |
||||||
|
|
||||||
|
@Focus(id = PLUGIN_ID, text = "Plugin-J7508-Sso", source = Original.PLUGIN) |
||||||
|
public static SsoConfig getInstance() { |
||||||
|
if (config == null) { |
||||||
|
config = ConfigContext.getConfigInstance(SsoConfig.class); |
||||||
|
} |
||||||
|
return config; |
||||||
|
} |
||||||
|
|
||||||
|
@Identifier(value = "frUri", name = "Plugin-J7508-Sso_Config_FrUri", description = "Plugin-J7508-Sso_Config_FrUri_Description", status = Status.SHOW) |
||||||
|
private Conf<String> frUri = Holders.simple(StringKit.EMPTY); |
||||||
|
@Identifier(value = "uriBase", name = "Plugin-J7508-Sso_Config_UriBase", description = "Plugin-J7508-Sso_Config_UriBase_Description", status = Status.SHOW) |
||||||
|
private Conf<String> uriBase = Holders.simple(BASE_URI); |
||||||
|
@Identifier(value = "esbUri", name = "Plugin-J7508-Sso_Config_EsbUri", description = "Plugin-J7508-Sso_Config_EsbUri_Description", status = Status.SHOW) |
||||||
|
private Conf<String> esbUri = Holders.simple(ESB_URI); |
||||||
|
@Identifier(value = "clientId", name = "Plugin-J7508-Sso_Config_ClientId", description = "Plugin-J7508-Sso_Config_ClientId_Description", status = Status.SHOW) |
||||||
|
private Conf<String> clientId = Holders.simple(StringKit.EMPTY); |
||||||
|
@Identifier(value = "clientSecret", name = "Plugin-J7508-Sso_Config_ClientSecret", description = "Plugin-J7508-Sso_Config_ClientSecret_Description", status = Status.SHOW) |
||||||
|
private Conf<String> clientSecret = Holders.simple(StringKit.EMPTY); |
||||||
|
@Identifier(value = "cronCondition", name = "Plugin-J7508-Sso_Config_CronCondition", description = "Plugin-J7508-Sso_Config_CronCondition_Description", status = Status.SHOW) |
||||||
|
private Conf<String> cronCondition = Holders.simple(CRON_CONDITION); |
||||||
|
@Identifier(value = "appID", name = "Plugin-J7508-Sso_Config_AppID", description = "Plugin-J7508-Sso_Config_AppID_Description", status = Status.SHOW) |
||||||
|
private Conf<String> appID = Holders.simple(StringKit.EMPTY); |
||||||
|
@Identifier(value = "appSecret", name = "Plugin-J7508-Sso_Config_AppSecret", description = "Plugin-J7508-Sso_Config_AppSecret_Description", status = Status.SHOW) |
||||||
|
private Conf<String> appSecret = Holders.simple(StringKit.EMPTY); |
||||||
|
|
||||||
|
public String getFrUri() { |
||||||
|
return frUri.get(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setFrUri(String frUri) { |
||||||
|
this.frUri.set(frUri); |
||||||
|
} |
||||||
|
|
||||||
|
public String getUriBase() { |
||||||
|
return uriBase.get(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setUriBase(String uriBase) { |
||||||
|
this.uriBase.set(uriBase); |
||||||
|
} |
||||||
|
|
||||||
|
public String getEsbUri() { |
||||||
|
return esbUri.get(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setEsbUri(String esbUri) { |
||||||
|
this.esbUri.set(esbUri); |
||||||
|
} |
||||||
|
|
||||||
|
public String getClientId() { |
||||||
|
return clientId.get(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setClientId(String clientId) { |
||||||
|
this.clientId.set(clientId); |
||||||
|
} |
||||||
|
|
||||||
|
public String getClientSecret() { |
||||||
|
return clientSecret.get(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setClientSecret(String clientSecret) { |
||||||
|
this.clientSecret.set(clientSecret); |
||||||
|
} |
||||||
|
|
||||||
|
public String getCronCondition() { |
||||||
|
return cronCondition.get(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setCronCondition(String cronCondition) { |
||||||
|
this.cronCondition.set(cronCondition); |
||||||
|
} |
||||||
|
|
||||||
|
public String getAppID() { |
||||||
|
return appID.get(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setAppID(String appID) { |
||||||
|
this.appID.set(appID); |
||||||
|
} |
||||||
|
|
||||||
|
public String getAppSecret() { |
||||||
|
return appSecret.get(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setAppSecret(String appSecret) { |
||||||
|
this.appSecret.set(appSecret); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: DingTalkScheduleHelper |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/4/21 15:52 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.helper; |
||||||
|
|
||||||
|
import com.fr.plugin.j7508.sso.bean.SsoUserJobConstructor; |
||||||
|
import com.fr.plugin.j7508.sso.job.SsoUserSyncMemberJob; |
||||||
|
import com.fr.scheduler.ScheduleJobManager; |
||||||
|
import com.fr.third.v2.org.quartz.CronScheduleBuilder; |
||||||
|
import com.fr.third.v2.org.quartz.TriggerBuilder; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.TimeZone; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <DingTalkScheduleHelper> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
public class SsoUserScheduleHelper { |
||||||
|
public static final String SSO_USER_SCHEDULE_SYN_MEMBER_JOB_NAME = "MqhSsoUserSynDepMemberJob"; |
||||||
|
public static final String SSO_USER_SCHEDULE_SYN_MEMBER_TRIGGER_NAME = "MqhSsoUserSynDepMemberTrigger"; |
||||||
|
public static final String SSO_USER_SCHEDULE_SYN_MEMBER_GROUP = "MqhSsoUserSynDepMemberGroup"; |
||||||
|
public static final String SSO_USER_SCHEDULE_SYN_MEMBER_TRIGGER_GROUP = "MqhSsoUserSynDepMemberTriggerGroup"; |
||||||
|
|
||||||
|
private SsoUserScheduleHelper() { |
||||||
|
} |
||||||
|
|
||||||
|
public static SsoUserScheduleHelper getInstance() { |
||||||
|
return SsoUserScheduleHelper.HOLDER.INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
public void startSynMemberSchedule(String cronCondition) throws Exception { |
||||||
|
SsoUserJobConstructor jobConstructor = (new SsoUserJobConstructor()) |
||||||
|
.cron(cronCondition).jobName(SSO_USER_SCHEDULE_SYN_MEMBER_JOB_NAME) |
||||||
|
.jobGroup(SSO_USER_SCHEDULE_SYN_MEMBER_GROUP).triggerName(SSO_USER_SCHEDULE_SYN_MEMBER_TRIGGER_NAME) |
||||||
|
.triggerGroup(SSO_USER_SCHEDULE_SYN_MEMBER_TRIGGER_GROUP).jobClazz(SsoUserSyncMemberJob.class); |
||||||
|
this.startSchedule(jobConstructor); |
||||||
|
} |
||||||
|
|
||||||
|
public void startSchedule(SsoUserJobConstructor var1) throws Exception { |
||||||
|
if (var1 != null) { |
||||||
|
String var2 = var1.getCron(); |
||||||
|
String var3 = var1.getTriggerName(); |
||||||
|
String var4 = var1.getTriggerGroup(); |
||||||
|
String var5 = var1.getJobName(); |
||||||
|
String var6 = var1.getJobGroup(); |
||||||
|
Class var7 = var1.getJobClazz(); |
||||||
|
TriggerBuilder var8 = TriggerBuilder.newTrigger(); |
||||||
|
var8.withIdentity(var3, var4); |
||||||
|
var8.withSchedule(CronScheduleBuilder.cronSchedule(var2).withMisfireHandlingInstructionFireAndProceed().inTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()))).startNow(); |
||||||
|
var8.forJob(var5, var6); |
||||||
|
ArrayList var9 = new ArrayList(); |
||||||
|
var9.add(var8.build()); |
||||||
|
ScheduleJobManager.getInstance().removeJob(var5, var6); |
||||||
|
ScheduleJobManager.getInstance().addJob(var5, var6, "jobDescription", var7, var9, new HashMap()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void stopSchedule(String var1, String var2) { |
||||||
|
ScheduleJobManager.getInstance().removeJob(var1, var2); |
||||||
|
} |
||||||
|
|
||||||
|
public static class HOLDER { |
||||||
|
private static final SsoUserScheduleHelper INSTANCE = new SsoUserScheduleHelper(); |
||||||
|
|
||||||
|
public HOLDER() { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: SsoUserSyncMemberJob |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/4/21 16:02 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.job; |
||||||
|
|
||||||
|
import com.fanruan.api.log.LogKit; |
||||||
|
import com.fr.cluster.core.ClusterNode; |
||||||
|
import com.fr.plugin.j7508.sso.user.SsoUserManager; |
||||||
|
import com.fr.scheduler.job.FineScheduleJob; |
||||||
|
import com.fr.third.v2.org.quartz.JobExecutionContext; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <SsoUserSyncMemberJob> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
public class SsoUserSyncMemberJob extends FineScheduleJob { |
||||||
|
public SsoUserSyncMemberJob() { |
||||||
|
} |
||||||
|
|
||||||
|
public void run(JobExecutionContext jobExecutionContext, ClusterNode clusterNode) { |
||||||
|
try { |
||||||
|
SsoUserManager.getInstance().synSSODepartments(); |
||||||
|
} catch (Exception e) { |
||||||
|
LogKit.error(e.getMessage(), e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,96 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: DepartmentServiceKit |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/5/14 9:38 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.kit; |
||||||
|
|
||||||
|
import com.fanruan.api.i18n.I18nKit; |
||||||
|
import com.fanruan.api.util.StringKit; |
||||||
|
import com.fr.decision.authority.AuthorityContext; |
||||||
|
import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; |
||||||
|
import com.fr.decision.authority.data.Department; |
||||||
|
import com.fr.decision.record.OperateMessage; |
||||||
|
import com.fr.decision.webservice.exception.general.DuplicatedNameException; |
||||||
|
import com.fr.decision.webservice.v10.user.DepartmentService; |
||||||
|
import com.fr.general.ComparatorUtils; |
||||||
|
import com.fr.intelli.record.MetricRegistry; |
||||||
|
import com.fr.stable.StableUtils; |
||||||
|
import com.fr.stable.query.QueryFactory; |
||||||
|
import com.fr.stable.query.condition.QueryCondition; |
||||||
|
import com.fr.stable.query.restriction.Restriction; |
||||||
|
import com.fr.stable.query.restriction.RestrictionFactory; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import static com.fr.decision.authority.base.AuthorityConstants.DECISION_DEP_ROOT; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <DepartmentServiceKit> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
public class DepartmentServiceKit extends DepartmentService { |
||||||
|
private static volatile DepartmentServiceKit departmentServiceKit = null; |
||||||
|
|
||||||
|
public DepartmentServiceKit() { |
||||||
|
} |
||||||
|
|
||||||
|
public static DepartmentServiceKit getInstance() { |
||||||
|
if (departmentServiceKit == null) { |
||||||
|
departmentServiceKit = new DepartmentServiceKit(); |
||||||
|
} |
||||||
|
return departmentServiceKit; |
||||||
|
} |
||||||
|
|
||||||
|
public void addDepartment(String id, String pId, String depName) throws Exception { |
||||||
|
if (StringKit.equals(pId, DECISION_DEP_ROOT)) { |
||||||
|
pId = null; |
||||||
|
} |
||||||
|
this.checkDuplicatedDepartmentName(pId, depName); |
||||||
|
Department department = (new Department()).id(id).name(depName).parentId(pId).creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); |
||||||
|
AuthorityContext.getInstance().getDepartmentController().add(department); |
||||||
|
MetricRegistry.getMetric().submit(OperateMessage.build("Dec-Module-User_Manager", "Dec-Department", this.getDepartmentFullPath(pId, depName, "/"), "Dec-Log_Add")); |
||||||
|
} |
||||||
|
|
||||||
|
private void checkDuplicatedDepartmentName(String parentId, String depName) throws Exception { |
||||||
|
QueryCondition condition = QueryFactory.create().addRestriction(RestrictionFactory.and(new Restriction[]{RestrictionFactory.eq("name", depName), RestrictionFactory.eq("parentId", parentId)})); |
||||||
|
Department sameNameDep = AuthorityContext.getInstance().getDepartmentController().findOne(condition); |
||||||
|
if (sameNameDep != null) { |
||||||
|
throw new DuplicatedNameException(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String getDepartmentFullPath(String pId, String depName, String splitter) throws Exception { |
||||||
|
List<String> paths = new ArrayList<>(); |
||||||
|
paths.add(depName); |
||||||
|
while (!ComparatorUtils.equals(pId, DECISION_DEP_ROOT) && pId != null) { |
||||||
|
Department parentDepartment = AuthorityContext.getInstance().getDepartmentController().getById(pId); |
||||||
|
paths.add(parentDepartment.getName()); |
||||||
|
pId = parentDepartment.getParentId(); |
||||||
|
} |
||||||
|
Collections.reverse(paths); |
||||||
|
return StableUtils.join(paths.toArray(new String[0]), splitter); |
||||||
|
} |
||||||
|
|
||||||
|
public void editDepartment(String departmentId, String depName, String pId) throws Exception { |
||||||
|
if (StringKit.equals(pId, DECISION_DEP_ROOT)) { |
||||||
|
pId = null; |
||||||
|
} |
||||||
|
Department department = AuthorityContext.getInstance().getDepartmentController().getById(departmentId); |
||||||
|
String departmentFullPath = DepartmentService.getInstance().getDepartmentFullPath(departmentId); |
||||||
|
if (!ComparatorUtils.equals(department.getName(), depName)) { |
||||||
|
this.checkDuplicatedDepartmentName(department.getParentId(), depName); |
||||||
|
department.setName(depName); |
||||||
|
department.setParentId(pId); |
||||||
|
AuthorityContext.getInstance().getDepartmentController().update(department); |
||||||
|
} |
||||||
|
MetricRegistry.getMetric().submit(OperateMessage.build("Dec-Module-User_Manager", "Dec-Department", DepartmentService.getInstance().getDepartmentFullPath(departmentId), "Dec-Log_Update", I18nKit.getLocText("Fine-Dec_Department") + ":" + departmentFullPath)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,169 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: UserServiceKit |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/5/14 8:28 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.kit; |
||||||
|
|
||||||
|
import com.fanruan.api.log.LogKit; |
||||||
|
import com.fanruan.api.util.StringKit; |
||||||
|
import com.fr.decision.authority.AuthorityContext; |
||||||
|
import com.fr.decision.authority.data.Post; |
||||||
|
import com.fr.decision.authority.data.User; |
||||||
|
import com.fr.decision.authority.data.personnel.DepRole; |
||||||
|
import com.fr.decision.privilege.TransmissionTool; |
||||||
|
import com.fr.decision.webservice.bean.user.DepartmentPostBean; |
||||||
|
import com.fr.decision.webservice.bean.user.UserBean; |
||||||
|
import com.fr.decision.webservice.utils.UserSourceFactory; |
||||||
|
import com.fr.decision.webservice.utils.WebServiceUtils; |
||||||
|
import com.fr.decision.webservice.v10.user.PositionService; |
||||||
|
import com.fr.decision.webservice.v10.user.UserService; |
||||||
|
import com.fr.json.JSONObject; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.stable.collections.CollectionUtils; |
||||||
|
import com.fr.stable.query.QueryFactory; |
||||||
|
import com.fr.stable.query.restriction.RestrictionFactory; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <UserServiceKit> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
public class UserServiceKit extends UserService { |
||||||
|
private static volatile UserServiceKit userServiceKit = null; |
||||||
|
|
||||||
|
public UserServiceKit() { |
||||||
|
} |
||||||
|
|
||||||
|
public static UserServiceKit getInstance() { |
||||||
|
if (userServiceKit == null) { |
||||||
|
userServiceKit = new UserServiceKit(); |
||||||
|
} |
||||||
|
return userServiceKit; |
||||||
|
} |
||||||
|
|
||||||
|
public UserBean createUserBean(JSONObject account) throws Exception { |
||||||
|
UserBean userBean = new UserBean(); |
||||||
|
userBean.setUsername(account.getString("uid")); |
||||||
|
userBean.setRealName(account.getString("userName")); |
||||||
|
userBean.setEnable(StringKit.equals(account.getString("status"), "1")); |
||||||
|
userBean.setEmail(account.getString("email")); |
||||||
|
userBean.setMobile(account.getString("mobile")); |
||||||
|
userBean.setPassword(TransmissionTool.defaultEncrypt(account.getString("uid") + "123456")); |
||||||
|
if (StringKit.isNotBlank(account.getString("orgCode"))) { |
||||||
|
List<String> departmentPostIds = createDepartmentPostIds(account.getString("orgCode"), account.getString("jobTitle")); |
||||||
|
userBean.setDepartmentPostIds(departmentPostIds); |
||||||
|
} |
||||||
|
return userBean; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 转为部门职务组合 |
||||||
|
* |
||||||
|
* @param departmentPostId |
||||||
|
* @param title |
||||||
|
* @return |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
private List<String> createDepartmentPostIds(String departmentPostId, String title) throws Exception { |
||||||
|
List<String> departmentPostIds = new ArrayList<>(); |
||||||
|
if (StringKit.isBlank(departmentPostId) || StringKit.equals(departmentPostId, "null")) { |
||||||
|
return departmentPostIds; |
||||||
|
} |
||||||
|
String positionId = positionSynOperation(title, departmentPostId); |
||||||
|
if (StringKit.isNotBlank(positionId)) { |
||||||
|
departmentPostId = departmentPostId + "@@@" + positionId; |
||||||
|
} |
||||||
|
departmentPostIds.add(departmentPostId); |
||||||
|
return departmentPostIds; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 职务同步操作 |
||||||
|
* |
||||||
|
* @param title |
||||||
|
* @return |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
private String positionSynOperation(String title, String departmentId) throws Exception { |
||||||
|
String position = StringKit.isNotBlank(title) ? title : "职员"; |
||||||
|
Post post = AuthorityContext.getInstance().getPostController().findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("name", position))); |
||||||
|
String positionId; |
||||||
|
if (post == null) { |
||||||
|
positionId = PositionService.getInstance().addPosition(position, position); |
||||||
|
} else { |
||||||
|
positionId = post.getId(); |
||||||
|
} |
||||||
|
List<DepartmentPostBean> departmentPostBeanList = PositionService.getInstance().getPositionsUnderParentDepartment(getAdminUserId(), departmentId, position); |
||||||
|
if (departmentPostBeanList == null || departmentPostBeanList.isEmpty()) { |
||||||
|
try { |
||||||
|
AuthorityContext.getInstance().getPostController().addPostToDepartment(positionId, departmentId); |
||||||
|
} catch (Exception e) { |
||||||
|
LogKit.info("sso-UserServiceKit-positionSynOperation-addPostToDepartmentFailed-position:{}, departmentId:{}", positionId + position, departmentId); |
||||||
|
LogKit.error(e.getMessage(), e); |
||||||
|
} |
||||||
|
} |
||||||
|
return positionId; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取管理员id |
||||||
|
* |
||||||
|
* @return |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
public String getAdminUserId() throws Exception { |
||||||
|
List<String> adminUserIdList = UserService.getInstance().getAdminUserIdList(); |
||||||
|
if (adminUserIdList.isEmpty()) { |
||||||
|
return "admin"; |
||||||
|
} |
||||||
|
return StringKit.isNotBlank(adminUserIdList.get(0)) ? adminUserIdList.get(0) : "admin"; |
||||||
|
} |
||||||
|
|
||||||
|
public UserBean updateUserBean(JSONObject account) throws Exception { |
||||||
|
User user = UserService.getInstance().getUserByUserName(account.getString("uid")); |
||||||
|
if (user == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
UserBean userBean = new UserBean(); |
||||||
|
userBean.setId(user.getId()); |
||||||
|
userBean.setUsername(user.getUserName()); |
||||||
|
userBean.setRealName(account.getString("userName")); |
||||||
|
userBean.setEnable(StringKit.equals(account.getString("status"), "1")); |
||||||
|
userBean.setEmail(account.getString("email")); |
||||||
|
userBean.setMobile(account.getString("mobile")); |
||||||
|
if (StringKit.isNotBlank(account.getString("orgCode"))) { |
||||||
|
List<String> departmentPostIds = createDepartmentPostIds(account.getString("orgCode"), account.getString("jobTitle")); |
||||||
|
userBean.setDepartmentPostIds(departmentPostIds); |
||||||
|
} |
||||||
|
return userBean; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 增加用户部门关联 |
||||||
|
* |
||||||
|
* @param userBean |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
public void addUserDepartment(UserBean userBean) throws Exception { |
||||||
|
if (CollectionUtils.isEmpty(userBean.getDepartmentPostIds())) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (String departmentPostId : userBean.getDepartmentPostIds()) { |
||||||
|
if (StringUtils.isEmpty(departmentPostId)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
User user = UserService.getInstance().getUserByUserName(userBean.getUsername()); |
||||||
|
DepRole depRole = WebServiceUtils.parseUniqueDepartmentPostId(departmentPostId); |
||||||
|
UserSourceFactory.getInstance().checkSource(user, AuthorityContext.getInstance().getDepartmentController().getById(depRole.getDepartmentId())); |
||||||
|
AuthorityContext.getInstance().getUserController().addUserToDepartmentAndPost(user.getId(), depRole.getDepartmentId(), depRole.getPostId()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,221 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: OAuthLogin |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/3/30 22:09 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.request; |
||||||
|
|
||||||
|
import com.fanruan.api.decision.login.LoginKit; |
||||||
|
import com.fanruan.api.decision.user.UserKit; |
||||||
|
import com.fanruan.api.log.LogKit; |
||||||
|
import com.fanruan.api.net.NetworkKit; |
||||||
|
import com.fanruan.api.net.http.HttpKit; |
||||||
|
import com.fanruan.api.util.StringKit; |
||||||
|
import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; |
||||||
|
import com.fr.decision.webservice.utils.DecisionServiceConstants; |
||||||
|
import com.fr.decision.webservice.v10.login.LoginService; |
||||||
|
import com.fr.json.JSONObject; |
||||||
|
import com.fr.plugin.j7508.sso.config.SsoConfig; |
||||||
|
import com.fr.third.org.apache.http.client.utils.URIBuilder; |
||||||
|
|
||||||
|
import javax.servlet.FilterChain; |
||||||
|
import javax.servlet.FilterConfig; |
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
import java.io.IOException; |
||||||
|
import java.net.URISyntaxException; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <OAuthLogin> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
public class OAuthLogin extends AbstractGlobalRequestFilterProvider { |
||||||
|
public static final String REMOTE_DESIGN = "/remote/design"; |
||||||
|
public static final String RESOURCES_PATH = "/resources"; |
||||||
|
public static final String FILE_PATH = "/file"; |
||||||
|
public static final String SYSTEM_INFO = "/system/info"; |
||||||
|
public static final String MATERIALS_MIN_JS_MAP = "/materials.min.js.map"; |
||||||
|
public static final String LOGIN_PATH = "/login"; |
||||||
|
public static final String LOGIN_OTHER = "/login/"; |
||||||
|
public static final String LOGOUT_PATH = "/logout"; |
||||||
|
public static final String USER_LANGUAGE = "/v10/user/language"; |
||||||
|
public static final String ACCOUNT_IAMPUSH = "/account/iamPush"; |
||||||
|
|
||||||
|
public static final String CODE_URL = "/esc-sso/oauth2.0/authorize"; |
||||||
|
public static final String TOKEN_URL = "/esc-sso/oauth2.0/accessToken"; |
||||||
|
public static final String USER_URL = "/esc-sso/oauth2.0/profile"; |
||||||
|
public static final String CODE = "code"; |
||||||
|
|
||||||
|
private SsoConfig config; |
||||||
|
|
||||||
|
/** |
||||||
|
* 过滤器名称 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String filterName() { |
||||||
|
return "J7508Filter"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 过滤规则 |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public String[] urlPatterns() { |
||||||
|
return new String[]{"/*"}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 过滤器初始化 |
||||||
|
* |
||||||
|
* @param filterConfig |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void init(FilterConfig filterConfig) { |
||||||
|
this.config = SsoConfig.getInstance(); |
||||||
|
super.init(filterConfig); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 过滤器处理 |
||||||
|
* |
||||||
|
* @param request |
||||||
|
* @param response |
||||||
|
* @param filterChain |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { |
||||||
|
try { |
||||||
|
if (operation(request, response)) { |
||||||
|
filterChain.doFilter(request, response); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
LogKit.error(e.getMessage(), e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 用户验证登陆操作 |
||||||
|
* |
||||||
|
* @param req |
||||||
|
* @param res |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
private boolean operation(HttpServletRequest req, HttpServletResponse res) throws Exception { |
||||||
|
String pathInfo = (req.getPathInfo() != null) ? req.getPathInfo() : StringKit.EMPTY; |
||||||
|
LogKit.info("sso-OAuthLogin-operation-pathInfo:{}", pathInfo); |
||||||
|
if (pathInfo.startsWith(REMOTE_DESIGN) || pathInfo.startsWith(LOGIN_OTHER) |
||||||
|
|| StringKit.equals(LOGIN_PATH, pathInfo) || StringKit.equals(ACCOUNT_IAMPUSH, pathInfo) |
||||||
|
|| pathInfo.startsWith(RESOURCES_PATH) || pathInfo.startsWith(LOGOUT_PATH) |
||||||
|
|| pathInfo.startsWith(SYSTEM_INFO) || pathInfo.startsWith(MATERIALS_MIN_JS_MAP) |
||||||
|
|| pathInfo.startsWith(USER_LANGUAGE) || pathInfo.startsWith(FILE_PATH)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
// 已登录
|
||||||
|
if (LoginService.getInstance().isLogged(req)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
String code = NetworkKit.getHTTPRequestParameter(req, CODE); |
||||||
|
LogKit.info("sso-OAuthLogin-operation-code:{}", code); |
||||||
|
if (StringKit.isBlank(code)) { |
||||||
|
res.sendRedirect(getLoginUrl(req)); |
||||||
|
return false; |
||||||
|
} |
||||||
|
String accessToken = getAccessToken(code); |
||||||
|
if (StringKit.isEmpty(accessToken)) { |
||||||
|
res.sendRedirect(getLoginUrl(req)); |
||||||
|
return false; |
||||||
|
} |
||||||
|
String username = getUsername(accessToken); |
||||||
|
if (StringKit.isEmpty(username) || !UserKit.existUsername(username)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
String tokenFR = LoginKit.login(req, res, username); |
||||||
|
req.setAttribute(DecisionServiceConstants.FINE_AUTH_TOKEN_NAME, tokenFR); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 通过凭证获得username |
||||||
|
* |
||||||
|
* @param accessToken |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private String getUsername(String accessToken) throws IOException { |
||||||
|
Map<String, String> userInfoParams = new HashMap<>(); |
||||||
|
userInfoParams.put("access_token", accessToken); |
||||||
|
String userRes = HttpKit.get(this.config.getUriBase() + USER_URL, userInfoParams); |
||||||
|
LogKit.info("sso-OAuthLogin-getUsername-userRes:{}", userRes); |
||||||
|
return new JSONObject(userRes).getString("id"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取access_token |
||||||
|
* |
||||||
|
* @param code |
||||||
|
* @return |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
private String getAccessToken(String code) throws Exception { |
||||||
|
Map<String, String> params = new HashMap<>(); |
||||||
|
params.put("grant_type", "authorization_code"); |
||||||
|
params.put("oauth_timestamp", String.valueOf(System.currentTimeMillis())); |
||||||
|
params.put("client_id", this.config.getClientId()); |
||||||
|
params.put("client_secret", this.config.getClientSecret()); |
||||||
|
params.put("code", code); |
||||||
|
params.put("redirect_uri", this.config.getFrUri()); |
||||||
|
String url = this.config.getUriBase() + TOKEN_URL; |
||||||
|
String res = HttpKit.post(url, params); |
||||||
|
LogKit.info("sso-OAuthLogin-getAccessToken-res:{}", res); |
||||||
|
if (StringKit.isEmpty(res)) { |
||||||
|
return StringKit.EMPTY; |
||||||
|
} |
||||||
|
String token = new JSONObject(res).getString("access_token"); |
||||||
|
if (StringKit.isNotBlank(token)) { |
||||||
|
return token; |
||||||
|
} |
||||||
|
return StringKit.EMPTY; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取login_url |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private String getLoginUrl(HttpServletRequest request) { |
||||||
|
String url = SsoConfig.getInstance().getUriBase() + CODE_URL; |
||||||
|
Map<String, String> params = new HashMap<>(); |
||||||
|
params.put("client_id", SsoConfig.getInstance().getClientId()); |
||||||
|
params.put("response_type", "code"); |
||||||
|
params.put("redirect_uri", this.config.getFrUri()); |
||||||
|
String loginUrl = buildUrl(url, params); |
||||||
|
LogKit.info("sso-OAuthLogin-getLoginUrl-loginUrl:{}", loginUrl); |
||||||
|
return loginUrl; |
||||||
|
} |
||||||
|
|
||||||
|
private String buildUrl(String url, Map<String, String> params) { |
||||||
|
if (params == null || params.isEmpty()) { |
||||||
|
return url; |
||||||
|
} |
||||||
|
try { |
||||||
|
URIBuilder builder = new URIBuilder(url); |
||||||
|
for (Map.Entry<String, String> entry : params.entrySet()) { |
||||||
|
builder.setParameter(entry.getKey(), entry.getValue()); |
||||||
|
} |
||||||
|
return builder.build().toString(); |
||||||
|
} catch (URISyntaxException e) { |
||||||
|
LogKit.debug("Error to build url, please check the arguments."); |
||||||
|
return url; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: UserControllerBridge |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/3/29 22:30 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.request; |
||||||
|
|
||||||
|
import com.fr.decision.fun.impl.AbstractControllerRegisterProvider; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <UserControllerBridge> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
public class UserControllerBridge extends AbstractControllerRegisterProvider { |
||||||
|
@Override |
||||||
|
public Class[] getControllers() { |
||||||
|
return new Class[]{ |
||||||
|
UserPushController.class |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: UserPushController |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/3/29 22:36 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.request; |
||||||
|
|
||||||
|
import com.fanruan.api.decision.user.UserKit; |
||||||
|
import com.fanruan.api.log.LogKit; |
||||||
|
import com.fr.decision.authority.data.User; |
||||||
|
import com.fr.decision.webservice.annotation.LoginStatusChecker; |
||||||
|
import com.fr.decision.webservice.bean.user.UserBean; |
||||||
|
import com.fr.decision.webservice.v10.user.UserService; |
||||||
|
import com.fr.json.JSONArray; |
||||||
|
import com.fr.json.JSONObject; |
||||||
|
import com.fr.plugin.j7508.sso.bean.DataResponse; |
||||||
|
import com.fr.plugin.j7508.sso.config.SsoConfig; |
||||||
|
import com.fr.plugin.j7508.sso.kit.UserServiceKit; |
||||||
|
import com.fr.third.springframework.stereotype.Controller; |
||||||
|
import com.fr.third.springframework.web.bind.annotation.RequestBody; |
||||||
|
import com.fr.third.springframework.web.bind.annotation.RequestMapping; |
||||||
|
import com.fr.third.springframework.web.bind.annotation.RequestMethod; |
||||||
|
import com.fr.third.springframework.web.bind.annotation.ResponseBody; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <UserPushController> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
@Controller |
||||||
|
@RequestMapping("account/iamPush") |
||||||
|
public class UserPushController { |
||||||
|
private SsoConfig config; |
||||||
|
private String adminName; |
||||||
|
|
||||||
|
public UserPushController() { |
||||||
|
this.config = SsoConfig.getInstance(); |
||||||
|
} |
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.POST) |
||||||
|
@ResponseBody |
||||||
|
@LoginStatusChecker(required = false) |
||||||
|
public DataResponse doAction(@RequestBody(required = false) String bodyContent, HttpServletResponse res) { |
||||||
|
try { |
||||||
|
LogKit.info("sso-UserPushController-doAction-bodyContent:{}", bodyContent); |
||||||
|
JSONObject bodyJson = new JSONObject(bodyContent); |
||||||
|
JSONArray accounts = bodyJson.getJSONArray("data"); |
||||||
|
this.adminName = UserService.getInstance().getAdminUserNameList().get(0); |
||||||
|
setHeader(res); |
||||||
|
operation(accounts); |
||||||
|
return DataResponse.success(); |
||||||
|
} catch (Exception e) { |
||||||
|
LogKit.error(e.getMessage(), e); |
||||||
|
return DataResponse.error("1", "error"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 企业应用业务事件处理 |
||||||
|
* |
||||||
|
* @param accounts |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private void operation(JSONArray accounts) throws Exception { |
||||||
|
for (int i = 0; i < accounts.size(); i++) { |
||||||
|
JSONObject account = accounts.getJSONObject(i); |
||||||
|
userSynOperation(account); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 用户同步操作 |
||||||
|
* |
||||||
|
* @param account |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
private void userSynOperation(JSONObject account) throws Exception { |
||||||
|
String userId = account.getString("uid"); |
||||||
|
UserBean userBean; |
||||||
|
if (UserKit.existUsername(userId)) { |
||||||
|
userBean = UserServiceKit.getInstance().updateUserBean(account); |
||||||
|
if (userBean == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
UserService.getInstance().editUser(userBean); |
||||||
|
UserService.getInstance().forbidUser(userBean.getId(), userBean.isEnable()); |
||||||
|
UserService.getInstance().updateUserDepartmentPost(UserServiceKit.getInstance().getAdminUserId(), userBean); |
||||||
|
} else { |
||||||
|
userBean = UserServiceKit.getInstance().createUserBean(account); |
||||||
|
try { |
||||||
|
UserService.getInstance().addUser(userBean); |
||||||
|
User user = UserService.getInstance().getUserByUserName(userBean.getUsername()); |
||||||
|
UserService.getInstance().forbidUser(user.getId(), userBean.isEnable()); |
||||||
|
} catch (Exception e) { |
||||||
|
LogKit.error("sso-UserPushController-userSynOperation-Username:{}, RealName:{}, Mobile:{}, Email:{}", |
||||||
|
userBean.getUsername(), userBean.getRealName(), userBean.getMobile(), userBean.getEmail()); |
||||||
|
LogKit.error(e.getMessage(), e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 解决跨域访问问题 |
||||||
|
* |
||||||
|
* @param res |
||||||
|
*/ |
||||||
|
private void setHeader(HttpServletResponse res) { |
||||||
|
// 跨域设置header
|
||||||
|
res.setHeader("Access-Control-Allow-Origin", "*"); |
||||||
|
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); |
||||||
|
res.setHeader("Access-Control-Max-Age", "3600"); |
||||||
|
res.setHeader("Access-Control-Allow-Headers", "x-requested-with"); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,158 @@ |
|||||||
|
/* |
||||||
|
* Copyright (C), 2018-2021 |
||||||
|
* Project: starter |
||||||
|
* FileName: SsoUserManager |
||||||
|
* Author: Louis |
||||||
|
* Date: 2021/4/21 16:18 |
||||||
|
*/ |
||||||
|
package com.fr.plugin.j7508.sso.user; |
||||||
|
|
||||||
|
import com.auth0.jwt.JWT; |
||||||
|
import com.auth0.jwt.JWTCreator; |
||||||
|
import com.auth0.jwt.algorithms.Algorithm; |
||||||
|
import com.fanruan.api.log.LogKit; |
||||||
|
import com.fanruan.api.net.http.HttpKit; |
||||||
|
import com.fanruan.api.util.StringKit; |
||||||
|
import com.fr.decision.authority.AuthorityContext; |
||||||
|
import com.fr.decision.authority.data.Department; |
||||||
|
import com.fr.decision.base.util.UUIDUtil; |
||||||
|
import com.fr.json.JSONArray; |
||||||
|
import com.fr.json.JSONObject; |
||||||
|
import com.fr.plugin.j7508.sso.config.SsoConfig; |
||||||
|
import com.fr.plugin.j7508.sso.kit.DepartmentServiceKit; |
||||||
|
import com.fr.plugin.j7508.sso.utils.SignatureUtil; |
||||||
|
import com.fr.third.org.apache.http.entity.StringEntity; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.UUID; |
||||||
|
|
||||||
|
import static com.fr.decision.authority.base.AuthorityConstants.DECISION_DEP_ROOT; |
||||||
|
|
||||||
|
/** |
||||||
|
* <Function Description><br> |
||||||
|
* <SsoUserManager> |
||||||
|
* |
||||||
|
* @author fr.open |
||||||
|
* @since 1.0.0 |
||||||
|
*/ |
||||||
|
public final class SsoUserManager { |
||||||
|
public static final String ORG_LIST_ALL = "/esb/organization/listAll/api"; |
||||||
|
private SsoConfig config; |
||||||
|
|
||||||
|
public SsoUserManager() { |
||||||
|
this.config = SsoConfig.getInstance(); |
||||||
|
} |
||||||
|
|
||||||
|
private static class HOLDER { |
||||||
|
private static final SsoUserManager INSTANCE = new SsoUserManager(); |
||||||
|
} |
||||||
|
public static SsoUserManager getInstance() { |
||||||
|
return HOLDER.INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 同步更新部门 |
||||||
|
* |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
public synchronized void synSSODepartments() throws Exception { |
||||||
|
LogKit.info("sso-SsoUserManager-synSSODepartments-start"); |
||||||
|
// 同步部门和用户信息
|
||||||
|
departmentSynLoop(); |
||||||
|
LogKit.info("sso-SsoUserManager-synSSODepartments-end"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 按部门遍历子部门并同步人员信息 |
||||||
|
* |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
private void departmentSynLoop() throws IOException { |
||||||
|
JSONArray departmentList = getDepartmentList(); |
||||||
|
// 同步部门信息
|
||||||
|
for (int i = 0; i < departmentList.size(); i++) { |
||||||
|
try { |
||||||
|
departmentSynOperation(departmentList.optJSONObject(i)); |
||||||
|
} catch (Exception e) { |
||||||
|
LogKit.error(e.getMessage(), e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 部门组织的新增更新操作 |
||||||
|
* |
||||||
|
* @param departmentJo |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
private void departmentSynOperation(JSONObject departmentJo) throws Exception { |
||||||
|
LogKit.info("sso-SsoUserManager-departmentSynOperation-departmentJo:{}", departmentJo.encode()); |
||||||
|
String departmentId = departmentJo.getString("orgCode"); |
||||||
|
if(StringKit.equals(departmentJo.getString("status"),"1")) { |
||||||
|
String parentId = departmentJo.getString("parentCode"); |
||||||
|
if (StringKit.isBlank(parentId)) { parentId = DECISION_DEP_ROOT; } |
||||||
|
String depName = departmentJo.getString("orgName"); |
||||||
|
Department department = AuthorityContext.getInstance().getDepartmentController().getById(departmentId); |
||||||
|
if (department == null) { |
||||||
|
DepartmentServiceKit.getInstance().addDepartment(departmentId, parentId, depName); |
||||||
|
} else { |
||||||
|
DepartmentServiceKit.getInstance().editDepartment(department.getId(), depName, parentId); |
||||||
|
} |
||||||
|
} |
||||||
|
if (StringKit.equals(departmentJo.getString("status"), "0")) { |
||||||
|
DepartmentServiceKit.getInstance().deleteDepartment(departmentId); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 通过接口获取部门列表 |
||||||
|
* |
||||||
|
* @return |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
private JSONArray getDepartmentList() throws IOException { |
||||||
|
Map<String, String> headers = new HashMap<>(); |
||||||
|
headers.put("Content-Type", "application/json;charset=UTF-8"); |
||||||
|
headers.put("requestId", UUIDUtil.generate()); |
||||||
|
headers.put("sourceSystem", "BI"); |
||||||
|
headers.put("serviceName", "S_0004_syncOrgnizeAll_S"); |
||||||
|
JSONObject params = JSONObject.create(); |
||||||
|
params.put("size", "3000"); |
||||||
|
params.put("page", "1"); |
||||||
|
params.put("startTime", StringKit.EMPTY); |
||||||
|
StringEntity stringEntity = new StringEntity(params.encode(), "UTF-8"); |
||||||
|
String response = HttpKit.executeAndParse(com.fanruan.api.net.http.rs.HttpRequest.custom() |
||||||
|
.url(this.config.getEsbUri() + ORG_LIST_ALL).post(stringEntity).headers(headers).build()); |
||||||
|
LogKit.info("sso-SsoUserManager-getDepartmentList-response:{}", response); |
||||||
|
JSONObject responseJo = new JSONObject(response); |
||||||
|
if (StringKit.equals(responseJo.getString("code"), "0")) { |
||||||
|
return responseJo.getJSONObject("data").getJSONArray("list"); |
||||||
|
} |
||||||
|
return JSONArray.create(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 产生JWT token |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private String getJWTToken() { |
||||||
|
Map<String, String> map = new HashMap<>(); |
||||||
|
map.put("timestamp", System.currentTimeMillis() + ""); |
||||||
|
map.put("noncestr", UUID.randomUUID().toString()); |
||||||
|
map.put("secretKey", this.config.getAppSecret()); |
||||||
|
String sign = SignatureUtil.getSign(map); |
||||||
|
JWTCreator.Builder builder = JWT.create().withAudience(this.config.getAppID()); |
||||||
|
map.remove("secretKey"); |
||||||
|
for (Map.Entry<String, String> entry : map.entrySet()) { |
||||||
|
builder.withClaim(entry.getKey(), entry.getValue()); |
||||||
|
} |
||||||
|
String token = builder.sign(Algorithm.HMAC256(sign)); |
||||||
|
if (StringKit.isNotBlank(token)) { |
||||||
|
return token; |
||||||
|
} |
||||||
|
return StringKit.EMPTY; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
package com.fr.plugin.j7508.sso.utils; |
||||||
|
|
||||||
|
import com.fanruan.api.util.StringKit; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
public class SignatureUtil { |
||||||
|
public static String getSign(Map<String, String> sortedParams) { |
||||||
|
StringBuilder content = new StringBuilder(); |
||||||
|
List<String> keys = new ArrayList<>(sortedParams.keySet()); |
||||||
|
Collections.sort(keys); |
||||||
|
int index = 0; |
||||||
|
Iterator<String> var4 = keys.iterator(); |
||||||
|
while (var4.hasNext()) { |
||||||
|
String key = var4.next(); |
||||||
|
String value = sortedParams.get(key); |
||||||
|
if (areNotEmpty(new String[] { key, value })) { |
||||||
|
content.append((index == 0) ? "" : "&").append(key).append("=").append(value); |
||||||
|
index++; |
||||||
|
} |
||||||
|
} |
||||||
|
return content.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
public static boolean areNotEmpty(String... values) { |
||||||
|
boolean result = true; |
||||||
|
if (values != null && values.length != 0) { |
||||||
|
String[] var2 = values; |
||||||
|
int var3 = values.length; |
||||||
|
for (int var4 = 0; var4 < var3; var4++) { |
||||||
|
String value = var2[var4]; |
||||||
|
result &= StringKit.isNotEmpty(value); |
||||||
|
} |
||||||
|
} else { |
||||||
|
result = false; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
Plugin-J7508-Sso=Sso Plugin |
||||||
|
Plugin-J7508-Sso_Group=Sso Plugin |
||||||
|
Plugin-J7508-Sso_Config_FrUri=Fr Uri |
||||||
|
Plugin-J7508-Sso_Config_FrUri_Description=Fr Uri |
||||||
|
Plugin-J7508-Sso_Config_UriBase=Base Uri |
||||||
|
Plugin-J7508-Sso_Config_UriBase_Description=Base Uri |
||||||
|
Plugin-J7508-Sso_Config_EsbUri=Esb Uri |
||||||
|
Plugin-J7508-Sso_Config_EsbUri_Description=Esb Uri |
||||||
|
Plugin-J7508-Sso_Config_ClientId=Client Id |
||||||
|
Plugin-J7508-Sso_Config_ClientId_Description=Client Id |
||||||
|
Plugin-J7508-Sso_Config_ClientSecret=Client Secret |
||||||
|
Plugin-J7508-Sso_Config_ClientSecret_Description=Client Secret |
||||||
|
Plugin-J7508-Sso_Config_CronCondition=Cron Condition |
||||||
|
Plugin-J7508-Sso_Config_CronCondition_Description=Cron Condition |
||||||
|
Plugin-J7508-Sso_Config_AppID=App ID |
||||||
|
Plugin-J7508-Sso_Config_AppID_Description=App ID |
||||||
|
Plugin-J7508-Sso_Config_AppSecret=App Secret |
||||||
|
Plugin-J7508-Sso_Config_AppSecret_Description=App Secret |
@ -0,0 +1,18 @@ |
|||||||
|
Plugin-J7508-Sso=\u5355\u70B9\u767B\u9646\u63D2\u4EF6 |
||||||
|
Plugin-J7508-Sso_Group=\u5355\u70B9\u767B\u9646\u63D2\u4EF6 |
||||||
|
Plugin-J7508-Sso_Config_FrUri=\u51B3\u7B56\u7CFB\u7EDFURL |
||||||
|
Plugin-J7508-Sso_Config_FrUri_Description=\u51B3\u7B56\u7CFB\u7EDFURL |
||||||
|
Plugin-J7508-Sso_Config_UriBase=\u767B\u9646\u8BA4\u8BC1\u63A5\u53E3 |
||||||
|
Plugin-J7508-Sso_Config_UriBase_Description=\u767B\u9646\u8BA4\u8BC1\u63A5\u53E3 |
||||||
|
Plugin-J7508-Sso_Config_EsbUri=\u7EC4\u7EC7\u4FE1\u606F\u63A5\u53E3 |
||||||
|
Plugin-J7508-Sso_Config_EsbUri_Description=\u7EC4\u7EC7\u4FE1\u606F\u63A5\u53E3 |
||||||
|
Plugin-J7508-Sso_Config_ClientId=\u5E94\u7528\u6CE8\u518CID |
||||||
|
Plugin-J7508-Sso_Config_ClientId_Description=\u5E94\u7528\u6CE8\u518CID |
||||||
|
Plugin-J7508-Sso_Config_ClientSecret=\u5E94\u7528\u6CE8\u518C\u5BC6\u7801 |
||||||
|
Plugin-J7508-Sso_Config_ClientSecret_Description=\u5E94\u7528\u6CE8\u518C\u5BC6\u7801 |
||||||
|
Plugin-J7508-Sso_Config_CronCondition=Cron\u8868\u8FBE\u5F0F |
||||||
|
Plugin-J7508-Sso_Config_CronCondition_Description=Cron\u8868\u8FBE\u5F0F |
||||||
|
Plugin-J7508-Sso_Config_AppID=IAM\u7533\u8BF7\u88AB\u63A8App ID |
||||||
|
Plugin-J7508-Sso_Config_AppID_Description=idm\u5E94\u7528\u914D\u7F6E\u7533\u8BF7\u7684\u88AB\u63A8App ID |
||||||
|
Plugin-J7508-Sso_Config_AppSecret=IAM\u7533\u8BF7\u88AB\u63A8App Secret |
||||||
|
Plugin-J7508-Sso_Config_AppSecret_Description=idm\u5E94\u7528\u914D\u7F6E\u7533\u8BF7\u7684\u88AB\u63A8App Secret |
Loading…
Reference in new issue