diff --git a/JSD-8928-需求文档V1.docx b/JSD-8928-需求文档V1.docx
new file mode 100644
index 0000000..27277a0
Binary files /dev/null and b/JSD-8928-需求文档V1.docx differ
diff --git a/README.md b/README.md
index 9514bdb..05b8526 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
# open-JSD-8928
-JSD-8928 ADFS( saml模式 )单点
\ No newline at end of file
+JSD-8928 ADFS( saml模式 )单点\
+免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\
+仅作为开发者学习参考使用!禁止用于任何商业用途!\
+为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。
\ No newline at end of file
diff --git a/doc/JSD-8928配置使用文档.docx b/doc/JSD-8928配置使用文档.docx
new file mode 100644
index 0000000..7b9ae36
Binary files /dev/null and b/doc/JSD-8928配置使用文档.docx differ
diff --git a/plugin.xml b/plugin.xml
new file mode 100644
index 0000000..9f2c91a
--- /dev/null
+++ b/plugin.xml
@@ -0,0 +1,21 @@
+
+
+ com.fr.plugin.xxxx.tpyy.adfs
+
+ yes
+ 1.2
+ 10.0
+ 2019-03-10
+ fr.open
+
+
+ [2021-11-10]【1.2】增加授权
+ [2021-11-08]【1.1】增加授权
+ [2021-10-29]【1.0】初始化插件。
+
+
+
+
+
+
+
diff --git a/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/Constants.java b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/Constants.java
new file mode 100644
index 0000000..afa0c5e
--- /dev/null
+++ b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/Constants.java
@@ -0,0 +1,9 @@
+package com.fr.plugin.xxxx.tpyy.adfs;
+
+/**
+ * @author fr.open
+ * @date 2020/11/11
+ */
+public class Constants {
+ public static final String PLUGIN_ID = "com.fr.plugin.xxxx.tpyy.adfs";
+}
diff --git a/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/CustomLogInOutEventProvider.java b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/CustomLogInOutEventProvider.java
new file mode 100644
index 0000000..42cd703
--- /dev/null
+++ b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/CustomLogInOutEventProvider.java
@@ -0,0 +1,19 @@
+package com.fr.plugin.xxxx.tpyy.adfs;
+
+import com.fr.decision.fun.impl.AbstractLogInOutEventProvider;
+import com.fr.decision.webservice.login.LogInOutResultInfo;
+import com.fr.general.PropertiesUtils;
+import com.fr.log.FineLoggerFactory;
+
+/**
+ * @author fr.open
+ * @date 2020/11/11
+ */
+public class CustomLogInOutEventProvider extends AbstractLogInOutEventProvider {
+ @Override
+ public String logoutAction(LogInOutResultInfo result) {
+ String logout = PropertiesUtils.getProperties("conf").getProperty("logout");
+ FineLoggerFactory.getLogger().info("get logout is {}",logout);
+ return logout;
+ }
+}
diff --git a/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/DesECBUtil.java b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/DesECBUtil.java
new file mode 100644
index 0000000..5c431cb
--- /dev/null
+++ b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/DesECBUtil.java
@@ -0,0 +1,65 @@
+package com.fr.plugin.xxxx.tpyy.adfs;
+
+import com.fr.third.org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.Key;
+
+/**
+ * @author fr.open
+ * @date 2020/11/11
+ */
+public class DesECBUtil {
+ /**
+ * 加密数据
+ *
+ * @param encryptString
+ * @param encryptKey
+ * @return
+ * @throws Exception
+ */
+ public static String encryptDES(String encryptString, String encryptKey) throws Exception {
+ Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(getKey(encryptKey), "DES"));
+ byte[] encryptedData = cipher.doFinal(encryptString.getBytes("UTF-8"));
+ return Base64.encodeBase64String(encryptedData);
+ }
+
+ /**
+ * key 不足8位补位
+ *
+ * @param
+ */
+ public static byte[] getKey(String keyRule) {
+ Key key = null;
+ byte[] keyByte = keyRule.getBytes();
+ // 创建一个空的八位数组,默认情况下为0
+ byte[] byteTemp = new byte[8];
+ // 将用户指定的规则转换成八位数组
+ for (int i = 0; i < byteTemp.length && i < keyByte.length; i++) {
+ byteTemp[i] = keyByte[i];
+ }
+ key = new SecretKeySpec(byteTemp, "DES");
+ return key.getEncoded();
+ }
+
+ /***
+ * 解密数据
+ * @param decryptString
+ * @param decryptKey
+ * @return
+ * @throws Exception
+ */
+
+ public static String decryptDES(String decryptString, String decryptKey) throws Exception {
+ byte[] sourceBytes = Base64.decodeBase64(decryptString);
+ Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
+ cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(getKey(decryptKey), "DES"));
+ byte[] decoded = cipher.doFinal(sourceBytes);
+ return new String(decoded, "UTF-8");
+
+ }
+
+}
+
diff --git a/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/KeysUtil.java b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/KeysUtil.java
new file mode 100644
index 0000000..3cf01fa
--- /dev/null
+++ b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/KeysUtil.java
@@ -0,0 +1,115 @@
+package com.fr.plugin.xxxx.tpyy.adfs;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import java.io.UnsupportedEncodingException;
+import java.security.SecureRandom;
+
+/**
+ * @author fr.open
+ * @date 2020/11/11
+ */
+public class KeysUtil {
+ /** 加密解密KEY */
+
+ /** 加密解密算法 */
+ private final static String ALGORITHM = "DES";
+ /**
+ * 加密
+ * @param data 未加密数据
+ * @param key 加密key
+ * @return
+ * @throws Exception
+ */
+ public static String encrypt(String data, String key) throws Exception {
+
+ byte[] dataBytes = data.getBytes("UTF-8");
+
+ byte[] keyBytes = key.getBytes("UTF-8");
+
+ // DES算法要求有一个可信任的随机数源
+ SecureRandom sr = new SecureRandom();
+
+ // 从原始密匙数据创建DESKeySpec对象
+ DESKeySpec dks = new DESKeySpec(keyBytes);
+
+ // 创建一个密匙工厂,然后用它把DESKeySpec转换成
+ // 一个SecretKey对象
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
+ SecretKey securekey = keyFactory.generateSecret(dks);
+
+ // Cipher对象实际完成加密操作
+ Cipher cipher = Cipher.getInstance(ALGORITHM);
+
+ // 用密匙初始化Cipher对象
+ cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
+
+ // 正式执行加密操作
+ byte[] bytes = cipher.doFinal(dataBytes);
+ return byte2hex(bytes);
+ }
+
+ /**
+ * 解密
+ * @param data 加密后的数据
+ * @param key 加密key
+ * @return
+ * @throws Exception
+ */
+ public static String decrypt(String data, String key) throws Exception {
+
+ byte[] dataBytes = data.getBytes("UTF-8");
+ byte[] hex2byte = hex2byte(dataBytes);
+ byte[] keyBytes = key.getBytes("UTF-8");
+
+ // DES算法要求有一个可信任的随机数源
+ SecureRandom sr = new SecureRandom();
+
+ // 从原始密匙数据创建一个DESKeySpec对象
+ DESKeySpec dks = new DESKeySpec(keyBytes);
+
+ // 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
+ // 一个SecretKey对象
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
+ SecretKey securekey = keyFactory.generateSecret(dks);
+
+ // Cipher对象实际完成解密操作
+ Cipher cipher = Cipher.getInstance(ALGORITHM);
+
+ // 用密匙初始化Cipher对象
+ cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
+
+ // 正式执行解密操作
+ byte[] doFinal = cipher.doFinal(hex2byte);
+
+ return new String(doFinal,"UTF-8");
+ }
+
+ private static byte[] hex2byte(byte[] b) throws UnsupportedEncodingException {
+ if ((b.length % 2) != 0) {
+ throw new IllegalArgumentException("长度不是偶数");
+ }
+ byte[] b2 = new byte[b.length / 2];
+ for (int n = 0; n < b.length; n += 2) {
+ String item = new String(b, n, 2, "UTF-8");
+ b2[n / 2] = (byte) Integer.parseInt(item, 16);
+ }
+ return b2;
+ }
+
+ private static String byte2hex(byte[] b) {
+ StringBuilder bulid = new StringBuilder();
+ String stmp = "";
+ for (int n = 0; n < b.length; n++) {
+ stmp = (Integer.toHexString(b[n] & 0XFF));
+ if (stmp.length() == 1) {
+ bulid.append("0").append(stmp);
+ } else {
+ bulid.append(stmp);
+ }
+ }
+ return bulid.toString().toUpperCase();
+ }
+}
diff --git a/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/SsoFilter.java b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/SsoFilter.java
new file mode 100644
index 0000000..7cac8fb
--- /dev/null
+++ b/src/main/java/com/fr/plugin/xxxx/tpyy/adfs/SsoFilter.java
@@ -0,0 +1,225 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain
+ * a copy of the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an
+ * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
+ * or implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ *
+ * The Original Code is OIOSAML Java Service Provider.
+ *
+ * The Initial Developer of the Original Code is Trifork A/S. Portions
+ * created by Trifork A/S are Copyright (C) 2008 Danish National IT
+ * and Telecom Agency (http://www.itst.dk). All Rights Reserved.
+ *
+ * Contributor(s):
+ * Joakim Recht
+ * Rolf Njor Jensen
+ * Aage Nielsen
+ *
+ */
+package com.fr.plugin.xxxx.tpyy.adfs;
+
+import com.fr.data.NetworkHelper;
+import com.fr.decision.authority.data.User;
+import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider;
+import com.fr.decision.mobile.terminal.TerminalHandler;
+import com.fr.decision.webservice.utils.DecisionServiceConstants;
+import com.fr.decision.webservice.utils.WebServiceUtils;
+import com.fr.decision.webservice.v10.config.ConfigService;
+import com.fr.decision.webservice.v10.login.LoginService;
+import com.fr.decision.webservice.v10.login.TokenResource;
+import com.fr.decision.webservice.v10.user.UserService;
+import com.fr.general.PropertiesUtils;
+import com.fr.intelli.record.Focus;
+import com.fr.intelli.record.Original;
+import com.fr.locale.InterProviderFactory;
+import com.fr.log.FineLoggerFactory;
+import com.fr.plugin.context.PluginContexts;
+import com.fr.plugin.transform.FunctionRecorder;
+import com.fr.record.analyzer.EnableMetrics;
+import com.fr.stable.StringUtils;
+import com.fr.stable.fun.Authorize;
+import com.fr.stable.web.Device;
+import com.fr.web.utils.WebUtils;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author fr.open
+ * @date 2020/11/11
+ */
+@FunctionRecorder
+@Authorize(callSignKey = Constants.PLUGIN_ID)
+@EnableMetrics
+public class SsoFilter extends AbstractGlobalRequestFilterProvider {
+
+ @Override
+ public String filterName() {
+ return "tpyy";
+ }
+
+ @Override
+ @Focus(id = Constants.PLUGIN_ID, text = "adfs", source = Original.PLUGIN)
+ public String[] urlPatterns() {
+ if (PluginContexts.currentContext().isAvailable()) {
+ String servletPathName = "decision";
+ try {
+ servletPathName = ConfigService.getInstance().getBasicParam().getServletPathName();
+ } catch (Exception e) {
+ FineLoggerFactory.getLogger().error(e.getMessage(), e);
+ }
+ return new String[]{
+ "/" + servletPathName,
+ "/" + servletPathName + "/view/report",
+
+ };
+ } else {
+ return new String[0];
+ }
+ }
+
+ @Override
+ public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) {
+ String samlToken = req.getParameter("samlToken");
+ try {
+ if (isAccept(req) || isLogin(req)) {
+ next(req, res, filterChain);
+ return;
+ }
+ if (StringUtils.isNotBlank(samlToken)) {
+ String key = PropertiesUtils.getProperties("conf").getProperty("key");
+ FineLoggerFactory.getLogger().info("get key config is {}", key);
+ String format = DesECBUtil.decryptDES(samlToken, key);
+ FineLoggerFactory.getLogger().info("decode format is {}", format);
+ String user = format.split("_")[0];
+ String timeout = format.split("_")[1];
+ String out = PropertiesUtils.getProperties("conf").getProperty("timeout");
+ FineLoggerFactory.getLogger().info("get timeout config is {}", out);
+ if (StringUtils.isBlank(out)) {
+ out = "10";
+ }
+ if (Instant.ofEpochMilli(Long.valueOf(timeout)).plusSeconds(Long.valueOf(out)).isBefore(Instant.now())) {
+ setError(res, "format timeout!");
+ return;
+ }
+ if (!checkUser(user)) {
+ setError(res, "user: 【" + user + "】 not exist !");
+ return;
+ }
+ loginFromToken(req, res, user);
+ }
+ } catch (Exception e) {
+ FineLoggerFactory.getLogger().error(e.getMessage(), e);
+ }
+ FineLoggerFactory.getLogger().info("current request {} not login send redirect login", getUrl(req));
+ String saml = PropertiesUtils.getProperties("conf").getProperty("saml");
+ try {
+ res.sendRedirect(saml);
+ } catch (IOException e) {
+ FineLoggerFactory.getLogger().error(e.getMessage(), e);
+ }
+ }
+
+ private String getUrl(HttpServletRequest req) {
+ String requestURL = req.getRequestURL().toString();
+ String queryString = req.getQueryString();
+ if (StringUtils.isNotBlank(queryString)) {
+ requestURL += "?" + queryString;
+ }
+ return requestURL;
+ }
+
+ private boolean isLogin(HttpServletRequest request) throws Exception {
+ String oldToken = TokenResource.COOKIE.getToken(request);
+ return oldToken != null && checkTokenValid(request, (String) oldToken);
+ }
+
+ private boolean checkTokenValid(HttpServletRequest req, String token) {
+ try {
+ Device device = NetworkHelper.getDevice(req);
+ LoginService.getInstance().loginStatusValid(token, TerminalHandler.getTerminal(req, device));
+ return true;
+ } catch (Exception ignore) {
+ }
+ return false;
+ }
+
+ private boolean isAccept(HttpServletRequest req) {
+ if (req.getRequestURI().endsWith("/view/form") || req.getRequestURI().endsWith("/view/report")) {
+ return StringUtils.isBlank(WebUtils.getHTTPRequestParameter(req, "viewlet")) && StringUtils.isNotBlank(req.getParameter("samlToken"));
+ }
+ return false;
+ }
+
+ private void next(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
+ try {
+ chain.doFilter(request, response);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean checkUser(String username) {
+ User user = null;
+ try {
+ user = UserService.getInstance().getUserByUserName(username);
+ FineLoggerFactory.getLogger().info("get user:" + user);
+ if (user != null) {
+ return true;
+ }
+ } catch (Exception e) {
+ FineLoggerFactory.getLogger().error(e.getMessage(), e);
+ }
+
+
+ return false;
+ }
+
+ private boolean loginFromToken(HttpServletRequest req, HttpServletResponse res, String username) throws Exception {
+ try {
+ if (StringUtils.isNotEmpty(username)) {
+ FineLoggerFactory.getLogger().info("current username:" + username);
+ String token = LoginService.getInstance().login(req, res, username);
+ FineLoggerFactory.getLogger().info("get login token:" + token);
+ req.setAttribute(DecisionServiceConstants.FINE_AUTH_TOKEN_NAME, token);
+ FineLoggerFactory.getLogger().info("username:" + username + "login success");
+ return true;
+ } else {
+ FineLoggerFactory.getLogger().warn("username is null!");
+ return false;
+ }
+ } catch (Exception e) {
+ FineLoggerFactory.getLogger().error(e.getMessage(), e);
+ }
+ return false;
+ }
+
+ private void setError(HttpServletResponse res, String reason) {
+ try {
+ PrintWriter printWriter = WebUtils.createPrintWriter(res);
+ Map map = new HashMap<>();
+ map.put("result", InterProviderFactory.getProvider().getLocText("Fine-Engine_Error_Page_Result"));
+ map.put("reason", reason);
+ map.put("solution", InterProviderFactory.getProvider().getLocText("Fine-Engine_Please_Contact_Platform_Admin"));
+ String page = WebServiceUtils.parseWebPageResourceSafe("com/fr/web/controller/decision/entrance/resources/unavailable.html", map);
+ printWriter.write(page);
+ printWriter.flush();
+ printWriter.close();
+ } catch (Exception e) {
+ FineLoggerFactory.getLogger().error(e.getMessage(), e);
+ }
+ }
+}
+
diff --git a/src/main/resources/conf.properties b/src/main/resources/conf.properties
new file mode 100644
index 0000000..6bb035a
--- /dev/null
+++ b/src/main/resources/conf.properties
@@ -0,0 +1,4 @@
+key=
+timeout=
+saml=https://ip:8075/oiosaml2/saml/login
+logout=https://ip:8075/oiosaml2/saml/logout
\ No newline at end of file