LAPTOP-SB56SG4Q\86185
3 years ago
10 changed files with 462 additions and 1 deletions
Binary file not shown.
@ -1,3 +1,6 @@ |
|||||||
# open-JSD-8928 |
# open-JSD-8928 |
||||||
|
|
||||||
JSD-8928 ADFS( saml模式 )单点 |
JSD-8928 ADFS( saml模式 )单点\ |
||||||
|
免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ |
||||||
|
仅作为开发者学习参考使用!禁止用于任何商业用途!\ |
||||||
|
为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。 |
Binary file not shown.
@ -0,0 +1,21 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
||||||
|
<plugin> |
||||||
|
<id>com.fr.plugin.xxxx.tpyy.adfs</id> |
||||||
|
<name><![CDATA[adfs单点]]></name> |
||||||
|
<active>yes</active> |
||||||
|
<version>1.2</version> |
||||||
|
<env-version>10.0</env-version> |
||||||
|
<jartime>2019-03-10</jartime> |
||||||
|
<vendor>fr.open</vendor> |
||||||
|
<description><![CDATA[adfs单点]]></description> |
||||||
|
<change-notes> |
||||||
|
[2021-11-10]【1.2】增加授权<br/> |
||||||
|
[2021-11-08]【1.1】增加授权<br/> |
||||||
|
[2021-10-29]【1.0】初始化插件。<br/> |
||||||
|
</change-notes> |
||||||
|
<function-recorder class="com.fr.plugin.xxxx.tpyy.adfs.SsoFilter"/> |
||||||
|
<extra-decision> |
||||||
|
<LogInOutEventProvider class="com.fr.plugin.xxxx.tpyy.adfs.CustomLogInOutEventProvider"/> |
||||||
|
<GlobalRequestFilterProvider class="com.fr.plugin.xxxx.tpyy.adfs.SsoFilter"/> |
||||||
|
</extra-decision> |
||||||
|
</plugin> |
@ -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"; |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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"); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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 <jre@trifork.com> |
||||||
|
* Rolf Njor Jensen <rolf@trifork.com> |
||||||
|
* Aage Nielsen <ani@openminds.dk> |
||||||
|
* |
||||||
|
*/ |
||||||
|
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<String, Object> 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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
Loading…
Reference in new issue