|
@@ -0,0 +1,366 @@
|
|
|
+package com.kingdee.eas.custom.sso;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.kingdee.bos.BOSException;
|
|
|
+import com.kingdee.bos.Context;
|
|
|
+import com.kingdee.eas.cp.eip.sso.ltpa.LtpaTokenManager;
|
|
|
+import com.kingdee.eas.cp.eip.sso.util.CASLoginConfigPropUtil;
|
|
|
+import com.kingdee.eas.cp.eip.sso.util.CloudParamUtil;
|
|
|
+import com.kingdee.eas.util.app.DbUtil;
|
|
|
+import com.kingdee.jdbc.rowset.IRowSet;
|
|
|
+import com.kingdee.shr.base.syssetting.exception.SHRWebException;
|
|
|
+import com.kingdee.util.StringUtils;
|
|
|
+import okhttp3.*;
|
|
|
+import org.apache.log4j.Logger;
|
|
|
+
|
|
|
+import javax.crypto.Cipher;
|
|
|
+import javax.crypto.SecretKey;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+import javax.servlet.ServletException;
|
|
|
+import javax.servlet.http.HttpServlet;
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.*;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.sql.SQLException;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @Description OA单点sHR
|
|
|
+ * @Date 2024/10/30 11:22
|
|
|
+ * @Created by Heyuan
|
|
|
+ */
|
|
|
+public class OAToSHR extends HttpServlet {
|
|
|
+ private static Logger logger = Logger.getLogger(OAToSHR.class);
|
|
|
+
|
|
|
+ private Properties prop = new Properties();
|
|
|
+ private String propPath = System.getProperty("EAS_HOME") + "/server/properties/scy/OASSOConfig.properties";
|
|
|
+
|
|
|
+ private final String SECRETKEY = "jOK7MpIonY+/56ulnl6RGQ==";
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
|
|
+ throws ServletException, IOException {
|
|
|
+ logger.error("OAToSHR -> doGet");
|
|
|
+ doPost(req, resp);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
|
|
+ throws ServletException, IOException {
|
|
|
+ logger.error("OAToSHR -> doPost");
|
|
|
+ BufferedReader streamReader = null;
|
|
|
+ String resultStr = null;
|
|
|
+ prop.load(new FileInputStream(propPath));
|
|
|
+ logger.error("OAToSHR requestUrl" + req.getRequestURL().toString());
|
|
|
+ try {
|
|
|
+ String ticket = req.getParameter("ticket");
|
|
|
+ logger.error("接收到的请求参数是:ticket " + ticket);
|
|
|
+ if (StringUtils.isEmpty(ticket)) {
|
|
|
+ //认证
|
|
|
+ authorize(req, resp);
|
|
|
+ } else {
|
|
|
+ //获取用户信息
|
|
|
+ callBack(req, resp, ticket);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ Map result = new HashMap();
|
|
|
+ result.put("msgType", "0");
|
|
|
+ result.put("reason", e.getMessage());
|
|
|
+ resultStr = JSON.toJSONString(result);
|
|
|
+ resp.setStatus(500);
|
|
|
+ PrintWriter writer = resp.getWriter();
|
|
|
+ resp.setContentType("application/json");
|
|
|
+ writer.write(resultStr);
|
|
|
+ writer.close();
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ if (streamReader != null) {
|
|
|
+ streamReader.close();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 认证
|
|
|
+ * 拼接OA认证接口地址,转发
|
|
|
+ *
|
|
|
+ * @param resp
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ public void authorize(HttpServletRequest req, HttpServletResponse resp) throws Exception {
|
|
|
+ String authorizePath = prop.getProperty("authorizePath");
|
|
|
+ if (StringUtils.isEmpty(authorizePath)) {
|
|
|
+ throw new RuntimeException("authorizePath不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ String response_type = prop.getProperty("response_type");
|
|
|
+ if (StringUtils.isEmpty(response_type)) {
|
|
|
+ throw new RuntimeException("response_type不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ String client_id = prop.getProperty("client_id");
|
|
|
+ if (StringUtils.isEmpty(client_id)) {
|
|
|
+ throw new RuntimeException("client_id不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ String redirect_uri1 = prop.getProperty("redirect_uri1");
|
|
|
+ if (StringUtils.isEmpty(redirect_uri1)) {
|
|
|
+ throw new RuntimeException("redirect_uri1不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ String redirectUrl = req.getParameter("redirect");
|
|
|
+ logger.error("callBack redirectUrl" + redirectUrl);
|
|
|
+ String encrypt = encrypt(redirectUrl, SECRETKEY);
|
|
|
+ logger.error("callBack encrypt" + encrypt);
|
|
|
+ redirect_uri1 += "?redirect=" + URLEncoder.encode(encrypt, "UTF-8");
|
|
|
+ Map params = new HashMap();
|
|
|
+ params.put("client_id", client_id);
|
|
|
+ params.put("response_type", response_type);
|
|
|
+ params.put("redirect_uri", URLEncoder.encode(redirect_uri1, "UTF-8"));
|
|
|
+ String urlString = appendUrl(authorizePath, params);
|
|
|
+ resp.sendRedirect(urlString);
|
|
|
+ logger.error("authorize url" + urlString);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * OA回调方法
|
|
|
+ * 获取Token和用户信息,单点到shr
|
|
|
+ *
|
|
|
+ * @param req
|
|
|
+ * @param resp
|
|
|
+ * @param ticket
|
|
|
+ * @throws SHRWebException
|
|
|
+ * @throws UnsupportedEncodingException
|
|
|
+ */
|
|
|
+ public void callBack(HttpServletRequest req, HttpServletResponse resp, String ticket) throws
|
|
|
+ SHRWebException, UnsupportedEncodingException {
|
|
|
+ logger.error("callback方法入参");
|
|
|
+ if (StringUtils.isEmpty(ticket)) {
|
|
|
+ throw new RuntimeException("ticket不能为空!");
|
|
|
+ }
|
|
|
+ String getAccessTokenPath = prop.getProperty("getAccessTokenPath");
|
|
|
+ if (StringUtils.isEmpty(getAccessTokenPath)) {
|
|
|
+ throw new RuntimeException("getAccessTokenPath不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ String client_secret = prop.getProperty("client_secret");
|
|
|
+ if (StringUtils.isEmpty(client_secret)) {
|
|
|
+ throw new RuntimeException("client_secret不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ String client_id = prop.getProperty("client_id");
|
|
|
+ if (StringUtils.isEmpty(client_id)) {
|
|
|
+ throw new RuntimeException("client_id不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ String redirect_uri2 = prop.getProperty("redirect_uri2");
|
|
|
+ if (StringUtils.isEmpty(redirect_uri2)) {
|
|
|
+ throw new RuntimeException("redirect_uri2不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ String getLoginIdPath = prop.getProperty("getLoginIdPath");
|
|
|
+ if (StringUtils.isEmpty(getLoginIdPath)) {
|
|
|
+ throw new RuntimeException("getLoginIdPath不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ String redirectUrl = req.getParameter("redirect");
|
|
|
+ logger.error("callBack redirectUrl" + redirectUrl);
|
|
|
+ Map params = new HashMap();
|
|
|
+ params.put("client_id", client_id);
|
|
|
+ params.put("client_secret", client_secret);
|
|
|
+ params.put("grant_type", "authorization_code");
|
|
|
+ params.put("code", ticket);
|
|
|
+ params.put("redirect_uri", URLEncoder.encode(redirect_uri2, "UTF-8"));
|
|
|
+ try {
|
|
|
+ String token = getAccessToken(getAccessTokenPath, params);
|
|
|
+ logger.error(token);
|
|
|
+ //从人员对象,获取纷享用户userId
|
|
|
+ String loginId = loginId2userId(getLoginIdPath, token);
|
|
|
+ String loginUrl = login(loginId, redirectUrl);
|
|
|
+ resp.sendRedirect(loginUrl);
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ throw new RuntimeException(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取携带账号和密码的登录地址
|
|
|
+ *
|
|
|
+ * @param userNumber
|
|
|
+ * @param redirectUrl
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String login(String userNumber, String redirectUrl) throws Exception {
|
|
|
+ String password = LtpaTokenManager.generate(userNumber, LtpaTokenManager.getDefaultLtpaConfig()).toString();
|
|
|
+ logger.error("login: password" + password);
|
|
|
+ String serverName = prop.getProperty("serverName");
|
|
|
+ if (StringUtils.isEmpty(serverName)) {
|
|
|
+ throw new RuntimeException("serverName不能为空! 请检查配置文件: " + propPath);
|
|
|
+ }
|
|
|
+ StringBuilder url = new StringBuilder();
|
|
|
+ url.append("/shr/index2sso.jsp?username=").append(userNumber)
|
|
|
+ .append("&password=").append(password).append("&redirectTo=");
|
|
|
+ StringBuilder redirectUrlStr = new StringBuilder();
|
|
|
+ if (StringUtils.isEmpty(redirectUrl)) {
|
|
|
+ redirectUrlStr.append(serverName).append("/shr/dynamic.do?uipk=shr.perself.homepage");
|
|
|
+ } else {
|
|
|
+ //解密
|
|
|
+ String decrpt = decrypt(redirectUrl, SECRETKEY);
|
|
|
+ if (decrpt.contains(serverName)) {
|
|
|
+ redirectUrlStr.append(decrpt);
|
|
|
+ } else {
|
|
|
+ redirectUrlStr.append(serverName).append(decrpt);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ url.append(URLEncoder.encode(redirectUrlStr.toString(), "UTF-8"));
|
|
|
+ logger.error("login: url" + url);
|
|
|
+ return url.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取泛微token方法
|
|
|
+ *
|
|
|
+ * @param getAccessTokenPath
|
|
|
+ * @param params
|
|
|
+ * @return
|
|
|
+ * @throws IOException
|
|
|
+ */
|
|
|
+ private String getAccessToken(String getAccessTokenPath, Map<String, String> params)
|
|
|
+ throws IOException {
|
|
|
+ logger.error("getAccessToken方法参数: " + params);
|
|
|
+ String url = appendUrl(getAccessTokenPath, params);
|
|
|
+ logger.error("access_token url" + url);
|
|
|
+ OkHttpClient client = new OkHttpClient();
|
|
|
+ Request request = new Request.Builder()
|
|
|
+ .url(url)
|
|
|
+ .get()
|
|
|
+ .addHeader("content-type", "multipart/form-data; boundary=---011000010111000001101001")
|
|
|
+ .build();
|
|
|
+ Response response = client.newCall(request).execute();
|
|
|
+ if (response.isSuccessful()) {
|
|
|
+ String string = response.body().string();
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(string);
|
|
|
+ String code = jsonObject.getString("code");
|
|
|
+ if ("0".equals(code)) {
|
|
|
+ String access_token = jsonObject.getString("access_token");
|
|
|
+ logger.error("access_token " + access_token);
|
|
|
+ return access_token;
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException(jsonObject.getString("msg"));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ //网络超时
|
|
|
+ throw new RuntimeException("获取token超时");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取用户信息
|
|
|
+ *
|
|
|
+ * @param accessToken
|
|
|
+ * @return
|
|
|
+ * @throws IOException
|
|
|
+ * @throws BOSException
|
|
|
+ * @throws SQLException
|
|
|
+ */
|
|
|
+ private String loginId2userId(String getLoginIdPath, String accessToken)
|
|
|
+ throws IOException, BOSException, SQLException {
|
|
|
+ if (StringUtils.isEmpty(accessToken)) {
|
|
|
+ throw new RuntimeException("accessToken不能为空! ");
|
|
|
+ }
|
|
|
+ //获取第三方用户信息
|
|
|
+ Map params = new HashMap();
|
|
|
+ params.put("access_token", accessToken);
|
|
|
+ String url = appendUrl(getLoginIdPath, params);
|
|
|
+ logger.error("loginId2userId url" + url);
|
|
|
+ OkHttpClient client = new OkHttpClient();
|
|
|
+ Request request = new Request.Builder()
|
|
|
+ .url(url)
|
|
|
+ .get()
|
|
|
+ .addHeader("content-type", "multipart/form-data; boundary=---011000010111000001101001")
|
|
|
+ .build();
|
|
|
+ Response response = client.newCall(request).execute();
|
|
|
+ if (response.isSuccessful()) {
|
|
|
+ String string = response.body().string();
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(string);
|
|
|
+ String code = jsonObject.getString("code");
|
|
|
+ if ("0".equals(code)) {
|
|
|
+ JSONObject attributes = jsonObject.getJSONObject("attributes");
|
|
|
+ //登录id
|
|
|
+ String loginid = attributes.getString("loginid");
|
|
|
+ String dataCenter = CASLoginConfigPropUtil.getDataCenter();
|
|
|
+ String locale = CASLoginConfigPropUtil.getLocale();
|
|
|
+ if (!StringUtils.isEmpty(dataCenter) && !StringUtils.isEmpty(locale)) {
|
|
|
+ Context ctx = CloudParamUtil.getContext(dataCenter, locale, "administrator");
|
|
|
+ String sql = "SELECT count(1) total FROM T_PM_USER WHERE fnumber=?";
|
|
|
+ IRowSet rs = DbUtil.executeQuery(ctx, sql, new Object[]{loginid});
|
|
|
+ int total = 0;
|
|
|
+ if (rs.next()) {
|
|
|
+ total = rs.getInt("total");
|
|
|
+ }
|
|
|
+ if (total <= 0) {
|
|
|
+ logger.error("SHR找不到对应的用户, loginid:" + loginid);
|
|
|
+ throw new RuntimeException("SHR找不到对应的用户, loginid: " + loginid);
|
|
|
+ //("您无权限访问SHR系统,请联系管理员处理。")
|
|
|
+ } else if (total > 1) {
|
|
|
+ logger.error("SHR找到多个对应的用户, loginid:" + loginid);
|
|
|
+ throw new RuntimeException("SHR找到多个对应的用户, loginid: " + loginid);
|
|
|
+ //("您无权限访问SHR系统,请联系管理员处理。")
|
|
|
+ } else {
|
|
|
+ return loginid;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ logger.error("获取用户信息报错,数据中心没找到!");
|
|
|
+ throw new RuntimeException("获取用户信息报错,数据中心没找到!");
|
|
|
+ } else {
|
|
|
+ logger.error(jsonObject.getString("msg"));
|
|
|
+ throw new RuntimeException(jsonObject.getString("msg"));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ //网络超时
|
|
|
+ logger.error("网络超时");
|
|
|
+ throw new RuntimeException("网络超时");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 拼接地址参数
|
|
|
+ */
|
|
|
+ private static String appendUrl(String url, Map<String, String> data) {
|
|
|
+ logger.error("appendUrl_url: " + url);
|
|
|
+ logger.error("appendUrl_data: " + data);
|
|
|
+ StringBuilder paramStr = new StringBuilder();
|
|
|
+ for (String key : data.keySet()) {
|
|
|
+ paramStr.append(key).append("=").append(data.get(key)).append("&");
|
|
|
+ }
|
|
|
+ paramStr.deleteCharAt(paramStr.lastIndexOf("&"));
|
|
|
+ String str = url.contains("?") ? (url + "&" + paramStr) : (url + "?" + paramStr);
|
|
|
+ logger.error("拼接后的地址为:" + str);
|
|
|
+ return str;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用 AES 加密
|
|
|
+ public static String encrypt(String plainText, String encodedKey) throws Exception {
|
|
|
+ // 将Base64字符串解码为字节数组
|
|
|
+ byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
|
|
|
+ // 使用字节数组创建一个AES密钥
|
|
|
+ SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
|
|
|
+ Cipher cipher = Cipher.getInstance("AES");
|
|
|
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
|
|
+ byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
|
|
|
+ return Base64.getEncoder().encodeToString(encryptedBytes);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用 AES 解密
|
|
|
+ public static String decrypt(String encryptedText, String encodedKey) throws Exception {
|
|
|
+ // 将Base64字符串解码为字节数组
|
|
|
+ byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
|
|
|
+ // 使用字节数组创建一个AES密钥
|
|
|
+ SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
|
|
|
+ Cipher cipher = Cipher.getInstance("AES");
|
|
|
+ cipher.init(Cipher.DECRYPT_MODE, secretKey);
|
|
|
+ byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
|
|
|
+ byte[] decryptedBytes = cipher.doFinal(decodedBytes);
|
|
|
+ return new String(decryptedBytes, "UTF-8");
|
|
|
+ }
|
|
|
+
|
|
|
+}
|