From d5adb42c58dd6edf9cc3a88d8ecf1c054770e5a1 Mon Sep 17 00:00:00 2001 From: "LAPTOP-SB56SG4Q\\86185" Date: Fri, 3 Sep 2021 14:12:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=BC=80=E6=BA=90=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=9D=90=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 5591-5860-6693使用文档.docx | Bin 0 -> 14415 bytes JSD-8232-需求确认书V1.docx | Bin 0 -> 59666 bytes README.md | 5 +- plugin.xml | 29 + .../com/fr/plugin/ChangeConfigHander.java | 38 + .../fr/plugin/ChangeDesignConfigHander.java | 38 + .../com/fr/plugin/DataFilterPlaceHolder.java | 487 +++++++++++ .../java/com/fr/plugin/FR2loginFilter.java | 344 ++++++++ .../java/com/fr/plugin/FRloginFilter.java | 165 ++++ .../java/com/fr/plugin/FilterMeConfig.java | 160 ++++ .../GlobalRequestFilterPlaceHolder.java | 110 +++ src/main/java/com/fr/plugin/HttpApi.java | 130 +++ src/main/java/com/fr/plugin/IpUtils.java | 62 ++ src/main/java/com/fr/plugin/JHHttpHander.java | 14 + .../com/fr/plugin/JHUrlAliasProvider.java | 17 + src/main/java/com/fr/plugin/JSONUtils.java | 28 + .../java/com/fr/plugin/LastloginFilter.java | 794 ++++++++++++++++++ src/main/java/com/fr/plugin/LoginFilter.java | 150 ++++ .../com/fr/plugin/LoginOutEventProvider.java | 25 + .../com/fr/plugin/PostionImportgHander.java | 139 +++ src/main/java/com/fr/plugin/RemoteFilter.java | 405 +++++++++ .../java/com/fr/plugin/RequestWrapper.java | 128 +++ .../com/fr/plugin/XContentRequestWrapper.java | 97 +++ .../java/com/fr/plugin/util/LogUtils.java | 90 ++ .../java/com/fr/plugin/util/RequestUtils.java | 26 + .../com/fr/plugin/util/ResponseUtils.java | 35 + .../java/com/fr/sso/cas/FRAccessFilter.java | 464 ++++++++++ src/main/resources/com/fr/plugin/error.html | 16 + 集成Filter需求文档.docx | Bin 0 -> 12958 bytes 29 files changed, 3995 insertions(+), 1 deletion(-) create mode 100644 5591-5860-6693使用文档.docx create mode 100644 JSD-8232-需求确认书V1.docx create mode 100644 plugin.xml create mode 100644 src/main/java/com/fr/plugin/ChangeConfigHander.java create mode 100644 src/main/java/com/fr/plugin/ChangeDesignConfigHander.java create mode 100644 src/main/java/com/fr/plugin/DataFilterPlaceHolder.java create mode 100644 src/main/java/com/fr/plugin/FR2loginFilter.java create mode 100644 src/main/java/com/fr/plugin/FRloginFilter.java create mode 100644 src/main/java/com/fr/plugin/FilterMeConfig.java create mode 100644 src/main/java/com/fr/plugin/GlobalRequestFilterPlaceHolder.java create mode 100644 src/main/java/com/fr/plugin/HttpApi.java create mode 100644 src/main/java/com/fr/plugin/IpUtils.java create mode 100644 src/main/java/com/fr/plugin/JHHttpHander.java create mode 100644 src/main/java/com/fr/plugin/JHUrlAliasProvider.java create mode 100644 src/main/java/com/fr/plugin/JSONUtils.java create mode 100644 src/main/java/com/fr/plugin/LastloginFilter.java create mode 100644 src/main/java/com/fr/plugin/LoginFilter.java create mode 100644 src/main/java/com/fr/plugin/LoginOutEventProvider.java create mode 100644 src/main/java/com/fr/plugin/PostionImportgHander.java create mode 100644 src/main/java/com/fr/plugin/RemoteFilter.java create mode 100644 src/main/java/com/fr/plugin/RequestWrapper.java create mode 100644 src/main/java/com/fr/plugin/XContentRequestWrapper.java create mode 100644 src/main/java/com/fr/plugin/util/LogUtils.java create mode 100644 src/main/java/com/fr/plugin/util/RequestUtils.java create mode 100644 src/main/java/com/fr/plugin/util/ResponseUtils.java create mode 100644 src/main/java/com/fr/sso/cas/FRAccessFilter.java create mode 100644 src/main/resources/com/fr/plugin/error.html create mode 100644 集成Filter需求文档.docx diff --git a/5591-5860-6693使用文档.docx b/5591-5860-6693使用文档.docx new file mode 100644 index 0000000000000000000000000000000000000000..2eaa15234218d3b5633646f7bd6b7adb22225a93 GIT binary patch literal 14415 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 DZB9!+ literal 0 HcmV?d00001 diff --git a/JSD-8232-需求确认书V1.docx b/JSD-8232-需求确认书V1.docx new file mode 100644 index 0000000000000000000000000000000000000000..5d39087c613b93a5708733f9bfb6ea6653c9ff55 GIT binary patch literal 59666 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%0ArM%a-8<}|FmrhrHdMryH%fTSIr3Emh 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 BwF3YE literal 0 HcmV?d00001 diff --git a/README.md b/README.md index f65f99d..11b3e29 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # open-JSD-8232 -JSD-8232 开源任务材料 \ No newline at end of file +JSD-8232 开源任务材料\ +免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ +仅作为开发者学习参考使用!禁止用于任何商业用途!\ +为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。 \ No newline at end of file diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..c97b140 --- /dev/null +++ b/plugin.xml @@ -0,0 +1,29 @@ + + + com.fr.plugin.sln5591 + + yes + 1.8.8 + 10.0 + 2019-03-10 + fr.open + + + + + com.fr.plugin + + + + + + + + + + + + + + + diff --git a/src/main/java/com/fr/plugin/ChangeConfigHander.java b/src/main/java/com/fr/plugin/ChangeConfigHander.java new file mode 100644 index 0000000..5c1aabe --- /dev/null +++ b/src/main/java/com/fr/plugin/ChangeConfigHander.java @@ -0,0 +1,38 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.stable.StringUtils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class ChangeConfigHander extends BaseHttpHandler { + @Override + public RequestMethod getMethod() { + return null; + } + + @Override + public String getPath() { + return "/change1"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest req, HttpServletResponse res) throws Exception { + String config = req.getParameter("config"); + FilterMeConfig instance = FilterMeConfig.getInstance(); + if (StringUtils.isBlank(config)) { + instance.setAdminFlags(""); + } else { + instance.setAdminFlags(config); + } + WebUtils.printAsString(res, "success"); + } +} \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/ChangeDesignConfigHander.java b/src/main/java/com/fr/plugin/ChangeDesignConfigHander.java new file mode 100644 index 0000000..71d31d5 --- /dev/null +++ b/src/main/java/com/fr/plugin/ChangeDesignConfigHander.java @@ -0,0 +1,38 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.stable.StringUtils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class ChangeDesignConfigHander extends BaseHttpHandler { + @Override + public RequestMethod getMethod() { + return null; + } + + @Override + public String getPath() { + return "/change2"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest req, HttpServletResponse res) throws Exception { + String designConfig = req.getParameter("config"); + FilterMeConfig instance = FilterMeConfig.getInstance(); + if (StringUtils.isBlank(designConfig)) { +// instance.setDesignFlags(""); + } else { +// instance.setDesignFlags(designConfig); + } + WebUtils.printAsString(res, "success"); + } +} \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/DataFilterPlaceHolder.java b/src/main/java/com/fr/plugin/DataFilterPlaceHolder.java new file mode 100644 index 0000000..1b1692c --- /dev/null +++ b/src/main/java/com/fr/plugin/DataFilterPlaceHolder.java @@ -0,0 +1,487 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Iterator; + +public class DataFilterPlaceHolder extends AbstractGlobalRequestFilterProvider { + @Override + public String filterName() { + return "a1data"; + } + + @Override + public String[] urlPatterns() { + return new String[]{ + "/*" + }; + } + + @Override + public void init(FilterConfig filterConfig) { + FilterMeConfig.getInstance(); + super.init(filterConfig); + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse httpServletResponse, FilterChain filterChain) { + if (request.getMethod().equalsIgnoreCase("POST")) { + if (request.getPathInfo() != null && request.getPathInfo().endsWith("v5/design/widget/data")) { + String header = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(header)) { + RequestWrapper wrapper = new RequestWrapper(request); + String bodyString = wrapper.getBodyString(); + JSONObject entries = new JSONObject(bodyString); + try { + delJSON(header, entries); +// FineLoggerFactory.getLogger().info("设置到body中 :{}", entries); + wrapper.setBody(entries.toString().getBytes()); + filterChain.doFilter(wrapper, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + return; + } + } + } + try { + filterChain.doFilter(request, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + } + + private void delJSON(String head, JSONObject req) { + try { + head = URLDecoder.decode(head, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().error("收到的 Context:{}", head); + JSONObject jsonObject = new JSONObject(head); + Iterator keys = jsonObject.keys(); + if (req.has("parameter")) { + JSONObject parameter = req.getJSONObject("parameter"); + while (keys.hasNext()) { + String key = keys.next(); + String value = jsonObject.getString(key); + if (!parameter.has(key)) { + setData(key, value, parameter); + } + } + } + +// else { +// JSONObject parameter = new JSONObject(); +// while (keys.hasNext()) { +// String key = keys.next(); +// String value = jsonObject.getString(key); +// if (!parameter.has(key)) { +// setData(key, value, parameter); +// } +// } +// req.put("parameter", parameter); +// } + +// String userNo = jsonObject.getString("usrNo"); +// String userNm = jsonObject.getString("usrNm"); +// String pstNo = jsonObject.getString("pstNo"); +// String instNo = jsonObject.getString("instNo"); +// String accInstNo = jsonObject.getString("accInstNo"); +// String admnInstNo = jsonObject.getString("admnInstNo"); +// if (!parameter.has(userNo)) { +// setData("usrNo", userNo, parameter); +// } +// if (!parameter.has(userNm)) { +// setData("userNm", userNm, parameter); +// } +// if (!parameter.has(pstNo)) { +// setData("pstNo", pstNo, parameter); +// } +// if (!parameter.has(instNo)) { +// setData("instNo", instNo, parameter); +// } +// if (!parameter.has(accInstNo)) { +// setData("accInstNo", accInstNo, parameter); +// } +// if (!parameter.has(admnInstNo)) { +// setData("admnInstNo", admnInstNo, parameter); +// } + } + + private void setData(String name, String data, JSONObject parameter) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("type", 97);//URL参数 + jsonObject.put("value", data); + parameter.put(name, jsonObject); + } + + // /** +// * 判断是否是远程设计请求 +// */ +// private boolean isRemoteDesignRequest(HttpServletRequest request) { +// String pathInfo = request.getPathInfo(); +// return StringUtils.isNotEmpty(pathInfo) && pathInfo.startsWith("/remote/design"); +// } +// +// +// private void adminLogin(HttpServletRequest request) { +// +// } +// +// /** +// * 判断是否是资源文件请求 +// */ +// private boolean isResourceFileRequest(HttpServletRequest request) { +// String pathInfo = request.getPathInfo(); +// if (StringUtils.isEmpty(pathInfo)) { +// return false; +// } +// +// return pathInfo.startsWith("/resources") || pathInfo.startsWith("/file"); +// } +// + public static void printException2FrLog(Throwable e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } +// +// private void sendRedirect(HttpServletResponse res, String url) { +// res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); +// res.setHeader("Location", url); +// } +// +// +// private void login(HttpServletRequest req, HttpServletResponse res, String username) { +// HttpSession session = req.getSession(true); +// String token = null; +// try { +// token = LoginService.getInstance().login(req, res, username); +//// session.setAttribute("fine_auth_token", token); +// } catch (Exception e) { +// FineLoggerFactory.getLogger().error(e.getMessage(), e); +// FineLoggerFactory.getLogger().error("login failed"); +// } +// FineLoggerFactory.getLogger().error("login success"); +// } +// +// private boolean ipblackCheck(String ip) { +// String[] blackIps = new String[]{}; +// try { +// InputStream inputStream = ResourceIOUtils.read("/config-all/whitelist.properties"); +// if (inputStream != null) { +// String lines = IOUtils.inputStream2String(inputStream); +// blackIps = lines.split("\n"); +// } +// } catch (IOException e) { +// LogUtils.error(e.getMessage(), e); +// } +// if ("0:0:0:0:0:0:0:1".equals(ip)) { +// LogUtils.info("IP 地址为服务器本机 IPv6 地址: {},直接放行。", ip); +// return true; +// } +// if ("127.0.0.1".equals(ip)) { +// LogUtils.info("IP 地址为服务器本机 IPv4 地址: {},直接放行。", ip); +// return true; +// } +// for (String blackIp : blackIps) { +// if (!blackIp.contains("-")) { +// if (ip.equals(blackIp)) { +// return true; +// } +// } else { +// String trim = blackIp.trim(); +// String[] split = trim.split("-"); +// if (split.length > 1) { +// if (IpUtils.ipExistsInRange(ip, split[0], split[1])) { +// return true; +// } +// } +// } +// } +// return false; +// } +// +// private void writerOurError(HttpServletResponse httpServletResponse) { +// try { +// WebUtils.writeOutTemplate("/com/fr/plugin/error.html", httpServletResponse, new HashMap()); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// +// private boolean isLogin(HttpServletRequest req) { +// return LoginService.getInstance().isLogged(req); +// } +//// +//// public void filter(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException { +//// String uri = request.getRequestURI(); +//// String ip = IpUtils.getIp(request); +//// if (!ipblackCheck(ip)) { +//// writerOurError(httpServletResponse); +//// return; +//// } +//// if (request.getMethod().equalsIgnoreCase("GET")) { +//// String header = request.getHeader("X-Context"); +//// if (StringUtils.isNotBlank(header)) { +//// XContentRequestWrapper wrapper = new XContentRequestWrapper(request); +////// String a = wrapper.getParameter("a"); +////// FineLoggerFactory.getLogger().error("获取的A :{} ", a); +//// if (isLogin(wrapper)) { +//// String usrNm = wrapper.getParameter("usrNm"); +//// String userNameFromRequest = LoginService.getInstance().getUserNameFromRequestCookie(request); +//// //如果登录的用户和content的中不一致则切换登录用户 +//// if (!ComparatorUtils.equals(userNameFromRequest, usrNm)) { +//// tokenLogin(request, httpServletResponse); +//// } +//// } else { +//// tokenLogin(request, httpServletResponse); +//// return; +//// } +//// +//// String loginUrl = request.getContextPath() + request.getServletPath() + "/login"; +//// String home = request.getContextPath() + request.getServletPath() + "?sendTwo=true"; +//// if (loginUrl.equals(uri)) { +//// String manualOut = request.getParameter("manual"); +//// if (!ComparatorUtils.equals(manualOut, "true")) { +////// String header = request.getHeader("X-Context"); +//// if (StringUtils.isNotBlank(header) && !isLogin(request)) { +//// tokenLogin(request, httpServletResponse); +//// sendRedirect(httpServletResponse, home); +//// } +//// } +//// } +//// } +//// } +//// return; +//// } +//// +//// private void tokenLogin(HttpServletRequest req, HttpServletResponse res) { +//// String header = req.getHeader("X-Context"); +//// try { +//// header = URLDecoder.decode(header, "UTF-8"); +//// } catch (UnsupportedEncodingException e) { +//// e.printStackTrace(); +//// } +//// FineLoggerFactory.getLogger().info("拦截器捕获一个请求 x-content :{}", header); +//// JSONObject entries = new JSONObject(header); +//// String usrNm = entries.getString("usrNm"); +//// +//// try { +//// User user = UserService.getInstance().getUserByUserName(usrNm); +//// UserController userController = AuthorityContext.getInstance().getUserController(); +//// if (user == null) { +//// FineLoggerFactory.getLogger().info("拦截器新增一个用户 :{}", usrNm); +//// PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(ManualOperationType.KEY).getPasswordValidator(); +//// user = (new User()).userName(usrNm).realName(usrNm).password(passwordValidator.encode(usrNm, "123456")) +//// .creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); +//// userController.add(user); +//// } +//// String userId = user.getId(); +//// dealCustomRole(req, userId); +//// //在判断本地的角色是不是远程没有了,要移除掉 +//// CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); +//// +//// List roles = customRoleController.findByUser(userId, QueryFactory.create()); +//// boolean isAdmin = false; +//// List adminFlags = getAdminFlags(); +//// if (!adminFlags.isEmpty()) { +//// for (CustomRole role : roles) { +//// String name = role.getName(); +//// if (isAdminFlags(name, adminFlags)) { +//// isAdmin = true; +//// break; +//// } +//// } +//// } +//// if (isAdmin) { +//// FineLoggerFactory.getLogger().info("本次为管理员登录 "); +//// login(req, res, "admin"); +//// } else { +//// FineLoggerFactory.getLogger().info("本次为普通用户登录:{}", usrNm); +//// login(req, res, usrNm); +//// } +//// +//// } catch (Exception e) { +//// e.printStackTrace(); +//// } +//// } +//// +//// private void dealCustomRole(HttpServletRequest req, String userId) throws Exception { +//// UserController userController = AuthorityContext.getInstance().getUserController(); +//// String header = req.getHeader("X-Context"); +//// CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); +//// +//// +//// try { +//// +//// header = URLDecoder.decode(header, "UTF-8"); +//// } catch (UnsupportedEncodingException e) { +//// e.printStackTrace(); +//// } +//// FineLoggerFactory.getLogger().info("开始同步角色信息x-content :{}", header); +//// JSONObject entries = new JSONObject(header); +//// String usrNm = entries.getString("usrNm"); +//// JSONArray rlNoList = entries.getJSONArray("rlNoList"); +//// List remoteRoles = new ArrayList<>(); +//// +//// try { +//// int size = rlNoList.size(); +//// for (int i = 0; i < size; i++) { +//// String name = rlNoList.getString(i); +//// remoteRoles.add(name); +//// FineLoggerFactory.getLogger().info("传送过来的角色:{}", name); +//// } +//// //先判断传过来的的角色是不是本地都有,没有要加上 +//// for (String role : remoteRoles) { +//// List controllerOne = customRoleController.find(QueryFactory.create().addRestriction(RestrictionFactory.eq("name", role))); +//// if (!controllerOne.isEmpty()) { +//// try { +//// CustomRole customRole = controllerOne.get(0); +//// userController.addUserToCustomRole(userId, customRole.getId()); +//// } catch (Exception e) { +//// } +//// } else { +//// FineLoggerFactory.getLogger().info("传送过来的角色在本地不存在,添加到本地:{}", role); +//// CustomRole addRole = new CustomRole(); +//// addRole.setId(role); +//// addRole.setName(role); +//// addRole.setAlias(role); +//// addRole.setEnable(true); +//// addRole.setDescription("通过xcontent添加"); +//// try { +//// customRoleController.add(addRole); +//// userController.addUserToCustomRole(userId, addRole.getId()); +//// } catch (Exception e) { +//// } +//// } +////// for (CustomRole customRole : roles) { +////// FineLoggerFactory.getLogger().info("远程角色:{} 本地:{}", role,customRole.getName()); +////// if (ComparatorUtils.equals(customRole.getName(), role)) { +////// find = true; +////// try { +////// userController.addUserToCustomRole(userId, customRole.getId()); +////// } catch (Exception e){ +////// +////// } +////// } +////// } +//// +//// } +//// //在判断本地的角色是不是远程没有了,要移除掉 +//// List roles = customRoleController.findByUser(userId, QueryFactory.create()); +//// +//// for (CustomRole customRole : roles) { +//// if (!remoteRoles.contains(customRole.getName())) { +//// if ("superusers".equals((customRole.getName()))) { +//// continue; +//// } +//// FineLoggerFactory.getLogger().info("远端没有的角色本地移除:{}", customRole.getName()); +//// userController.removeUserFromCustomRole(userId, customRole.getId()); +//// } +//// } +//// } catch (Exception e) { +//// e.printStackTrace(); +//// } +//// } +//// +//// private static boolean isAdminFlags(String flag, List flags) { +//// return flags.contains(flag); +//// } +//// +//// private List getAdminFlags() { +//// String adminFlags = FilterMeConfig.getInstance().getAdminFlags(); +//// FineLoggerFactory.getLogger().info("本地管理员角色获取:{}", adminFlags); +//// if (StringUtils.isNotBlank(adminFlags)) { +//// String[] split = adminFlags.split(";"); +//// return Arrays.asList(split); +//// } +//// return Collections.emptyList(); +//// } +//// +//// private String getUrl(HttpServletRequest request) { +//// String url = "/"; +//// try { +//// url = "http://" + request.getServerName()//服务器地址 +//// + ":" +//// + request.getServerPort() + request.getRequestURI(); +//// Enumeration parameterNames = request.getParameterNames(); +//// Map reqParams = new HashMap<>(); +//// while (parameterNames.hasMoreElements()) { +//// String key = parameterNames.nextElement(); +//// reqParams.put(key, request.getParameter(key)); +//// } +//// Map parmas = header2url(request); +//// reqParams.putAll(parmas); +//// String header2url = map2String(reqParams); +//// FineLoggerFactory.getLogger().info("转换之后的url参数:{}", header2url); +//// if (url.contains("?")) { +//// url += header2url; +//// } else { +//// url += "?a=1" + header2url; +//// } +//// +//// } catch (Exception e) { +//// e.printStackTrace(); +//// } +//// url += "&sendTwo=true"; +//// return url; +//// } +//// +//// +//// private Map header2url(HttpServletRequest request) { +//// Map params = new HashMap<>(); +//// String context = request.getHeader("X-Context"); +//// if (StringUtils.isNotBlank(context)) { +//// try { +//// context = URLDecoder.decode(context, "UTF-8"); +//// } catch (UnsupportedEncodingException e) { +//// e.printStackTrace(); +//// } +//// JSONObject jsonObject = new JSONObject(context); +//// String userNo = jsonObject.getString("usrNo"); +//// String userNm = jsonObject.getString("usrNm"); +//// String pstNo = jsonObject.getString("pstNo"); +// String instNo = jsonObject.getString("instNo"); +// String accInstNo = jsonObject.getString("accInstNo"); +// String admnInstNo = jsonObject.getString("admnInstNo"); +// params.put("usrNo", userNo); +// params.put("usrNm", userNm); +// params.put("pstNo", pstNo); +// params.put("instNo", instNo); +// params.put("accInstNo", accInstNo); +// params.put("admnInstNo", admnInstNo); +// } +// params.put("sendTwo", "true"); +// return params; +// } +// +// private String map2String(Map params) { +// StringBuffer buffer = new StringBuffer(); +// Set strings = params.keySet(); +// for (String key : strings) { +// buffer.append("&").append(key).append("=").append(params.get(key)); +// } +// return buffer.toString(); +// } + +} diff --git a/src/main/java/com/fr/plugin/FR2loginFilter.java b/src/main/java/com/fr/plugin/FR2loginFilter.java new file mode 100644 index 0000000..10aa3d1 --- /dev/null +++ b/src/main/java/com/fr/plugin/FR2loginFilter.java @@ -0,0 +1,344 @@ +package com.fr.plugin; + +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.UserController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.decision.privilege.encrpt.PasswordValidator; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.general.ComparatorUtils; +import com.fr.general.IOUtils; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.UUID; + +@FunctionRecorder(localeKey = "fr2") +public class FR2loginFilter extends AbstractEmbedRequestFilterProvider { + @Override + public void init(FilterConfig filterConfig) { + super.init(filterConfig); + + } + + private static String[] filterUrls = new String[]{ + "v5/design/report/", + "view/report", + "view/form" + }; + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + private boolean needFilter(String url) { + if (StringUtils.isNotBlank(url)) { + for (String filterUrl : filterUrls) { + if (url.contains(filterUrl)) { + return true; + } + } + } + return false; + } + + private void login(HttpServletRequest req, HttpServletResponse res, String username) { + HttpSession session = req.getSession(true); + String token = null; + try { + token = LoginService.getInstance().login(req, res, username); + req.setAttribute("fine_auth_token", token); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + FineLoggerFactory.getLogger().error("login failed"); + } + FineLoggerFactory.getLogger().error("login success"); + } + + private boolean ipblackCheck(String ip) { + String[] blackIps = new String[]{}; + try { + InputStream inputStream = ResourceIOUtils.read("/config-all/whitelist.properties"); + if (inputStream != null) { + String lines = IOUtils.inputStream2String(inputStream); + blackIps = lines.split("\n"); + } + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + if ("0:0:0:0:0:0:0:1".equals(ip)) { + return true; + } + for (String blackIp : blackIps) { + if (!blackIp.contains("-")) { + if (ip.equals(blackIp)) { + return true; + } + } else { + String trim = blackIp.trim(); + String[] split = trim.split("-"); + if (split.length > 1) { + if (com.fr.plugin.IpUtils.ipExistsInRange(ip, split[0], split[1])) { + return true; + } + } + } + } + return false; + } + + private void writerOurError(HttpServletResponse httpServletResponse) { + try { + WebUtils.writeOutTemplate("/com/fr/plugin/error.html", httpServletResponse, new HashMap()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private boolean isLogin(HttpServletRequest req) { + return LoginService.getInstance().isLogged(req); + } + + @Override + public void filter(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException { + String uri = request.getRequestURI(); + String ip = IpUtils.getIp(request); + if (!ipblackCheck(ip)) { + writerOurError(httpServletResponse); + return; + } + if (request.getMethod().equalsIgnoreCase("GET")) { + //直接return 就开始执行原来的逻辑 + if (request.getParameter("sendTwo") != null) { + return; + } + + //需要特殊处理的url按照特殊处理 + if (needFilter(uri)) { + sendRedirect(httpServletResponse, getUrl(request)); + return; + } + + String loginUrl = request.getContextPath() + request.getServletPath() + "/login"; + String home = request.getContextPath() + request.getServletPath() + "?sendTwo=true"; + if (loginUrl.equals(uri)) { + String manualOut = request.getParameter("manual"); + if (!ComparatorUtils.equals(manualOut, "true")) { + String header = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(header) && !isLogin(request)) { + tokenLogin(request, httpServletResponse); + sendRedirect(httpServletResponse, home); + } + } + } + } + return; + } + + private void tokenLogin(HttpServletRequest req, HttpServletResponse res) { + String header = req.getHeader("X-Context"); + try { + header = URLDecoder.decode(header, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().info("拦截器捕获一个请求 x-content :{}", header); + JSONObject entries = new JSONObject(header); + String usrNm = entries.getString("usrNm"); + JSONArray rlNoList = entries.getJSONArray("rlNoList"); + try { + User user = UserService.getInstance().getUserByUserName(usrNm); + UserController userController = AuthorityContext.getInstance().getUserController(); + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + if (user == null) { + FineLoggerFactory.getLogger().info("拦截器新增一个用户 :{}", usrNm); + PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(ManualOperationType.KEY).getPasswordValidator(); + user = (new User()).userName(usrNm).realName(usrNm).password(passwordValidator.encode(usrNm, UUID.randomUUID().toString())) + .creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + userController.add(user); + } + String userId = user.getId(); + List roles = customRoleController.findByUser(userId, QueryFactory.create()); + boolean isAdminRoleFlag = false; + List adminFlags = getAdminFlags(); + FineLoggerFactory.getLogger().info("当前的管理员标识符 :{}", adminFlags); + List remoteRoles = new ArrayList<>(); + try { + int size = rlNoList.size(); + for (int i = 0; i < size; i++) { + String name = rlNoList.getString(i); + remoteRoles.add(name); + FineLoggerFactory.getLogger().info("传送过来的角色:{}", name); + if (isAdminFlags(name, adminFlags)) { + isAdminRoleFlag = true; + } + } + + //先判断传过来的的角色是不是本地都有,没有要加上 + for (String role : remoteRoles) { + boolean find = false; + for (CustomRole customRole : roles) { + if (ComparatorUtils.equals(customRole.getName(), role)) { + find = true; + } + } + if (!find) { +// FineLoggerFactory.getLogger().info("传送过来的角色在本地不存在,添加到本地:{}", role); +// List roleList = customRoleController.find(QueryFactory.create().addRestriction(RestrictionFactory.eq("name", role))); +// FineLoggerFactory.getLogger().info("通过角色名称{} 查询出来的本地角色:{}", role, roleList.size()); +// for (CustomRole customRole : roleList) { +// FineLoggerFactory.getLogger().info("给用户新增角色:{}", customRole.getId()); +// userController.addUserToCustomRole(userId, customRole.getId()); +// } + FineLoggerFactory.getLogger().info("传送过来的角色在本地不存在,添加到本地:{}", role); + CustomRole addRole = new CustomRole(); + addRole.setName(role); + addRole.setAlias(role); + addRole.setId(UUID.randomUUID().toString()); + addRole.setDescription("通过xcontent添加"); + customRoleController.add(addRole); + userController.addUserToCustomRole(userId, addRole.getId()); + } + } + //在判断本地的角色是不是远程没有了,要移除掉 + for (CustomRole customRole : roles) { + if (!remoteRoles.contains(customRole.getName())) { + FineLoggerFactory.getLogger().info("远端没有的角色本地移除:{}", customRole.getName()); + userController.removeUserFromCustomRole(userId, customRole.getId()); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + //如果是管理员标志就直接登录admin账号 + if (isAdminRoleFlag) { + FineLoggerFactory.getLogger().info("本次为管理员登录"); + login(req, res, "admin"); + } else { + FineLoggerFactory.getLogger().info("本次为普通用户登录:{}", usrNm); + login(req, res, usrNm); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static boolean isAdminFlags(String flag, List flags) { + return flags.contains(flag); + } + + private List getAdminFlags() { + InputStream inputStream = ResourceIOUtils.read("/config-all/adminflag.properties"); + if (inputStream != null) { + Properties properties = new Properties(); + try { + properties.load(inputStream); + String adminflag = properties.getProperty("adminflag"); + if (StringUtils.isNotBlank(adminflag)) { + String[] split = adminflag.split(","); + return Arrays.asList(split); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return Collections.emptyList(); + } + + private String getUrl(HttpServletRequest request) { + String url = "/"; + try { + url = "http://" + request.getServerName()//服务器地址 + + ":" + + request.getServerPort() + request.getRequestURI(); + Enumeration parameterNames = request.getParameterNames(); + Map reqParams = new HashMap<>(); + while (parameterNames.hasMoreElements()) { + String key = parameterNames.nextElement(); + reqParams.put(key, request.getParameter(key)); + } + Map parmas = header2url(request); + reqParams.putAll(parmas); + String header2url = map2String(reqParams); + FineLoggerFactory.getLogger().info("转换之后的url参数:{}", header2url); + if (url.contains("?")) { + url += header2url; + } else { + url += "?a=1" + header2url; + } + + } catch (Exception e) { + e.printStackTrace(); + } + url += "&sendTwo=true"; + return url; + } + + + private Map header2url(HttpServletRequest request) { + Map params = new HashMap<>(); + String context = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(context)) { + try { + context = URLDecoder.decode(context, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + JSONObject jsonObject = new JSONObject(context); + String userNo = jsonObject.getString("usrNo"); + String userNm = jsonObject.getString("usrNm"); + String pstNo = jsonObject.getString("pstNo"); + String instNo = jsonObject.getString("instNo"); + String accInstNo = jsonObject.getString("accInstNo"); + String admnInstNo = jsonObject.getString("admnInstNo"); + params.put("usrNo", userNo); + params.put("usrNm", userNm); + params.put("pstNo", pstNo); + params.put("instNo", instNo); + params.put("accInstNo", accInstNo); + params.put("admnInstNo", admnInstNo); + } + params.put("sendTwo", "true"); + return params; + } + + private String map2String(Map params) { + StringBuffer buffer = new StringBuffer(); + Set strings = params.keySet(); + for (String key : strings) { + buffer.append("&").append(key).append("=").append(params.get(key)); + } + return buffer.toString(); + } + +} diff --git a/src/main/java/com/fr/plugin/FRloginFilter.java b/src/main/java/com/fr/plugin/FRloginFilter.java new file mode 100644 index 0000000..82ba782 --- /dev/null +++ b/src/main/java/com/fr/plugin/FRloginFilter.java @@ -0,0 +1,165 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.stable.StringUtils; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@FunctionRecorder(localeKey = "frfilter") +public class FRloginFilter extends AbstractEmbedRequestFilterProvider { + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + @Override + public void filter(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException { + String uri = request.getRequestURI(); + if (uri != null && uri.contains("v5/design/report/share") && request.getMethod().equalsIgnoreCase("GET")) { + FineLoggerFactory.getLogger().info("拦截器捕获一个请求,需要跳转share {}", uri); + if (request.getParameter("sendTwo") != null) { + return; + } + String url = getUrl(request); + FineLoggerFactory.getLogger().info("拦截器捕获一个请求,跳转出来的 {}", url); + sendRedirect(httpServletResponse, url); + } + } + + // +// private String getUrl(HttpServletRequest request){ +// String url = "/"; +// try { +// url = "http://" + request.getServerName()//服务器地址 +// + ":" +// + request.getServerPort() + request.getRequestURI() ; +// String queryString = request.getQueryString(); +// String header2url = header2url(request); +// if (StringUtils.isNotBlank(queryString)) { +// url+= "?"+queryString+header2url; +// }else{ +// url+="?a=1"+header2url; +// } +// if(url.contains("?")){ +// url+="&sendTwo=true"; +// }else{ +// url+="?sendTwo=true"; +// } +// } catch (Exception e) { +// printException2Frlog(e); +// } +// return url; +// } + public static void printException2Frlog(Exception e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } +// +// private String header2url(HttpServletRequest request){ +// StringBuffer buffer=new StringBuffer(); +// String context = request.getHeader("X-Context"); +// if (StringUtils.isNotBlank(context)) { +// try { +// context = URLDecoder.decode(context, "UTF-8"); +// } catch (UnsupportedEncodingException e) { +// e.printStackTrace(); +// } +// JSONObject jsonObject = new JSONObject(context); +// String userNo = jsonObject.getString("usrNo"); +// String userNm = jsonObject.getString("usrNm"); +// String pstNo = jsonObject.getString("pstNo"); +// String instNo = jsonObject.getString("instNo"); +// String accInstNo = jsonObject.getString("accInstNo"); +// String admnInstNo = jsonObject.getString("admnInstNo"); +// buffer.append("&userNo=").append(userNo).append("&userNm=").append(userNm).append("&pstNo=").append(pstNo).append("&instNo=").append(instNo) +// .append("&accInstNo=").append(accInstNo).append("&admnInstNo=").append(admnInstNo); +// } +// +// buffer.append("&sendTwo=true"); +// return buffer.toString(); +// } + + private String getUrl(HttpServletRequest request) { + String url = "/"; + try { + url = "http://" + request.getServerName()//服务器地址 + + ":" + + request.getServerPort() + request.getRequestURI(); + Enumeration parameterNames = request.getParameterNames(); + Map reqParams = new HashMap<>(); + while (parameterNames.hasMoreElements()) { + String key = parameterNames.nextElement(); + reqParams.put(key, request.getParameter(key)); + } + Map parmas = header2url(request); + reqParams.putAll(parmas); + String header2url = map2String(reqParams); + FineLoggerFactory.getLogger().info("转换之后的url参数:{}", header2url); + if (url.contains("?")) { + url += header2url; + } else { + url += "?a=1" + header2url; + } + + } catch (Exception e) { + e.printStackTrace(); + } + url += "&sendTwo=true"; + return url; + } + + + private Map header2url(HttpServletRequest request) { + Map params = new HashMap<>(); + String context = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(context)) { + try { + context = URLDecoder.decode(context, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + JSONObject jsonObject = new JSONObject(context); + String userNo = jsonObject.getString("usrNo"); + String userNm = jsonObject.getString("usrNm"); + String pstNo = jsonObject.getString("pstNo"); + String instNo = jsonObject.getString("instNo"); + String accInstNo = jsonObject.getString("accInstNo"); + String admnInstNo = jsonObject.getString("admnInstNo"); + params.put("usrNo", userNo); + params.put("usrNm", userNm); + params.put("pstNo", pstNo); + params.put("instNo", instNo); + params.put("accInstNo", accInstNo); + params.put("admnInstNo", admnInstNo); + } + params.put("sendTwo", "true"); + return params; + } + + private String map2String(Map params) { + StringBuffer buffer = new StringBuffer(); + Set strings = params.keySet(); + for (String key : strings) { + buffer.append("&").append(key).append("=").append(params.get(key)); + } + return buffer.toString(); + } + +} diff --git a/src/main/java/com/fr/plugin/FilterMeConfig.java b/src/main/java/com/fr/plugin/FilterMeConfig.java new file mode 100644 index 0000000..b72489c --- /dev/null +++ b/src/main/java/com/fr/plugin/FilterMeConfig.java @@ -0,0 +1,160 @@ +package com.fr.plugin; + +import com.fr.config.*; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.web.utils.WebUtils; + +import static com.fr.plugin.PostionImportgHander.saveDep; +import static com.fr.plugin.PostionImportgHander.savePosition; + +@Visualization(category = "系统接口地址配置") +public class FilterMeConfig extends DefaultConfiguration { + private static volatile FilterMeConfig config = null; + + public static FilterMeConfig getInstance() { + if (config == null) { + config = ConfigContext.getConfigInstance(FilterMeConfig.class); + } + return config; + } + + @Identifier(value = "baseUrl", name = "接口地址", description = "", status = Status.SHOW) + private Conf baseUrl = Holders.simple("http://localhost:8080/"); + + @Identifier(value = "adminFlags", name = "管理员标识符rlNoList", description = "使用分号分隔", status = Status.SHOW) + private Conf adminFlags = Holders.simple(""); + + @Identifier(value = "adminFlagsNm", name = "管理员标识符usrNm", description = "使用分号分隔", status = Status.SHOW) + private Conf adminFlagsNm = Holders.simple(""); + + @Identifier(value = "disposeRlNoListFlags", name = "数据处理用户标识符(rlNoList)", description = "使用分号分隔", status = Status.SHOW) + private Conf disposeRlNoListFlags = Holders.simple(""); + + @Identifier(value = "disposeUsrNmFlags", name = "数据处理用户标识符(usrNm)", description = "使用分号分隔", status = Status.SHOW) + private Conf disposeUsrNmFlags = Holders.simple(""); + + @Identifier(value = "analyseRlNoListFlags", name = "数据分析用户标识符(rlNoList)", description = "使用分号分隔", status = Status.SHOW) + private Conf analyseRlNoListFlags = Holders.simple(""); + + @Identifier(value = "analyseDesignFlags", name = "数据分析用户标识符(usrNm)", description = "使用分号分隔", status = Status.SHOW) + private Conf analyseDesignFlags = Holders.simple(""); + + @Identifier(value = "adminImport", name = "导入机构部门角色", description = "导入机构部门角色", status = Status.SHOW) + private Conf adminImport = Holders.simple(""); + + @Identifier(value = "needflush", name = "是否需要刷新权限", description = "使用分号分隔", status = Status.SHOW) + private Conf needflush = Holders.simple(true); + + public String getDisposeRlNoListFlags() { + return disposeRlNoListFlags.get(); + } + + public void setDisposeRlNoListFlags(String disposeRlNoListFlags) { + this.disposeRlNoListFlags.set(disposeRlNoListFlags); + } + + public String getDisposeUsrNmFlags() { + return disposeUsrNmFlags.get(); + } + + public void setDisposeUsrNmFlags(String disposeUsrNmFlags) { + this.disposeUsrNmFlags.set(disposeUsrNmFlags); + } + + public String getAnalyseRlNoListFlags() { + return analyseRlNoListFlags.get(); + } + + public void setAnalyseRlNoListFlags(String analyseRlNoListFlags) { + this.analyseRlNoListFlags .set(analyseRlNoListFlags); + } + + public String getAnalyseDesignFlags() { + return analyseDesignFlags.get(); + } + + public void setAnalyseDesignFlags(String analyseDesignFlags) { + this.analyseDesignFlags .set(analyseDesignFlags); + } + + public String getAdminImport() { + return adminImport.get(); + } + + public void setAdminImport( String adminImport) { + //执行导入操作 + this.adminImport.set(""); + try { + FineLoggerFactory.getLogger().error("----------------开始保存管理员标识:{}",adminImport); + JSONArray jsonArray = new JSONArray(adminImport); + int length = jsonArray.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String fInstNo = jsonObject.getString("fInstNo"); + String instNo = jsonObject.getString("instNo");//部门id + String instNm = jsonObject.getString("instNm");//部门名称 + String pstNo = jsonObject.getString("pstNo");//职务编号 + String pstNm = jsonObject.getString("pstNm");//职务名 + saveDep(fInstNo, instNo, instNm); + savePosition(instNo, pstNo, pstNm); + } + FineLoggerFactory.getLogger().error("+++++++++++++++结束保存管理员标识:{}",adminImport); + } catch (Exception e) { + FineLoggerFactory.getLogger().error("====++++通过配置接口导入数据出错:",e); + } + } + + public String getAdminFlags() { + return adminFlags.get(); + } + + public void setAdminFlags(String adminFlags) { + this.adminFlags.set(adminFlags); + } + + + public String getBaseUrl() { + return baseUrl.get(); + } + + + public void setBaseUrl(String baseUrl) { + this.baseUrl.set(baseUrl); + } + + public String getAdminFlagsNm() { + return adminFlagsNm.get(); + } + + public void setAdminFlagsNm(String adminFlagsNm) { + this.adminFlagsNm.set(adminFlagsNm); + } + + public Boolean getNeedflush() { + return needflush.get(); + } + + public void setNeedflush(Boolean needflush) { + this.needflush.set(needflush); + } + + @Override + public Object clone() throws CloneNotSupportedException { + FilterMeConfig cloned = (FilterMeConfig) super.clone(); + cloned.baseUrl = (Conf) baseUrl.clone(); + cloned.adminFlags = (Conf) adminFlags.clone(); + cloned.adminFlagsNm = (Conf) adminFlagsNm.clone(); +// cloned.designFlags = (Conf) designFlags.clone(); + cloned.disposeRlNoListFlags = (Conf) disposeRlNoListFlags.clone(); + cloned.disposeUsrNmFlags = (Conf) disposeUsrNmFlags.clone(); + cloned.analyseRlNoListFlags = (Conf) analyseRlNoListFlags.clone(); + cloned.analyseDesignFlags = (Conf) analyseDesignFlags.clone(); + cloned.needflush = (Conf) needflush.clone(); + cloned.adminImport = (Conf) adminImport.clone(); + return cloned; + } +} diff --git a/src/main/java/com/fr/plugin/GlobalRequestFilterPlaceHolder.java b/src/main/java/com/fr/plugin/GlobalRequestFilterPlaceHolder.java new file mode 100644 index 0000000..a984d0a --- /dev/null +++ b/src/main/java/com/fr/plugin/GlobalRequestFilterPlaceHolder.java @@ -0,0 +1,110 @@ +package com.fr.plugin; + +import com.fr.decision.ExtraDecisionClassManager; +import com.fr.decision.fun.GlobalRequestFilterProvider; +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.event.Event; +import com.fr.event.EventDispatcher; +import com.fr.event.Listener; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.context.PluginContext; +import com.fr.plugin.injectable.PluginModule; +import com.fr.plugin.observer.PluginEventType; +import com.fr.stable.StringUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Set; + +/** + * @author Peng + */ +public class GlobalRequestFilterPlaceHolder extends AbstractGlobalRequestFilterProvider { + private static final String CURRENT_PLUGIN_ID = "com.fr.plugin.sln5591";//需要求个这两个配置 + private static final String CURRENT_FILTER_NAME = "lastLogin"; + private static GlobalRequestFilterProvider PLACE_HOLDER_IMPL_FILTER; + + @Override + public void init(FilterConfig filterConfig) { + Set providers = ExtraDecisionClassManager.getInstance().getArray(GlobalRequestFilterProvider.MARK_STRING); + if (providers != null) { + for (GlobalRequestFilterProvider provider : providers) { + String filterName = provider.filterName(); + if (StringUtils.isNotEmpty(filterName) && CURRENT_FILTER_NAME.equals(filterName)) { + PLACE_HOLDER_IMPL_FILTER = provider; + break; + } + } + } + + com.fr.stable.Filter filter = new com.fr.stable.Filter() { + @Override + public boolean accept(PluginContext context) { + String pluginId = context.getID(); + return context.contain(PluginModule.ExtraDecision, GlobalRequestFilterProvider.MARK_STRING) && CURRENT_PLUGIN_ID.equals(pluginId); + } + }; + + EventDispatcher.listen(PluginEventType.AfterRun, new Listener() { + @Override + public void on(Event event, PluginContext context) { + Set providers = context.getRuntime().get(PluginModule.ExtraDecision, GlobalRequestFilterProvider.MARK_STRING); + if (providers != null) { + for (GlobalRequestFilterProvider provider : providers) { + String filterName = provider.filterName(); + if (StringUtils.isNotEmpty(filterName) && CURRENT_FILTER_NAME.equals(filterName)) { + PLACE_HOLDER_IMPL_FILTER = provider; + break; + } + } + } + } + }, filter); + + EventDispatcher.listen(PluginEventType.BeforeStop, new Listener() { + @Override + public void on(Event event, PluginContext context) { + PLACE_HOLDER_IMPL_FILTER = null; + } + }, filter); + } + + @Override + public String filterName() { + return "GlobalRequestFilterPlaceHolder"; + } + + @Override + public String[] urlPatterns() { + return new String[]{ + "/*" + }; + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { + try { + process(request, response, filterChain); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e, e.getMessage()); + try { + filterChain.doFilter(request, response); + } catch (Exception ex) { + FineLoggerFactory.getLogger().error(ex, ex.getMessage()); + } + } + } + + public void process(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws Exception { + if (PLACE_HOLDER_IMPL_FILTER == null) { + if(FineLoggerFactory.getLogger().isDebugEnabled()) { + FineLoggerFactory.getLogger().debug("[GlobalRequestFilterPlaceHolder] placeHolderImplFilter 为 null"); + } + filterChain.doFilter(request, response); + } else { + PLACE_HOLDER_IMPL_FILTER.doFilter(request, response, filterChain); + } + } +} diff --git a/src/main/java/com/fr/plugin/HttpApi.java b/src/main/java/com/fr/plugin/HttpApi.java new file mode 100644 index 0000000..ec13789 --- /dev/null +++ b/src/main/java/com/fr/plugin/HttpApi.java @@ -0,0 +1,130 @@ +package com.fr.plugin; + +import com.fr.base.ServerConfig; +import com.fr.json.JSONObject; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class HttpApi { + + + + private static String getParam(Map var0, String enc) { + String var1 = ""; + Set var2 = var0.keySet(); + Iterator var3 = var2.iterator(); + + while (var3.hasNext()) { + String var4 = (String) var3.next(); + String var5 = var0.get(var4) + ""; + + try { + var1 = var1 + (var1.length() == 0 ? "" : "&") + URLEncoder.encode(var4, enc) + "=" + URLEncoder.encode(var5, enc); + } catch (Exception var7) { + ; + } + } + + return var1; + } + public static String post(String path, Map param) { + String var3 = getParam(param, ServerConfig.getInstance().getServerCharset()); + PrintWriter var4 = null; + BufferedReader var5 = null; + String var6 = ""; + + try { + URL var7 = new URL(path); + HttpURLConnection var8 = (HttpURLConnection) var7.openConnection(); + var8.setRequestProperty("accept", "*/*"); + var8.setRequestProperty("connection", "Keep-Alive"); + var8.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); +// var8.setRequestProperty("Accept-Charset", "UTF-8"); + var8.setRequestMethod("POST"); + var8.setDoOutput(true); + var8.setDoInput(true); + var4 = new PrintWriter(var8.getOutputStream()); + var4.print(var3); + var4.flush(); + + String var9; + for (var5 = new BufferedReader(new InputStreamReader(var8.getInputStream(), "UTF-8")); (var9 = var5.readLine()) != null; var6 = var6 + var9) { + ; + } + } catch (Exception var18) { + var18.printStackTrace(); + } finally { + try { + if (var4 != null) { + var4.close(); + } + + if (var5 != null) { + var5.close(); + } + } catch (Exception var17) { + ; + } + + } + + return var6; + } + + public static String sendJsonPost(String var0, JSONObject var1, String var2) { + PrintWriter var3 = null; + BufferedReader var4 = null; + HttpURLConnection var5 = null; + String var6 = ""; + + try { + URL var7 = new URL(var0); + var5 = (HttpURLConnection) var7.openConnection(); + var5.setRequestProperty("Content-Type", "application/json;charset=utf8"); +// var5.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf8"); + var5.setRequestProperty("accept", "*/*"); + var5.setRequestProperty("connection", "Keep-Alive"); + var5.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + var5.setRequestProperty("Accept-Charset", var2); + var5.setRequestMethod("POST"); + var5.setDoOutput(true); + var5.setDoInput(true); + var3 = new PrintWriter(var5.getOutputStream()); + var3.print(var1.toString()); + var3.flush(); + + String var8; + for (var4 = new BufferedReader(new InputStreamReader(var5.getInputStream(), var2)); (var8 = var4.readLine()) != null; var6 = var6 + var8) { + ; + } + } catch (Exception var17) { + var17.printStackTrace(); + } finally { + try { + if (var3 != null) { + var3.close(); + } + + if (var4 != null) { + var4.close(); + } + } catch (Exception var16) { + ; + } + + var5.disconnect(); + } + + System.out.println(var6); + return var6; + } + +} diff --git a/src/main/java/com/fr/plugin/IpUtils.java b/src/main/java/com/fr/plugin/IpUtils.java new file mode 100644 index 0000000..6bfe7ba --- /dev/null +++ b/src/main/java/com/fr/plugin/IpUtils.java @@ -0,0 +1,62 @@ +package com.fr.plugin; + +import javax.servlet.http.HttpServletRequest; + +public class IpUtils { + /** + * 将字符串形式IP地址127.0.0.1转换10234564321 + * + * @param strIP + * @return + */ + public static long ip2Long(String strIP) { + long[] ip = new long[4]; +// 先找到IP地址字符串中.的位置 + int position1 = strIP.indexOf("."); + int position2 = strIP.indexOf(".", position1 + 1); + int position3 = strIP.indexOf(".", position2 + 1); +// 将每个.之间的字符串转换成整型 + ip[0] = Long.parseLong(strIP.substring(0, position1)); + ip[1] = Long.parseLong(strIP.substring(position1 + 1, position2)); + ip[2] = Long.parseLong(strIP.substring(position2 + 1, position3)); + ip[3] = Long.parseLong(strIP.substring(position3 + 1)); + return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3]; + } + + public static String getIp(HttpServletRequest request) { + String remoteIp = request.getRemoteAddr(); + + // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if (remoteIp != null && remoteIp.length() > 15) { + if (remoteIp.indexOf(",") > 0) { + remoteIp = remoteIp.substring(0, remoteIp.indexOf(",")); + } + } + return remoteIp; + } + + /** + * 将字符串形式IP地址转换long类型 + * @param ip + * @return + */ + public static long getIp2long(String ip) { + ip = ip.trim(); + String[] ips = ip.split("\\."); + long ip1 = Integer.parseInt(ips[0]); + long ip2 = Integer.parseInt(ips[1]); + long ip3 = Integer.parseInt(ips[2]); + long ip4 = Integer.parseInt(ips[3]); + return ip1 * 256 * 256 * 256 + ip2 * 256 * 256 + ip3 * 256 + ip4; + } + /** + * 判断一个ip地址是否在某个ip段范围内 + * @param ip + * @param startIP + * @param endIP + * @return + */ + public static boolean ipExistsInRange(String ip, String startIP, String endIP) { + return (getIp2long(startIP)<=getIp2long(ip)) && (getIp2long(ip)<=getIp2long(endIP)); + } +} diff --git a/src/main/java/com/fr/plugin/JHHttpHander.java b/src/main/java/com/fr/plugin/JHHttpHander.java new file mode 100644 index 0000000..454cde1 --- /dev/null +++ b/src/main/java/com/fr/plugin/JHHttpHander.java @@ -0,0 +1,14 @@ +package com.fr.plugin; + +import com.fr.decision.fun.HttpHandler; +import com.fr.decision.fun.impl.AbstractHttpHandlerProvider; + +public class JHHttpHander extends AbstractHttpHandlerProvider { + + private HttpHandler[] actions = new HttpHandler[]{new ChangeConfigHander() ,new ChangeDesignConfigHander(),new PostionImportgHander() }; + + @Override + public HttpHandler[] registerHandlers() { + return actions; + } +} diff --git a/src/main/java/com/fr/plugin/JHUrlAliasProvider.java b/src/main/java/com/fr/plugin/JHUrlAliasProvider.java new file mode 100644 index 0000000..cd673ad --- /dev/null +++ b/src/main/java/com/fr/plugin/JHUrlAliasProvider.java @@ -0,0 +1,17 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.AbstractURLAliasProvider; +import com.fr.decision.webservice.url.alias.URLAlias; +import com.fr.decision.webservice.url.alias.URLAliasFactory; + +public class JHUrlAliasProvider extends AbstractURLAliasProvider { + @Override + public URLAlias[] registerAlias() { + return new URLAlias[]{ + //第一个参数就是就是别名地址,第二个就是HttpHandler中设置的地址,第三个参数就是设置该请求是否公开(需要和对应的HttpHandler.isPublic方法返回一直,要不然会报错404),返回的是一个非通配 + URLAliasFactory.createPluginAlias("/config/admin", "/change1", true), + URLAliasFactory.createPluginAlias("/config/import", "/depAndPosImport", true), + URLAliasFactory.createPluginAlias("/config/design", "/change2", true) + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/JSONUtils.java b/src/main/java/com/fr/plugin/JSONUtils.java new file mode 100644 index 0000000..fc66f61 --- /dev/null +++ b/src/main/java/com/fr/plugin/JSONUtils.java @@ -0,0 +1,28 @@ +package com.fr.plugin; + +import com.fr.third.fasterxml.jackson.core.JsonGenerationException; +import com.fr.third.fasterxml.jackson.databind.JsonMappingException; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; + +/** + * JSON序列化和反序列化相关操作类 + */ +public class JSONUtils { + private static ObjectMapper objectMapper = new ObjectMapper(); + + public static String serialize(Object object) { + Writer write = new StringWriter(); + try { + objectMapper.writeValue(write, object); + } catch (JsonGenerationException e) { + } catch (JsonMappingException e) { + } catch (IOException e) { + } + return write.toString(); + } + +} diff --git a/src/main/java/com/fr/plugin/LastloginFilter.java b/src/main/java/com/fr/plugin/LastloginFilter.java new file mode 100644 index 0000000..10c5ae8 --- /dev/null +++ b/src/main/java/com/fr/plugin/LastloginFilter.java @@ -0,0 +1,794 @@ +package com.fr.plugin; + +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.DepartmentController; +import com.fr.decision.authority.controller.PostController; +import com.fr.decision.authority.controller.UserController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.Department; +import com.fr.decision.authority.data.Post; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.decision.privilege.encrpt.PasswordValidator; +import com.fr.decision.webservice.impl.user.type.UserProductType; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.general.ComparatorUtils; +import com.fr.general.IOUtils; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.plugin.util.LogUtils; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; +import com.fr.stable.query.restriction.RestrictionFactory; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.*; +import java.net.URLDecoder; +import java.util.*; + +@FunctionRecorder(localeKey = "fr2") +public class LastloginFilter extends AbstractGlobalRequestFilterProvider { + @Override + public String filterName() { + return "lastLogin"; + } + + @Override + public String[] urlPatterns() { + return new String[]{ + "/oookkkk" + }; + } + + @Override + public void init(FilterConfig filterConfig) { + FilterMeConfig.getInstance(); + super.init(filterConfig); + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse httpServletResponse, FilterChain filterChain) { + String uri = request.getRequestURI(); + String ip = com.fr.plugin.IpUtils.getIp(request); + String header = request.getHeader("X-Context"); +// try { +// FineLoggerFactory.getLogger().info("Context:{}", URLDecoder.decode(header, "utf-8")); +// } catch (UnsupportedEncodingException e) { +// e.printStackTrace(); +// } +// printCookies(request); +// FineLoggerFactory.getLogger().info("请求url:{}", WebUtils.getOriginalURL(request)); + if (isRemoteDesignRequest(request) || isResourceFileRequest(request)) { + try { + filterChain.doFilter(request, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + return; + } + + if (!ipblackCheck(ip)) { + writerOurError(httpServletResponse); + return; + } + + if (request.getMethod().equalsIgnoreCase("GET")) { + + if (StringUtils.isNotBlank(header)) { + XContentRequestWrapper wrapper = new XContentRequestWrapper(request); +// wrapper.getParameterMap(); +// String a = wrapper.getParameter("a"); +// wrapper.getParameterNames(); +// FineLoggerFactory.getLogger().error("获取的A :{} ", a); + if (isLogin(wrapper)) { +// String usrNm = wrapper.getParameter("usrNm"); + //从2021-01-29开始的需求, SLN-6585开始修改用户名使用userNo代替 + String usrNo = wrapper.getParameter("usrNo"); + String instNo = wrapper.getParameter("instNo"); + String realUserName = usrNo + "_" + instNo; +// FineLoggerFactory.getLogger().info("登录信息:{}", realUserName); + User logindUser = null; + try { + logindUser = UserService.getInstance().getUserByRequestCookie(request); + UserService instance = UserService.getInstance(); +// FineLoggerFactory.getLogger().info("当前登录的用户名称:{}", logindUser.getUserName()); + //如果登录过并且不是管理员就判断是否是同一个用户 + if (!instance.isAdmin(logindUser.getId())) { + //如果登录的用户和content的中不一致则切换登录用户 + if (!ComparatorUtils.equals(logindUser.getUserName(), realUserName)) { + FineLoggerFactory.getLogger().info("当前登录的用户和xcontent的不一致,切换用户为:{}", realUserName); + tokenLogin(request, httpServletResponse); + } else { + //如果一致则判断角色是否变化 + try { + FineLoggerFactory.getLogger().info("当前用户已经登录,进行权限适配:{}", realUserName); + dealCustomRole(wrapper, logindUser.getId()); + } catch (Exception e) { + printException2FrLog(e); + } + } + } + } catch (Exception e) { + printException2FrLog(e); + } + } else { + FineLoggerFactory.getLogger().info("xc1当前未登录开始登录"); + tokenLogin(request, httpServletResponse); + } + try { + filterChain.doFilter(wrapper, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + return; + } + } + try { + filterChain.doFilter(request, httpServletResponse); + } catch (IOException e) { + printException2FrLog(e); + } catch (ServletException e) { + printException2FrLog(e); + } + } + +// private void printCookies(HttpServletRequest request) { +// Cookie[] cookies = request.getCookies(); +// if (null != cookies) { +// for (Cookie cookie : cookies) { +// FineLoggerFactory.getLogger().info("收到的cookies name {} value:{}", cookie.getName(), cookie.getValue()); +// } +// } +// } + + /** + * 判断是否是远程设计请求 + */ + private boolean isRemoteDesignRequest(HttpServletRequest request) { + String pathInfo = request.getPathInfo(); + return StringUtils.isNotEmpty(pathInfo) && pathInfo.startsWith("/remote/design"); + } + + + private void adminLogin(HttpServletRequest request) { + + } + + /** + * 判断是否是资源文件请求 + */ + private boolean isResourceFileRequest(HttpServletRequest request) { + String pathInfo = request.getPathInfo(); + if (StringUtils.isEmpty(pathInfo)) { + return false; + } + + return pathInfo.startsWith("/resources") || pathInfo.startsWith("/file"); + } + + public static void printException2FrLog(Throwable e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + + private static void login(HttpServletRequest req, HttpServletResponse res, String username) { + HttpSession session = req.getSession(true); + String token = null; + try { + token = LoginService.getInstance().login(req, res, username); + req.setAttribute("fine_auth_token", token); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + FineLoggerFactory.getLogger().error("login failed"); + } + FineLoggerFactory.getLogger().error("login success"); + } + + private boolean ipblackCheck(String ip) { + String[] blackIps = new String[]{}; + try { + InputStream inputStream = ResourceIOUtils.read("/config-all/whitelist.properties"); + if (inputStream != null) { + String lines = IOUtils.inputStream2String(inputStream); + blackIps = lines.split("\n"); + } + } catch (IOException e) { + LogUtils.error(e.getMessage(), e); + } + if ("0:0:0:0:0:0:0:1".equals(ip)) { +// LogUtils.info("IP 地址为服务器本机 IPv6 地址: {},直接放行。", ip); + return true; + } + if ("127.0.0.1".equals(ip)) { +// LogUtils.info("IP 地址为服务器本机 IPv4 地址: {},直接放行。", ip); + return true; + } + for (String blackIp : blackIps) { + if (!blackIp.contains("-")) { + if (ip.equals(blackIp)) { + return true; + } + } else { + String trim = blackIp.trim(); + String[] split = trim.split("-"); + if (split.length > 1) { + if (com.fr.plugin.IpUtils.ipExistsInRange(ip, split[0], split[1])) { + return true; + } + } + } + } + return false; + } + + private void writerOurError(HttpServletResponse httpServletResponse) { + try { + WebUtils.writeOutTemplate("/com/fr/plugin/error.html", httpServletResponse, new HashMap()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private boolean isLogin(HttpServletRequest req) { + return LoginService.getInstance().isLogged(req); + } + +// public void filter(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException { +// String uri = request.getRequestURI(); +// String ip = IpUtils.getIp(request); +// if (!ipblackCheck(ip)) { +// writerOurError(httpServletResponse); +// return; +// } +// if (request.getMethod().equalsIgnoreCase("GET")) { +// String header = request.getHeader("X-Context"); +// if (StringUtils.isNotBlank(header)) { +// XContentRequestWrapper wrapper = new XContentRequestWrapper(request); +//// String a = wrapper.getParameter("a"); +//// FineLoggerFactory.getLogger().error("获取的A :{} ", a); +// if (isLogin(wrapper)) { +// String usrNm = wrapper.getParameter("usrNm"); +// String userNameFromRequest = LoginService.getInstance().getUserNameFromRequestCookie(request); +// //如果登录的用户和content的中不一致则切换登录用户 +// if (!ComparatorUtils.equals(userNameFromRequest, usrNm)) { +// tokenLogin(request, httpServletResponse); +// } +// } else { +// tokenLogin(request, httpServletResponse); +// return; +// } +// +// String loginUrl = request.getContextPath() + request.getServletPath() + "/login"; +// String home = request.getContextPath() + request.getServletPath() + "?sendTwo=true"; +// if (loginUrl.equals(uri)) { +// String manualOut = request.getParameter("manual"); +// if (!ComparatorUtils.equals(manualOut, "true")) { +//// String header = request.getHeader("X-Context"); +// if (StringUtils.isNotBlank(header) && !isLogin(request)) { +// tokenLogin(request, httpServletResponse); +// sendRedirect(httpServletResponse, home); +// } +// } +// } +// } +// } +// return; +// } + + public static void tokenLogin(HttpServletRequest req, HttpServletResponse res) { + String header = req.getHeader("X-Context"); + try { + header = URLDecoder.decode(header, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().info("xf拦截器捕获一个请求 x-content :{}", header); + JSONObject entries = new JSONObject(header); + JSONArray rlNoList = entries.getJSONArray("rlNoList"); + int size = rlNoList.size(); + List adminFlags = getAdminFlagsRolList(); + boolean isAdmin = false; + for (int i = 0; i < size; i++) { + String adminPost = rlNoList.getString(i); + if (isAdminFlags(adminPost, adminFlags)) { + FineLoggerFactory.getLogger().info("本次使用rlNoList管理员登录 "); + isAdmin = true; + break; + } + } + List adminFlagsNmList = getAdminFlagsNmList(); + String usrNm = entries.getString("usrNm"); + for (String adminName : adminFlagsNmList) { + if (StringUtils.equals(adminName, usrNm)) { + FineLoggerFactory.getLogger().info("本次使用usrNm管理员登录 "); + isAdmin = true; + break; + } + } + if (isAdmin) { + FineLoggerFactory.getLogger().info("本次为管理员登录 "); + login(req, res, "admin"); + return; + } +// String usrNm = entries.getString("usrNm"); + //从 SLN-6585开始usrNm更新成userNo + //如果usrNo的用户名存在则把这个用户的userName改成realUserName + String usrNo = entries.getString("usrNo"); + String instNo = entries.getString("instNo");//部门id + String realUserName = usrNo + "_" + instNo;//更改后的用户名 + String fullName = entries.getString("fullName"); + String alias = fullName + "(" + realUserName + ")"; + try { + User user = UserService.getInstance().getUserByUserName(usrNo); + UserController userController = AuthorityContext.getInstance().getUserController(); + if (user == null) { + //如果usrNo的用户不存在则检查realUserName的用户是否存在 + user = UserService.getInstance().getUserByUserName(realUserName); + if (user == null) { +// FineLoggerFactory.getLogger().info("拦截器新增一个用户 用户名称 :{}", realUserName); + PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(ManualOperationType.KEY).getPasswordValidator(); + user = (new User()).userName(realUserName).userAlias(alias).realName(fullName).password(passwordValidator.encode(realUserName, UUID.randomUUID().toString())) + .creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + userController.add(user); + } + } +// FineLoggerFactory.getLogger().info(" 拦截的用户更新:{}", fullName); + user.setRealName(fullName); + user.setUserName(realUserName); + try { + userController.update(user); + } catch (Exception e) { + } + String userId = user.getId(); + dealCustomRole(req, userId); + +// CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); +// List roles = customRoleController.findByUser(userId, QueryFactory.create()); + + login(req, res, realUserName); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static List getUserRlNoListAnalyseFlags() { + String adminFlags = FilterMeConfig.getInstance().getAnalyseRlNoListFlags(); + FineLoggerFactory.getLogger().info("本地分析角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } + + private static List getUserProcessFlags() { + String adminFlags = FilterMeConfig.getInstance().getDisposeRlNoListFlags(); + FineLoggerFactory.getLogger().info("本地数据处理角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } + + private static List getAnalyseFlagsNmList() { + String adminFlags = FilterMeConfig.getInstance().getAnalyseDesignFlags(); + FineLoggerFactory.getLogger().info("本地analyseNm角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } + + private static List getProcessFlagsNmList() { + String adminFlags = FilterMeConfig.getInstance().getDisposeUsrNmFlags(); + FineLoggerFactory.getLogger().info("本地analyseNm角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } + + private static void dealUserBiProductType(List list, String userName, String userId, int uProductType) { + boolean isDesignUser = false; + UserController userController = AuthorityContext.getInstance().getUserController(); + if (!list.isEmpty()) { + for (String nm : list) { + if (StringUtils.equals(nm, userName)) { + isDesignUser = true; + break; + } + } + } + + + try { + UserProductType userProductType = UserProductType.fromInteger(uProductType);//数据处理用户 + if (isDesignUser) { + userController.addUserProductType(userId, userProductType.transProductKey()); + } else { + userController.removeUserProductType(userId, userProductType.transProductKey()); + } + } catch (Exception e) { + } + } + + private static void dealUserBiProductType(List usrNmList, List rolesLists, JSONArray rlNoList, String userId, String usrNm, int uProductType) { + boolean isDesignUser = false; + UserController userController = AuthorityContext.getInstance().getUserController(); + FineLoggerFactory.getLogger().info("远程rolist:{} 本地:{}", rlNoList, rolesLists); + if (!rolesLists.isEmpty()) { + for (int i = 0; i < rlNoList.size(); i++) { + String name = rlNoList.getString(i); + for (String designFlag : rolesLists) { + if (StringUtils.equals(designFlag, name)) { + isDesignUser = true; + break; + } + } + } + } + FineLoggerFactory.getLogger().info("通过rolList判断是否为对应用户:{}", isDesignUser); + if (!isDesignUser) { + for (String name : usrNmList) { + if (StringUtils.equals(name, usrNm)) { + isDesignUser = true; + break; + } + } + } + FineLoggerFactory.getLogger().info("通过usrNm判断是否为对应用户:{}", isDesignUser); + try { + UserProductType userProductType = UserProductType.fromInteger(uProductType);//数据处理用户 + if (isDesignUser) { + userController.addUserProductType(userId, userProductType.transProductKey()); + } else { + userController.removeUserProductType(userId, userProductType.transProductKey()); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error("添加执行权限失败:", e); + printException2FrLog(e); + } + } + + private interface BIProductType { + int BIDataMiningProductType = 7; + int BIDataAnalysisProductType = 5; + int BIDataProcessProductType = 6; + int BIDesignProductType = 3; + int BIViewProductType = 2; + int DefaultUserProductType = 0; + int DecisionUserProductType = 1; + int MobileUserProductType = 4; + } + + private static void dealCustomRole(HttpServletRequest req, String userId) throws Exception { + UserController userController = AuthorityContext.getInstance().getUserController(); + Boolean needflush = FilterMeConfig.getInstance().getNeedflush(); + String header = req.getHeader("X-Context"); + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + try { + header = URLDecoder.decode(header, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().info("开始同步角色信息x-content :{}", header); + JSONObject entries = new JSONObject(header); + List remoteRoles = new ArrayList<>(); + String pstNo = entries.getString("pstNo");//角色id + String pstNm = entries.getString("pstNm");//角色名称 + String usrNm = entries.getString("usrNm");//用户名 + String realRoleName = pstNm + "(" + pstNo + ")"; + + if (StringUtils.isNotBlank(pstNo)) { +//先判断传过来的的角色是不是本地都有,没有要加上 + List controllerOne = customRoleController.find(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pstNo))); + if (controllerOne.isEmpty()) { + CustomRole addRole = new CustomRole(); + addRole.setId(pstNo); + addRole.setName(realRoleName); + if (StringUtils.isBlank(pstNm)) { + addRole.setName(realRoleName); + } + addRole.setAlias(pstNo); + addRole.setEnable(true); + addRole.setDescription("通过xcontent添加"); + try { + customRoleController.add(addRole); + } catch (Exception e) { + } + } else { + try { +// FineLoggerFactory.getLogger().info("传送过来的角色已存在开始更新,添加关联用户:{}", pstNo, userId); + CustomRole customRole = controllerOne.get(0); + customRole.setName(realRoleName); + if (StringUtils.isBlank(pstNm)) { + customRole.setName(realRoleName); + } + customRole.setAlias(pstNo); + customRole.setEnable(true); + customRole.setDescription("通过xcontent添加"); + customRoleController.update(customRole); + } catch (Exception e) { + } +// FineLoggerFactory.getLogger().info("传送过来的角色在本地不存在,添加到本地:{}", pstNo); + } + //在判断本地的角色是不是远程没有了,要移除掉 + List thatUserAllRole = customRoleController.findByUser(userId, QueryFactory.create()); + if (needflush) { + List roles = thatUserAllRole; + for (CustomRole customRole : roles) { + if ("superusers".equals((customRole.getName()))) { + continue; + } + userController.removeUserFromCustomRole(userId, customRole.getId()); + } + } + + try { + boolean needAdd = false; + for (CustomRole customRole : thatUserAllRole) { + if (StringUtils.equals(customRole.getId(), pstNo)) { + needAdd = true; + break; + } + } + if (!needAdd) { + userController.addUserToCustomRole(userId, pstNo); + } + } catch (Exception e) { + + } + } + JSONArray rlNoList = entries.getJSONArray("rlNoList");//角色列表 + remoteRoles.add(pstNo); + try { + + + DepartmentController departmentController = AuthorityContext.getInstance().getDepartmentController(); + + String instNm = entries.getString("instNm"); + String instNo = entries.getString("instNo");//部门id + String pInstNo = entries.getString("pInstNo");//父级部门id + if (StringUtils.isNotBlank(instNo)) { + //移除原来的部门 + if (needflush) { + List departmentList = departmentController.findByUser(userId, QueryFactory.create()); + for (Department department : departmentList) { + userController.removeUserFromDepartment(userId, department.getId()); + } + } + Department department = departmentController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", instNo))); + if (department == null) { + department = new Department(); + department.setName(instNm + "(" + instNo + ")"); + if (StringUtils.isNotBlank(instNm)) { + department.setName(instNo); + } + department.setId(instNo); + department.setEnable(true); + if (StringUtils.isNotBlank(pInstNo)) { + department.setParentId(pInstNo); + } + department.description("通过x-content创建"); + departmentController.add(department); +// FineLoggerFactory.getLogger().info("新增部门,dep: {}: name {}", department.getId(), department.getName()); + } else { + if (StringUtils.isNotBlank(instNm)) { + department.setName(instNm + "(" + instNo + ")"); + FineLoggerFactory.getLogger().info("已存在部门,dep: {}: name {}", department.getId(), department.getName()); + } + if (StringUtils.isNotBlank(pInstNo)) { + department.setParentId(pInstNo); + FineLoggerFactory.getLogger().info("已存在部门,dep: {}: name {}更新父级id:{}", department.getId(), department.getName(), pInstNo); + } + department.setEnable(true); + departmentController.update(department); + } + } + + + PostController postController = AuthorityContext.getInstance().getPostController(); + List byUser = postController.findByUser(userId, QueryFactory.create()); + //移除原来职位 + if (needflush) { + for (Post post : byUser) { + List byPost = departmentController.findByPost(post.getId(), QueryFactory.create()); + for (Department department1 : byPost) { +// FineLoggerFactory.getLogger().info("---->>>>移除用户职位 职位id {} 部门id{}", post.getId(), department1.getId()); + try { + userController.removeUserFromDepartmentAndPost(userId, department1.getId(), post.getId()); +// FineLoggerFactory.getLogger().info("^^^^^^移除用户职位成功 职位id {} 部门id{}", post.getId(), department1.getId()); + } catch (Exception e) { +// FineLoggerFactory.getLogger().info("vvvvvv移除用户职位失败 职位id {} 部门id{}", post.getId(), department1.getId()); + } + } + } + } + + if (StringUtils.isNotBlank(pstNo)) { + Post post = postController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pstNo))); + if (post == null) { + post = new Post(); + post.setId(pstNo); + if (StringUtils.isNotBlank(pstNm)) { + post.setName(pstNm + "(" + pstNo + ")"); + } else { + post.setName(pstNo); + } + post.setEnable(true); + postController.add(post); + FineLoggerFactory.getLogger().info("新增职位,post: {}: name {}", post.getId(), post.getName()); + } else { + if (StringUtils.isNotBlank(pstNm)) { + post.setName(pstNm + "(" + pstNo + ")"); + } else { + post.setName(pstNo); + } + post.setEnable(true); + postController.update(post); + FineLoggerFactory.getLogger().info("已存在职位,post: {}: name {}", post.getId(), post.getName()); + } + try { + List byDepartment = postController.findByDepartment(instNo, QueryFactory.create()); + boolean needAddMe = false; + for (Post me : byDepartment) { + if (StringUtils.equals(me.getId(), pstNo)) { + needAddMe = true; + break; + } + } + if (!needAddMe) { + postController.addPostToDepartment(pstNo, instNo); + FineLoggerFactory.getLogger().info("添加职位到部门,post: {}: dep {}", pstNo, instNo); + } + } catch (Exception e) { + } + try { +// FineLoggerFactory.getLogger().info("添加用户到职位 到部门,post: {}: dep {},userId:{} ", pstNo, instNo, realRoleName); + userController.addUserToDepartmentAndPost(userId, instNo, pstNo); + } catch (Exception e) { + } + } + + List analyseFlagsRolList = getUserRlNoListAnalyseFlags(); + List analyseFlagsNmList = getAnalyseFlagsNmList(); + dealUserBiProductType(analyseFlagsNmList, analyseFlagsRolList, rlNoList, userId, usrNm, BIProductType.BIDataAnalysisProductType); + + List processFlagsRolist = getUserProcessFlags(); + List processFlagsNmList = getProcessFlagsNmList(); + dealUserBiProductType(processFlagsNmList, processFlagsRolist, rlNoList, userId, usrNm, BIProductType.BIDataProcessProductType); + } catch (Exception e) { + printException2FrLog(e); + } + } + + private static boolean isAdminFlags(String flag, List flags) { + return flags.contains(flag); + } + + private static List getAdminFlagsRolList() { + String adminFlags = FilterMeConfig.getInstance().getAdminFlags(); + FineLoggerFactory.getLogger().info("本地role管理员角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } + + private static List getAdminFlagsNmList() { + String adminFlags = FilterMeConfig.getInstance().getAdminFlagsNm(); + FineLoggerFactory.getLogger().info("本地管理员Nm角色获取:{}", adminFlags); + if (StringUtils.isNotBlank(adminFlags)) { + String[] split = adminFlags.split(";"); + return Arrays.asList(split); + } + return Collections.emptyList(); + } +// +// private static List getDesignFlags() { +// //这里要调整 +// String adminFlags = FilterMeConfig.getInstance().getAdminFlagsNm(); +// FineLoggerFactory.getLogger().info("本地设计角色获取:{}", adminFlags); +// if (StringUtils.isNotBlank(adminFlags)) { +// String[] split = adminFlags.split(";"); +// return Arrays.asList(split); +// } +// return Collections.emptyList(); +// } + + private String getUrl(HttpServletRequest request) { + String url = "/"; + try { + url = "http://" + request.getServerName()//服务器地址 + + ":" + + request.getServerPort() + request.getRequestURI(); + Enumeration parameterNames = request.getParameterNames(); + Map reqParams = new HashMap<>(); + while (parameterNames.hasMoreElements()) { + String key = parameterNames.nextElement(); + reqParams.put(key, request.getParameter(key)); + } + Map parmas = header2url(request); + reqParams.putAll(parmas); + String header2url = map2String(reqParams); +// FineLoggerFactory.getLogger().info("转换之后的url参数:{}", header2url); + if (url.contains("?")) { + url += header2url; + } else { + url += "?a=1" + header2url; + } + + } catch (Exception e) { + e.printStackTrace(); + } + url += "&sendTwo=true"; + return url; + } + + + private Map header2url(HttpServletRequest request) { + Map params = new HashMap<>(); + String context = request.getHeader("X-Context"); + if (StringUtils.isNotBlank(context)) { + try { + context = URLDecoder.decode(context, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + JSONObject jsonObject = new JSONObject(context); + String userNo = jsonObject.getString("usrNo"); + String userNm = jsonObject.getString("usrNm"); + String pstNo = jsonObject.getString("pstNo"); + String instNo = jsonObject.getString("instNo"); + String accInstNo = jsonObject.getString("accInstNo"); + String admnInstNo = jsonObject.getString("admnInstNo"); + params.put("usrNo", userNo); + params.put("usrNm", userNm); + params.put("pstNo", pstNo); + params.put("instNo", instNo); + params.put("accInstNo", accInstNo); + params.put("admnInstNo", admnInstNo); + } + params.put("sendTwo", "true"); + return params; + } + + private String map2String(Map params) { + StringBuffer buffer = new StringBuffer(); + Set strings = params.keySet(); + for (String key : strings) { + buffer.append("&").append(key).append("=").append(params.get(key)); + } + return buffer.toString(); + } + +} diff --git a/src/main/java/com/fr/plugin/LoginFilter.java b/src/main/java/com/fr/plugin/LoginFilter.java new file mode 100644 index 0000000..b6d09ae --- /dev/null +++ b/src/main/java/com/fr/plugin/LoginFilter.java @@ -0,0 +1,150 @@ +package com.fr.plugin; + +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.UserController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.decision.privilege.encrpt.PasswordValidator; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.*; + +/** + * 废弃 + */ +public class LoginFilter extends AbstractEmbedRequestFilterProvider { + + private boolean isLogin(HttpServletRequest req){ + return LoginService.getInstance().isLogged(req); + } + @Override + public void filter(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { + String header = req.getHeader("X-Context"); + if (StringUtils.isNotBlank(header) && !isLogin(req)) { + try { + header = URLDecoder.decode(header, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + FineLoggerFactory.getLogger().info("拦截器捕获一个请求 x-content :{}", header); + JSONObject entries = new JSONObject(header); + String usrNm = entries.getString("usrNm"); + JSONArray rlNoList = entries.getJSONArray("rlNoList"); + try { + User user = UserService.getInstance().getUserByUserName(usrNm); + UserController userController = AuthorityContext.getInstance().getUserController(); + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + if (user == null) { + FineLoggerFactory.getLogger().info("拦截器新增一个用户 :{}", usrNm); + PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(ManualOperationType.KEY).getPasswordValidator(); + user = (new User()).userName(usrNm).realName(usrNm).password(passwordValidator.encode(usrNm, UUID.randomUUID().toString())) + .creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + userController.add(user); + } + String userId = user.getId(); + List roles = customRoleController.findByUser(userId, QueryFactory.create()); + List localRoles = new ArrayList<>(); + boolean isAdminRoleFlag = false; + List adminFlags = getAdminFlags(); + for (CustomRole role : roles) { + String name = role.getName(); + localRoles.add(name); + if(isAdminFlags(name,adminFlags)){ + isAdminRoleFlag = true; + } + } + List remoteRoles = new ArrayList<>(); + try { + int size = rlNoList.size(); + for (int i = 0; i < size; i++) { + String name = rlNoList.getString(i); + remoteRoles.add(name); + } + //先判断传过来的的角色是不是本地都有,没有要加上 + for (String role : remoteRoles) { + if (!localRoles.contains(role)) { + userController.addUserToCustomRole(userId, role); + } + } + //在判断本地的角色是不是远程没有了,要移除掉 + for (String localRole : localRoles) { + if (!remoteRoles.contains(localRole)) { + userController.removeUserFromCustomRole(userId, localRole); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + //如果是管理员标志就直接登录admin账号 + if(isAdminRoleFlag){ + login(req, res, "admin"); + }else { + login(req, res, usrNm); + } + String home = req.getContextPath() + req.getServletPath(); + sendRedirect(res, home); + return; + } catch (Exception e) { + e.printStackTrace(); + } + } + return; + } + + private static boolean isAdminFlags(String flag,List flags){ + return flags.contains(flag); + } + private List getAdminFlags() { + InputStream inputStream = ResourceIOUtils.read("/config-all/adminflag.properties"); + if (inputStream != null) { + Properties properties = new Properties(); + try { + properties.load(inputStream); + String adminflag = properties.getProperty("adminflag"); + if (StringUtils.isNotBlank(adminflag)) { + String[] split = adminflag.split(","); + return Arrays.asList(split); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return Collections.emptyList(); + } + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + private void login(HttpServletRequest req, HttpServletResponse res, String username) { + String token = null; + try { + token = LoginService.getInstance().login(req, res, username); + req.setAttribute("fine_auth_token", token); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + FineLoggerFactory.getLogger().error("login failed"); + } + FineLoggerFactory.getLogger().error("login success"); + } +} diff --git a/src/main/java/com/fr/plugin/LoginOutEventProvider.java b/src/main/java/com/fr/plugin/LoginOutEventProvider.java new file mode 100644 index 0000000..935a957 --- /dev/null +++ b/src/main/java/com/fr/plugin/LoginOutEventProvider.java @@ -0,0 +1,25 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.AbstractLogInOutEventProvider; +import com.fr.decision.webservice.login.LogInOutResultInfo; +import com.fr.decision.webservice.v10.user.UserService; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +public class LoginOutEventProvider extends AbstractLogInOutEventProvider { + + @Override + public String logoutAction(LogInOutResultInfo result) { + HttpServletRequest request = result.getRequest(); + + HttpSession session = request.getSession(); + if (session != null) { + session.removeAttribute("fine_auth_token"); + } + String servletPath = request.getServletPath(); + String contextPath = request.getContextPath(); + String url = contextPath + servletPath + "/login?manual=true"; + return url; + } +} diff --git a/src/main/java/com/fr/plugin/PostionImportgHander.java b/src/main/java/com/fr/plugin/PostionImportgHander.java new file mode 100644 index 0000000..e9dfe51 --- /dev/null +++ b/src/main/java/com/fr/plugin/PostionImportgHander.java @@ -0,0 +1,139 @@ +package com.fr.plugin; + +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.DepartmentController; +import com.fr.decision.authority.controller.PostController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.Department; +import com.fr.decision.authority.data.Post; +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; +import com.fr.stable.query.restriction.RestrictionFactory; +import com.fr.third.org.apache.commons.io.IOUtils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class PostionImportgHander extends BaseHttpHandler { + @Override + public RequestMethod getMethod() { + return RequestMethod.POST; + } + + @Override + public String getPath() { + return "/depAndPosImport"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest req, HttpServletResponse res) throws Exception { +// 传入的json格式 + /** + * [ + * { + * "fInstNo": "", //父级id + * "instNo": "A01", + * "instNm": "部门一", + * "pstNo": "X1", + * "pstNm": "职务一" + * } + * ] + */ + String body = IOUtils.toString(req.getReader()); + FineLoggerFactory.getLogger().info("批量添加部门信息接口收到:{}", body); + try { + JSONArray jsonArray = new JSONArray(body); + int length = jsonArray.length(); + for (int i = 0; i < length; i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String fInstNo = jsonObject.getString("fInstNo"); + String instNo = jsonObject.getString("instNo");//部门id + String instNm = jsonObject.getString("instNm");//部门名称 + String pstNo = jsonObject.getString("pstNo");//职务编号 + String pstNm = jsonObject.getString("pstNm");//职务名 + saveDep(fInstNo, instNo, instNm); + savePosition(instNo, pstNo, pstNm); + } + } catch (Exception e) { + WebUtils.printAsString(res, "json format error"); + } + WebUtils.printAsString(res, "success"); + } + + public static void savePosition(String did, String pid, String name) { + PostController postController = AuthorityContext.getInstance().getPostController(); + Post post; + try { + post = postController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pid))); + if (post == null) { + post = new Post(); + post.setId(pid); + post.setName(name + "(" + pid + ")"); + post.setEnable(true); + postController.add(post); + FineLoggerFactory.getLogger().info("从接口新增职位,post: {}: name {}", post.getId(), post.getName()); + } else { + post.setName(name + "(" + pid + ")"); + post.setEnable(true); + postController.update(post); + FineLoggerFactory.getLogger().info("已存在职位,post: {}: name {}", post.getId(), post.getName()); + } + postController.addPostToDepartment(pid, did); + + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + CustomRole customRole = customRoleController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pid))); + if (customRole == null) { + customRole = new CustomRole(); + customRole.id(pid).name(name + "(" + pid + ")").enable(true); + customRoleController.add(customRole); + FineLoggerFactory.getLogger().info("从接口新增角色,id: {}: name {}", customRole.getId(), customRole.getName()); + } else { + customRole.name(name + "(" + pid + ")").enable(true); + customRoleController.update(customRole); + FineLoggerFactory.getLogger().info("已存在角色,id: {}: name {}", customRole.getId(), customRole.getName()); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().info("添加职位异常: 职位id :{} 部门id:{}: name {}", pid, did, name); + } + } + + public static void saveDep(String pid, String id, String name) { + try { + DepartmentController departmentController = AuthorityContext.getInstance().getDepartmentController(); + Department department = departmentController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", id))); + if (StringUtils.isBlank(pid)) { + pid = null; + } + if (department == null) { + department = new Department(); + department.setName(name + "(" + id + ")"); + department.setId(id); + department.setParentId(pid); + department.setEnable(true); + department.description("通过x-content创建"); + departmentController.add(department); + FineLoggerFactory.getLogger().info("新增部门,dep: {}: name {}", department.getId(), department.getName()); + } else { + department.setName(name + "(" + id + ")"); + department.setParentId(pid); + department.setEnable(true); + departmentController.update(department); + FineLoggerFactory.getLogger().info("已存在部门,dep: {}: name {}", department.getId(), department.getName()); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error("添加部门:{} 异常", id, e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/RemoteFilter.java b/src/main/java/com/fr/plugin/RemoteFilter.java new file mode 100644 index 0000000..f27fe78 --- /dev/null +++ b/src/main/java/com/fr/plugin/RemoteFilter.java @@ -0,0 +1,405 @@ +package com.fr.plugin; + +import com.fr.data.NetworkHelper; +import com.fr.decision.authority.AuthorityContext; +import com.fr.decision.authority.base.constant.type.operation.ManualOperationType; +import com.fr.decision.authority.controller.CustomRoleController; +import com.fr.decision.authority.controller.DepartmentController; +import com.fr.decision.authority.controller.PostController; +import com.fr.decision.authority.controller.UserController; +import com.fr.decision.authority.data.CustomRole; +import com.fr.decision.authority.data.Department; +import com.fr.decision.authority.data.Post; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.AbstractEmbedRequestFilterProvider; +import com.fr.decision.privilege.encrpt.PasswordValidator; +import com.fr.decision.webservice.Response; +import com.fr.decision.webservice.impl.user.type.UserProductType; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.exception.RemoteDesignPermissionDeniedException; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.security.JwtUtils; +import com.fr.stable.StringUtils; +import com.fr.stable.query.QueryFactory; +import com.fr.stable.query.restriction.RestrictionFactory; +import com.fr.web.service.RemoteDesignAuthorityDataService; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.*; + +public class RemoteFilter extends AbstractEmbedRequestFilterProvider { + + @Override + public void init(FilterConfig filterConfig) { + FineLoggerFactory.getLogger().info("RemoteFilter 拦截器启动"); + FilterMeConfig instance = FilterMeConfig.getInstance(); + } + + @Override + public void filter(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException, ServletException { + String uri = request.getRequestURI(); + if (uri != null && uri.contains("remote/design/token") && request.getMethod().equalsIgnoreCase("GET")) { + FineLoggerFactory.getLogger().info("拦截器捕获一个请求,design/token {}", uri); + String username = NetworkHelper.getHTTPRequestParameter(request, "username"); + String password = NetworkHelper.getHTTPRequestParameter(request, "password"); + try { + FineLoggerFactory.getLogger().info("登录的用户名{} 密码:{}", username, password); + JSONObject apilogin = apilogin(username, password); + if (apilogin != null) { + String remoteToken = tokenLogin(apilogin); + FineLoggerFactory.getLogger().info("响应的token: {} ", remoteToken); + if (StringUtils.isNotBlank(remoteToken)) { + WebUtils.printAsString(httpServletResponse, com.fr.plugin.JSONUtils.serialize(Response.ok(remoteToken))); + return; + } + FineLoggerFactory.getLogger().info("登录异常{}", remoteToken); + } else { + renderError(httpServletResponse, "21300007", "User password error!"); + } + } catch (Exception e) { + printException2Frlog(e); + if (e instanceof RemoteDesignPermissionDeniedException) { + renderError(httpServletResponse, "31300101", "Fine-Engine_Remote_Design_Permission_Denied"); + } + } + } + return; + } + + private List getAdminFlags() { + InputStream inputStream = ResourceIOUtils.read("/config-all/adminflag.properties"); + if (inputStream != null) { + Properties properties = new Properties(); + try { + properties.load(inputStream); + String adminflag = properties.getProperty("adminflag"); + if (StringUtils.isNotBlank(adminflag)) { + String[] split = adminflag.split(","); + return Arrays.asList(split); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return Collections.emptyList(); + } + + private static boolean isAdminFlags(String flag, List flags) { + return flags.contains(flag); + } + + private String tokenLogin(JSONObject jsonObject) { + JSONObject entries = jsonObject.getJSONObject("data"); +// String usrNm = entries.getString("usrNm"); + JSONArray rlNoList = entries.getJSONArray("rlNoList"); + int size = rlNoList.size(); + List adminFlags = getAdminFlags(); + boolean isAdmin = false; + for (int i = 0; i < size; i++) { + String adminPost = rlNoList.getString(i); + if (isAdminFlags(adminPost, adminFlags)) { + isAdmin = true; + break; + } + } + if (isAdmin) { + FineLoggerFactory.getLogger().info("remote 本次为管理员登录 "); + try { + return getRemoteToken("admin"); + } catch (Exception e) { + printException2Frlog(e); + } + } + String usrNo = entries.getString("usrNo"); + String instNo = entries.getString("instNo");//部门id + String realUserName = usrNo + "_" + instNo;//更改后的用户名 + String fullName = entries.getString("fullName"); + String alias = fullName + "(" + realUserName + ")"; + try { + User user = UserService.getInstance().getUserByUserName(realUserName); + UserController userController = AuthorityContext.getInstance().getUserController(); + CustomRoleController customRoleController = AuthorityContext.getInstance().getCustomRoleController(); + if (user == null) { + FineLoggerFactory.getLogger().info("设计器拦截 拦截器新增一个用户 :{}", usrNo); + //如果usrNo的用户不存在则检查realUserName的用户是否存在 + user = UserService.getInstance().getUserByUserName(realUserName); + if (user == null) { + FineLoggerFactory.getLogger().info("拦截器新增一个用户 用户名称 :{}", realUserName); + PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(ManualOperationType.KEY).getPasswordValidator(); + user = (new User()).userName(realUserName).userAlias(alias).realName(fullName).password(passwordValidator.encode(realUserName, UUID.randomUUID().toString())) + .creationType(ManualOperationType.KEY).lastOperationType(ManualOperationType.KEY).enable(true); + userController.add(user); + } + } + + String userId = user.getId(); +// List adminFlags = getAdminFlags(); + FineLoggerFactory.getLogger().info(" 设计器拦截 当前的管理员标识符 :{}", adminFlags); + List remoteRoles = new ArrayList<>(); + String pstNo = entries.getString("pstNo");//角色id + String pstNm = entries.getString("pstNm");//角色名称 + String realRoleName = pstNm + "(" + pstNo + ")"; + remoteRoles.add(pstNo); + + //先判断传过来的的角色是不是本地都有,没有要加上 + List controllerOne = customRoleController.find(QueryFactory.create().addRestriction(RestrictionFactory.eq("name", pstNo))); + if (!controllerOne.isEmpty()) { + try { + FineLoggerFactory.getLogger().info(" 设计器拦截 传送过来的角色在本地已存在,添加到用户{} 到角色:{}", userId, pstNo); + CustomRole customRole = controllerOne.get(0); + customRole.setName(realRoleName); + customRole.setAlias(pstNo); + customRole.setEnable(true); + customRole.setDescription("通过xcontent添加"); + customRoleController.update(customRole); + userController.addUserToCustomRole(userId, customRole.getId()); + } catch (Exception e) { + } + } else { + FineLoggerFactory.getLogger().info(" 设计器拦截 传送过来的角色在本地不存在,添加到本地:{}", pstNo); + CustomRole addRole = new CustomRole(); + addRole.setId(pstNo); + addRole.setName(realRoleName); + addRole.setAlias(pstNo); + addRole.setEnable(true); + addRole.setDescription("通过xcontent添加"); + try { + customRoleController.add(addRole); + userController.addUserToCustomRole(userId, addRole.getId()); + } catch (Exception e) { + } + } + //在判断本地的角色是不是远程没有了,要移除掉 + List roles = customRoleController.findByUser(userId, QueryFactory.create()); + + for (CustomRole customRole : roles) { + if (!remoteRoles.contains(customRole.getId())) { + if ("superusers".equals((customRole.getName()))) { + continue; + } + FineLoggerFactory.getLogger().info("远端没有的角色本地移除:{}", customRole.getName()); + userController.removeUserFromCustomRole(userId, customRole.getId()); + } + } + + DepartmentController departmentController = AuthorityContext.getInstance().getDepartmentController(); + //移除原来的部门 + List departmentList = departmentController.findByUser(userId, QueryFactory.create()); + for (Department department : departmentList) { + userController.removeUserFromDepartment(userId, department.getId()); + } + String instNm = entries.getString("instNm"); + Department department = departmentController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", instNo))); + if (department == null) { + FineLoggerFactory.getLogger().info("添加 部门,dep: {}: name {}", instNo, instNo, instNm + "(" + pstNo + "" + ")"); + department = new Department(); + department.setName(instNm + "(" + pstNo + ")"); + department.setId(instNo); + department.setEnable(true); + department.description("通过x-content创建"); + departmentController.add(department); + } else { + department.setName(instNm + "(" + pstNo + ")"); + department.setParentId(null); + department.setEnable(true); + departmentController.update(department); + FineLoggerFactory.getLogger().info("设计器节点 已存在部门,dep: {}: name {}", department.getId(), department.getName()); + } + + PostController postController = AuthorityContext.getInstance().getPostController(); + List byUser = postController.findByUser(userId, QueryFactory.create()); + //移除原来职位 + for (Post post : byUser) { + userController.removeUserFromPost(userId, post.getId()); + } + Post post = postController.findOne(QueryFactory.create().addRestriction(RestrictionFactory.eq("id", pstNo))); + if (post == null) { + post = new Post(); + post.setId(pstNo); + post.setName(pstNm + "(" + pstNo + ")"); + post.setEnable(true); + postController.add(post); + } else { + post.setName(pstNm + "(" + pstNo + ")"); + post.setEnable(true); + postController.update(post); + } + try { + FineLoggerFactory.getLogger().info("添加职位到部门,post: {}: dep {}", pstNo, instNo); + postController.addPostToDepartment(pstNo, instNo); + } catch (Exception e) { + } + try { + FineLoggerFactory.getLogger().info("添加用户到部门,post: {}: dep {} ,userId:{}", pstNo, instNo, userId); + userController.addUserToDepartmentAndPost(userId, instNo, pstNo); + } catch (Exception e) { + } +// try { +// int size = rlNoList.size(); +// for (int i = 0; i < size; i++) { +// String name = rlNoList.getString(i); +// remoteRoles.add(name); +// FineLoggerFactory.getLogger().info("传送过来的角色:{}", name); +// if (isAdminFlags(name, adminFlags)) { +// isAdminRoleFlag = true; +// } +// } +// //先判断传过来的的角色是不是本地都有,没有要加上 +// for (String role : remoteRoles) { +// boolean find = false; +// for (CustomRole customRole : roles) { +// if (ComparatorUtils.equals(customRole.getName(), role)) { +// find = true; +// } +// } +// if (!find) { +// FineLoggerFactory.getLogger().info("传送过来的角色在本地不存在,添加到本地:{}", role); +// CustomRole addRole = new CustomRole(); +// addRole.setName(role); +// addRole.setAlias(role); +// addRole.setId(UUID.randomUUID().toString()); +// addRole.setDescription("通过xcontent添加"); +// customRoleController.add(addRole); +// userController.addUserToCustomRole(userId, addRole.getId()); +//// List roleList = customRoleController.find(QueryFactory.create().addRestriction(RestrictionFactory.eq("name", role))); +//// FineLoggerFactory.getLogger().info("通过角色名称{} 查询出来的本地角色:{}", role, roleList.size()); +//// for (CustomRole customRole : roleList) { +//// FineLoggerFactory.getLogger().info("给用户新增角色:{}", customRole.getId()); +//// +//// } +// } +// } +// //在判断本地的角色是不是远程没有了,要移除掉 +// for (CustomRole customRole : roles) { +// if (!remoteRoles.contains(customRole.getName())) { +// FineLoggerFactory.getLogger().info("远端没有的角色本地移除:{}", customRole.getId()); +// userController.removeUserFromCustomRole(userId, customRole.getId()); +// } +// } +// } catch (Exception e) { +// printException2Frlog(e); +// } + + //如果是管理员标志就直接登录admin账号 + //在判断本地的角色是不是远程没有了,要移除掉 +// boolean isAdmin = false; +// if (!adminFlags.isEmpty()) { +// for (CustomRole role : roles) { +// String name = role.getName(); +// if (isAdminFlags(name, adminFlags)) { +// isAdmin = true; +// break; +// } +// } +// } + boolean isDesignUser = false; +// List designFlags = getDesignFlags(); +// if (!designFlags.isEmpty()) { +// for (CustomRole role : roles) { +// String name = role.getName(); +// //如果是有 +// if (isAdminFlags(name, designFlags)) { +// isDesignUser = true; +// break; +// } +// } +// } +// try { +// UserProductType userProductType = UserProductType.fromInteger(6);//数据处理用户 +// if (isDesignUser) { +// userController.addUserProductType(userId, userProductType.transProductKey()); +// } else { +// userController.removeUserProductType(userId, userProductType.transProductKey()); +// } +// } catch (Exception e) { +// } +// if (isAdmin) { +// FineLoggerFactory.getLogger().info("设计器拦截 本次为管理员登录"); +// return getRemoteToken("admin"); +// } else { + FineLoggerFactory.getLogger().info("设计器拦截 本次为普通用户登录:{}", realUserName); + return getRemoteToken(realUserName); +// } + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } +// +// private static List getDesignFlags() { +// String adminFlags = FilterMeConfig.getInstance().getDesignFlags(); +// FineLoggerFactory.getLogger().info("remote本地设计角色获取:{}", adminFlags); +// if (StringUtils.isNotBlank(adminFlags)) { +// String[] split = adminFlags.split(";"); +// return Arrays.asList(split); +// } +// return Collections.emptyList(); +// } + + private void renderError(HttpServletResponse httpServletResponse, String code, String msg) { + try { + WebUtils.printAsString(httpServletResponse, JSONUtils.serialize(Response.error(code, msg))); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private JSONObject apilogin(String username, String pwd) throws Exception { + String baseUrl = getBaseUrl(); + if (StringUtils.isBlank(baseUrl)) { + throw new Exception("未配置接口地址"); + } + String api = getBaseUrl() + "check/pwd/finebi"; + FineLoggerFactory.getLogger().info("remote访问的接口地址为 {}", api); + JSONObject parm = new JSONObject(); + parm.put("userName", username); + parm.put("verifyCode", pwd); + + String resp = HttpApi.sendJsonPost(api, parm, "UTF-8"); + FineLoggerFactory.getLogger().info("接口响应 {}", resp); + JSONObject jsonObject = new JSONObject(resp); + return jsonObject; + } + + + public String getRemoteToken(String var1) throws Exception { + User var4 = UserService.getInstance().getUserByUserName(var1); + if (var4 != null && RemoteDesignAuthorityDataService.getInstance().hasAuthority(var4.getId())) { + return JwtUtils.createDefaultJWT(var1); + } else { + throw new RemoteDesignPermissionDeniedException(); + } + } + + private String getBaseUrl() { + FilterMeConfig instance = FilterMeConfig.getInstance(); + String baseUrl = instance.getBaseUrl(); + if (StringUtils.isNotBlank(baseUrl)) { + String[] split = baseUrl.split(";"); + List strings = Arrays.asList(split); + Collections.shuffle(strings); + return strings.get(0); + } + return ""; + } + + public static void printException2Frlog(Exception e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } + +} diff --git a/src/main/java/com/fr/plugin/RequestWrapper.java b/src/main/java/com/fr/plugin/RequestWrapper.java new file mode 100644 index 0000000..fb596b9 --- /dev/null +++ b/src/main/java/com/fr/plugin/RequestWrapper.java @@ -0,0 +1,128 @@ +package com.fr.plugin; + + +import com.fr.log.FineLoggerFactory; +import com.fr.log.FineLoggerProvider; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * @author 01 + * @program wrapper-demo + * @description 包装HttpServletRequest,目的是让其输入流可重复读 + * @create 2018-12-24 20:48 + * @since 1.0 + **/ +public class RequestWrapper extends HttpServletRequestWrapper { + FineLoggerProvider log = FineLoggerFactory.getLogger(); + /** + * 存储body数据的容器 + */ + private byte[] body; + + public RequestWrapper(HttpServletRequest request) { + super(request); + // 将body数据存储起来 + String bodyStr = getBodyString(request); + body = bodyStr.getBytes(Charset.defaultCharset()); + } + + public void setBody(byte[] body) { + this.body = body; + } + + /** + * 获取请求Body + * + * @param request request + * @return String + */ + private String getBodyString(final ServletRequest request) { + try { + return inputStream2String(request.getInputStream()); + } catch (IOException e) { + log.error("", e); + throw new RuntimeException(e); + } + } + + /** + * 获取请求Body + * + * @return String + */ + public String getBodyString() { + return new String(this.body, StandardCharsets.UTF_8); + } + + /** + * 将inputStream里的数据读取出来并转换成字符串 + * + * @param inputStream inputStream + * @return String + */ + private String inputStream2String(InputStream inputStream) { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + + try { + reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + log.error("", e); + throw new RuntimeException(e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + log.error("", e); + } + } + } + + return sb.toString(); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + + final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); + + return new ServletInputStream() { + @Override + public int read() throws IOException { + return inputStream.read(); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/XContentRequestWrapper.java b/src/main/java/com/fr/plugin/XContentRequestWrapper.java new file mode 100644 index 0000000..76da138 --- /dev/null +++ b/src/main/java/com/fr/plugin/XContentRequestWrapper.java @@ -0,0 +1,97 @@ +package com.fr.plugin; + +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.third.jodd.util.collection.ArrayEnumeration; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.*; + +public class XContentRequestWrapper extends HttpServletRequestWrapper { + JSONObject jsonObject = null; + + public XContentRequestWrapper(HttpServletRequest request) { + super(request); + String header = request.getHeader("X-Context"); + String url = WebUtils.getOriginalURL(request); + try { + if (StringUtils.isNotBlank(header)) { + header = URLDecoder.decode(header, "UTF-8"); + jsonObject = new JSONObject(header); + FineLoggerFactory.getLogger().info("当前请求地址:{} \n X-Context :{} ", url, header); + } else { + FineLoggerFactory.getLogger().info("当前请求地址:{} 未携带 X-Context ", url); + } + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + @Override + public String getParameter(String name) { + String parameter = super.getParameter(name); + if (StringUtils.isBlank(parameter) && jsonObject != null) { + return jsonObject.getString(name); + } + return parameter; + } + + @Override + public Map getParameterMap() { + Map parameterMap = super.getParameterMap(); + Map stringHashMap = new HashMap<>(); + if (jsonObject != null) { + Iterator keys = jsonObject.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object value = jsonObject.getValue(key); + if (value != null) { + stringHashMap.put(key, new String[]{ + value.toString() + }); + } + } + } + stringHashMap.putAll(parameterMap); + return stringHashMap; + } + + @Override + public Enumeration getParameterNames() { + Enumeration parameterNames = super.getParameterNames(); + if (jsonObject != null) { + Iterator keys = jsonObject.keys(); + ArrayList arrayList = new ArrayList<>(); + while (keys.hasNext()) { + String next = keys.next(); + arrayList.add(next); + } + while (parameterNames.hasMoreElements()) { + String s = parameterNames.nextElement(); + arrayList.add(s); + } + ArrayEnumeration dayNames = new ArrayEnumeration(arrayList.toArray(new String[0])); + return dayNames; + } + return parameterNames; + } + + @Override + public String[] getParameterValues(String name) { + String[] parameterValues = super.getParameterValues(name); + if (parameterValues == null && jsonObject != null) { + Object stringObjectMap = jsonObject.getValue(name); + if (stringObjectMap != null) { + return new String[]{ + stringObjectMap.toString() + }; + } + } + return parameterValues; + } +} diff --git a/src/main/java/com/fr/plugin/util/LogUtils.java b/src/main/java/com/fr/plugin/util/LogUtils.java new file mode 100644 index 0000000..e64cada --- /dev/null +++ b/src/main/java/com/fr/plugin/util/LogUtils.java @@ -0,0 +1,90 @@ +package com.fr.plugin.util; + +import com.fr.log.FineLoggerFactory; +import com.fr.log.FineLoggerProvider; +import com.fr.plugin.context.PluginContexts; +import com.fr.stable.StringUtils; + +public final class LogUtils { + private static String LOG_PREFIX = "[单点登录] "; + private static final String PLUGIN_VERSION; + + private static final FineLoggerProvider LOGGER = FineLoggerFactory.getLogger(); + + static { + String version = PluginContexts.currentContext().getMarker().getVersion(); + if (StringUtils.isNotBlank(version)) { + PLUGIN_VERSION = "[v" + version + "] "; + } else { + PLUGIN_VERSION = "[unknown version] "; + } + + LOG_PREFIX = LOG_PREFIX + PLUGIN_VERSION; + } + + public static void setPrefix(String prefix) { + if (prefix != null) { + LOG_PREFIX = prefix; + } + } + + public static boolean isDebugEnabled() { + return LOGGER.isDebugEnabled(); + } + + public static void debug(String s) { + LOGGER.debug(LOG_PREFIX + s); + } + + public static void debug(String s, Object... objects) { + LOGGER.debug(LOG_PREFIX + s, objects); + } + + public static void debug(String s, Throwable throwable) { + LOGGER.debug(LOG_PREFIX + s, throwable); + } + + public static boolean isInfoEnabled() { + return LOGGER.isInfoEnabled(); + } + + public static void info(String s) { + LOGGER.info(LOG_PREFIX + s); + } + + public static void info(String s, Object... objects) { + LOGGER.info(LOG_PREFIX + s, objects); + } + + public static void warn(String s) { + LOGGER.warn(LOG_PREFIX + s); + } + + public static void warn(String s, Object... objects) { + LOGGER.warn(LOG_PREFIX + s, objects); + } + + public static void warn(String s, Throwable throwable) { + LOGGER.warn(LOG_PREFIX + s, throwable); + } + + public static void warn(Throwable throwable, String s, Object... objects) { + LOGGER.warn(throwable, LOG_PREFIX + s, objects); + } + + public static void error(String s) { + LOGGER.error(LOG_PREFIX + s); + } + + public static void error(String s, Object... objects) { + LOGGER.error(LOG_PREFIX + s, objects); + } + + public static void error(String s, Throwable throwable) { + LOGGER.error(LOG_PREFIX + s, throwable); + } + + public static void error(Throwable throwable, String s, Object... objects) { + LOGGER.error(throwable, LOG_PREFIX + s, objects); + } +} diff --git a/src/main/java/com/fr/plugin/util/RequestUtils.java b/src/main/java/com/fr/plugin/util/RequestUtils.java new file mode 100644 index 0000000..fc4540a --- /dev/null +++ b/src/main/java/com/fr/plugin/util/RequestUtils.java @@ -0,0 +1,26 @@ +package com.fr.plugin.util; + +import com.fr.stable.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author Peng + */ +public class RequestUtils { + + /** + * todo 有待测试 + * @param request + * @return + */ + public static String getFullUrl(HttpServletRequest request) { + String url = request.getRequestURI(); + String queryString = request.getQueryString(); + if (StringUtils.isNotEmpty(queryString)) { + url = url + "?" + queryString; + } + + return url; + } +} diff --git a/src/main/java/com/fr/plugin/util/ResponseUtils.java b/src/main/java/com/fr/plugin/util/ResponseUtils.java new file mode 100644 index 0000000..9c8223d --- /dev/null +++ b/src/main/java/com/fr/plugin/util/ResponseUtils.java @@ -0,0 +1,35 @@ +package com.fr.plugin.util; + +import com.fr.decision.webservice.utils.WebServiceUtils; +import com.fr.json.JSONObject; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletResponse; + +public final class ResponseUtils { + + /** + * 打印之前会自动处理 Content-Type + */ + public static void printAsJSON( HttpServletResponse response, JSONObject jsonObject) throws Exception { + // todo 这里的 charset 有没有可能是别的呢? + response.setContentType("application/json;charset=utf-8"); + WebUtils.printAsJSON(response, jsonObject); + } + + /** + * todo + * + * 参考方法签名: + * generateUnavailableWebPage(String result, String reason, String solution) + * generateErrorWebPage(String result, String reason, String solution) + * + */ + public static void printErrorPage() { + // 可参考 ↓ +// String errorHtml = WebServiceUtils.generateUnavailableWebPage("单点登录失败", reason, ""); +// String errorHtml = WebServiceUtils.generateErrorWebPage("单点登录失败", reason, ""); +// WebUtils.printAsString(response, errorHtml); + + } +} diff --git a/src/main/java/com/fr/sso/cas/FRAccessFilter.java b/src/main/java/com/fr/sso/cas/FRAccessFilter.java new file mode 100644 index 0000000..0ebde4e --- /dev/null +++ b/src/main/java/com/fr/sso/cas/FRAccessFilter.java @@ -0,0 +1,464 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package com.fr.sso.cas; + +import com.fr.base.ServerConfig; +import com.fr.base.TemplateUtils; +import com.fr.data.NetworkHelper; +import com.fr.decision.authority.data.User; +import com.fr.decision.mobile.terminal.TerminalHandler; +import com.fr.decision.privilege.TransmissionTool; +import com.fr.decision.webservice.Response; +import com.fr.decision.webservice.bean.authentication.LoginRequestInfoBean; +import com.fr.decision.webservice.bean.authentication.LoginResponseInfoBean; +import com.fr.decision.webservice.bean.authentication.OriginUrlResponseBean; +import com.fr.decision.webservice.exception.login.UserLoginException; +import com.fr.decision.webservice.utils.WebServiceUtils; +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.ComparatorUtils; +import com.fr.log.FineLoggerFactory; +import com.fr.security.JwtUtils; +import com.fr.stable.StringUtils; +import com.fr.stable.web.Device; +import com.fr.third.fasterxml.jackson.core.JsonGenerationException; +import com.fr.third.fasterxml.jackson.core.JsonParseException; +import com.fr.third.fasterxml.jackson.databind.JsonMappingException; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; +import com.fr.third.fasterxml.jackson.databind.type.TypeFactory; +import com.fr.web.utils.WebUtils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.Hashtable; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +public class FRAccessFilter implements Filter { + public String[] loginPageNecessaryResources = new String[]{"fineui.min.css", "login.min.css", "fineui.min.js", "I18nTextGenerator", "ConstantGenerator", "login.min.js", "logo.png", "login.png", "login-img.png", "iconfont.woff"}; + private String servletNamePrefix = "/" + ServerConfig.getInstance().getServletName(); + private String[] noFilterUrls; + private static ObjectMapper objectMapper = new ObjectMapper(); + private static boolean isDev = true; + + public FRAccessFilter() { + this.noFilterUrls = new String[]{this.servletNamePrefix + "/login", this.servletNamePrefix + "/login/config", this.servletNamePrefix + "/system/info", this.servletNamePrefix + "/login/cross/domain", this.servletNamePrefix + "/remote"}; + } + + public void init(FilterConfig filterConfig) throws ServletException { + } + + private boolean isApp(HttpServletRequest request) { + String header = request.getHeader("User-Agent"); + if (StringUtils.isNotBlank(header) && header.contains("FineReact")) { + return true; + } else { + String deviceType = request.getParameter("deviceType"); + if (StringUtils.isNotBlank(deviceType)) { + return true; + } else { + String terminal = request.getHeader("terminal"); + return StringUtils.isNotBlank(terminal) && terminal.equals("APP"); + } + } + } + + private boolean isMobLogin(HttpServletRequest request) { + if (this.isApp(request) && request.getMethod().equals("POST")) { + String requestURI = request.getRequestURI(); + if (requestURI.endsWith("/login")) { + return true; + } + } + + return false; + } + + public static void printException2Frlog(Exception e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", new Object[]{s}); + } + + public boolean getAuthenticator(String useruid, String userPassword, String applicationUsername, String applicationPassword) throws NamingException { + boolean result = false; + boolean flag = false; + DirContext ctx = null; + Hashtable env = new Hashtable(); + env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory"); + if (isDev) { + env.put("java.naming.provider.url", "ldap://xxxx"); + } else { + env.put("java.naming.provider.url", "ldap://xxxx"); + } + + env.put("java.naming.security.authentication", "simple"); + env.put("java.naming.security.principal", applicationUsername); + env.put("java.naming.security.credentials", applicationPassword); + + try { + ctx = new InitialDirContext(env); + FineLoggerFactory.getLogger().info("Applications Authentication Succeed"); + flag = true; + } catch (NamingException var31) { + if (ctx != null) { + try { + ctx.close(); + FineLoggerFactory.getLogger().info("------------------------------------------"); + } catch (NamingException var26) { + FineLoggerFactory.getLogger().info("ctx close occur NamingException, error message:" + var26); + } + } + + FineLoggerFactory.getLogger().info(" Applications Authentication Failed, error messgae: " + var31); + flag = false; + return false; + } + + SearchControls constraints = new SearchControls(); + if (flag) { + NamingEnumeration en = null; + + try { + constraints.setSearchScope(2); + String[] returnAttrs = new String[]{"*"}; + constraints.setReturningAttributes(returnAttrs); + String basedn = "dc=bizenit,dc=com"; + String searchFilter = "(|(uid=" + useruid + ")(smart-alias=" + useruid + "))"; + FineLoggerFactory.getLogger().info("filter:" + searchFilter); + en = ctx.search(basedn, searchFilter, constraints); + } catch (Exception var29) { + FineLoggerFactory.getLogger().info("search occur Exception, error message:" + var29); + printException2Frlog(var29); + } + + if (en.hasMoreElements()) { + DirContext ctxUser = null; + Hashtable envUser = new Hashtable(); + envUser.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory"); + if (isDev) { + envUser.put("java.naming.provider.url", "xxxx"); + } else { + envUser.put("java.naming.provider.url", "xxxx"); + } + + envUser.put("java.naming.security.authentication", "simple"); + envUser.put("java.naming.security.principal", ((SearchResult)en.next()).getNameInNamespace()); + envUser.put("java.naming.security.credentials", userPassword); + + try { + Object o = envUser.get("java.naming.provider.url"); + FineLoggerFactory.getLogger().info("LDAP认证服务器地址:{} 认证用户名:{}", new Object[]{o, envUser.get("java.naming.security.principal")}); + ctxUser = new InitialDirContext(envUser); + FineLoggerFactory.getLogger().info("User Login Succeed"); + result = true; + constraints.setTimeLimit(1000); + constraints.setDerefLinkFlag(false); + constraints.setReturningAttributes(new String[]{"*"}); + } catch (NamingException var28) { + FineLoggerFactory.getLogger().info("user Authentication occured NamingException,error message:" + var28); + printException2Frlog(var28); + } finally { + if (ctxUser != null) { + try { + ctxUser.close(); + } catch (NamingException var25) { + FineLoggerFactory.getLogger().info("ctxUser close occured NamingException,error message:" + var25); + } + } + + } + } else { + result = false; + } + + try { + if (en != null) { + en.close(); + } + + if (ctx != null) { + ctx.close(); + } + } catch (NamingException var27) { + FineLoggerFactory.getLogger().info("close occured NamingException,error message:" + var27); + printException2Frlog(var27); + } + } + + return result; + } + + public static T deserialize(String json, Class clazz) { + Object object = null; + try { + object = objectMapper.readValue(json, TypeFactory.rawClass(clazz)); + } catch (JsonParseException e) { + } catch (JsonMappingException e) { + } catch (IOException e) { + } + return (T) object; + } + + private String inputStream2String(InputStream inputStream) { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + + try { + reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); + + String line; + while((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException var12) { + throw new RuntimeException(var12); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException var11) { + } + } + + } + + return sb.toString(); + } + + public static String serialize(Object object) { + StringWriter write = new StringWriter(); + + try { + objectMapper.writeValue(write, object); + } catch (JsonGenerationException var3) { + } catch (JsonMappingException var4) { + } catch (IOException var5) { + } + + return write.toString(); + } + + private void mobLogin(HttpServletRequest req, HttpServletResponse response, LoginRequestInfoBean loginReqInfo) throws Exception { + String password = TransmissionTool.decrypt(loginReqInfo.isEncrypted(), loginReqInfo.getPassword()); + String username = loginReqInfo.getUsername(); + if (WebServiceUtils.containSQLChars(username)) { + Response error = Response.error("21300015", "Special char prohibit!"); + WebUtils.printAsString(response, serialize(error)); + } else { + User user = UserService.getInstance().getUserByUserName(username); + if (user == null) { + throw new UserLoginException(); + } else { + String adminName = "uid=lyfr_bind,ou=Application,ou=People,dc=bizenit,dc=com"; + String adminPwd = "kihFNUQeae2U"; + if (isDev) { + adminName = "uid=kk_bind,ou=Application,ou=People,dc=bizenit,dc=com"; + adminPwd = "K0G3x4Gyf93Q"; + } + + if (this.getAuthenticator(username, password, adminName, adminPwd)) { + String token = this.login(req, response, username); + OriginUrlResponseBean originUrlResponseBean = new OriginUrlResponseBean(TemplateUtils.render("${fineServletURL}")); + LoginResponseInfoBean infoBean = new LoginResponseInfoBean(token, originUrlResponseBean, user.getUserName(), loginReqInfo.getValidity()); + WebUtils.printAsString(response, serialize(infoBean)); + } else { + Response error = Response.error("21300007", "User not exist, or wrong password!"); + WebUtils.printAsString(response, serialize(error)); + } + } + } + } + + public static boolean checkTokenValid(HttpServletRequest req, String token, String currentUserName) { + try { + if (!ComparatorUtils.equals(currentUserName, JwtUtils.parseJWT(token).getSubject())) { + FineLoggerFactory.getLogger().info("username changed:" + currentUserName); + return false; + } else { + Device device = NetworkHelper.getDevice(req); + LoginService.getInstance().loginStatusValid(token, TerminalHandler.getTerminal(req, device)); + return true; + } + } catch (Exception var4) { + return false; + } + } + + private boolean isLogin(HttpServletRequest request) { + try { + return LoginService.getInstance().isLogged(request); + } catch (Exception var3) { + return false; + } + } + + private String login(HttpServletRequest req, HttpServletResponse res, String username) { + try { + String oldToken = TokenResource.COOKIE.getToken(req); + if (oldToken == null || !checkTokenValid(req, oldToken, username)) { + HttpSession session = req.getSession(true); + String token = LoginService.getInstance().login(req, res, username); +// session.setAttribute("fine_auth_token", token); + req.setAttribute("fine_auth_token", token); + return token; + } + } catch (Exception var7) { + FineLoggerFactory.getLogger().error(var7.getMessage(), var7); + } + + return null; + } + + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + servletRequest.setCharacterEncoding("UTF-8"); + HttpServletRequest request = (HttpServletRequest)servletRequest; + HttpServletResponse response = (HttpServletResponse)servletResponse; + String noIdmParameter = request.getParameter("no-idm"); + String pathInfo; + if (this.isMobLogin(request)) { + pathInfo = this.inputStream2String(request.getInputStream()); + + try { + LoginRequestInfoBean infoBean = (LoginRequestInfoBean)deserialize(pathInfo, LoginRequestInfoBean.class); + if (this.isLogin(request)) { + String oldToken = TokenResource.COOKIE.getToken(request); + OriginUrlResponseBean originUrlResponseBean = new OriginUrlResponseBean(TemplateUtils.render("${fineServletURL}")); + LoginResponseInfoBean respBean = new LoginResponseInfoBean(oldToken, originUrlResponseBean, infoBean.getUsername(), infoBean.getValidity()); + WebUtils.printAsString(response, serialize(respBean)); + } else { + this.mobLogin(request, response, infoBean); + } + + return; + } catch (Exception var13) { + FineLoggerFactory.getLogger().info("LDAP认证失败:" + var13.getMessage()); + } + } + + if (this.isApp(request)) { + setCookie(response, "no-idm", "true"); + pathInfo = request.getPathInfo(); + this.forwardRequest(request, response); + } else { + if (StringUtils.isNotEmpty(noIdmParameter)) { + boolean isNoIdm = Boolean.parseBoolean(noIdmParameter); + Cookie noIdmCookie = com.fr.third.springframework.web.util.WebUtils.getCookie(request, "no-idm"); + if (noIdmCookie != null) { + if (Boolean.parseBoolean(noIdmCookie.getValue()) != isNoIdm) { + if (isNoIdm) { + setCookie(response, "no-idm", "true"); + } else { + setCookie(response, "no-idm", "false"); + } + } + } else if (isNoIdm) { + setCookie(response, "no-idm", "true"); + } else { + setCookie(response, "no-idm", "false"); + } + } else { + Cookie noIdmCookie = com.fr.third.springframework.web.util.WebUtils.getCookie(request, "no-idm"); + if (noIdmCookie == null) { + setCookie(response, "no-idm", "false"); + } + } + + if (!this.isNoFilterUrls(request) && !this.isRequestLoginPageNecessaryResources(request) && !this.isFromNoIdmPage(request)) { + User user = null; + + try { + user = UserService.getInstance().getUserByRequestCookie(request); + } catch (Exception var12) { + } + + if (user != null) { + this.forwardRequest(request, response); + } else if (StringUtils.isNotEmpty(noIdmParameter) && Boolean.parseBoolean(noIdmParameter)) { + this.forwardRequest(request, response); + } else { + filterChain.doFilter(request, response); + } + } else { + this.forwardRequest(request, response); + } + } + } + + private static void setCookie(HttpServletResponse response, String name, String value) { + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + response.addCookie(cookie); + } + + public void destroy() { + } + + public boolean isNoFilterUrls(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + String[] var3 = this.noFilterUrls; + int var4 = var3.length; + + for(int var5 = 0; var5 < var4; ++var5) { + String url = var3[var5]; + if (requestURI.contains(url)) { + return true; + } + } + + return false; + } + + public boolean isRequestLoginPageNecessaryResources(HttpServletRequest request) { + String queryString = request.getQueryString(); + if (StringUtils.isEmpty(queryString)) { + return false; + } else { + String[] var3 = this.loginPageNecessaryResources; + int var4 = var3.length; + + for(int var5 = 0; var5 < var4; ++var5) { + String parameter = var3[var5]; + if (queryString.contains(parameter)) { + return true; + } + } + + return false; + } + } + + private boolean isFromNoIdmPage(HttpServletRequest request) { + String referer = request.getHeader("Referer"); + return StringUtils.isNotEmpty(referer) && referer.contains("no-idm=true"); + } + + private void forwardRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String contextPath = request.getContextPath(); + String relativePath = request.getRequestURI().replace(contextPath, ""); + request.getRequestDispatcher(relativePath).forward(request, response); + } +} diff --git a/src/main/resources/com/fr/plugin/error.html b/src/main/resources/com/fr/plugin/error.html new file mode 100644 index 0000000..f5e45f8 --- /dev/null +++ b/src/main/resources/com/fr/plugin/error.html @@ -0,0 +1,16 @@ + + + + + + + 无权访问 + + +
+

用户不在白名单,访问被拒绝,请联系管理员处理

+
+
+ + \ No newline at end of file diff --git a/集成Filter需求文档.docx b/集成Filter需求文档.docx new file mode 100644 index 0000000000000000000000000000000000000000..fde5f888317f72bee139e754c36252a6092ef271 GIT binary patch literal 12958 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 literal 0 HcmV?d00001