From 37bd097a71f19906258ccde9a20c4a94a913308c Mon Sep 17 00:00:00 2001 From: "LAPTOP-SB56SG4Q\\86185" Date: Tue, 8 Feb 2022 10:53:45 +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 --- JSD-9224-需求确认书V1.docx | Bin 0 -> 58781 bytes README.md | 5 +- plugin.xml | 30 ++ .../BodyReaderHttpServletRequestWrapper.java | 65 ++++ .../sso/CustomLogInOutEventProvider.java | 39 +++ .../com/fr/plugin/xxxx/gjdbjj/sso/LRGT.java | 28 ++ .../plugin/xxxx/gjdbjj/sso/LoginFilter.java | 162 +++++++++ .../xxxx/gjdbjj/sso/PluginConstants.java | 13 + .../xxxx/gjdbjj/sso/conf/AuthSsoConfig.java | 43 +++ .../xxxx/gjdbjj/sso/utils/CommonUtils.java | 132 +++++++ .../xxxx/gjdbjj/sso/utils/HttpUtil.java | 327 ++++++++++++++++++ .../xxxx/gjdbjj/sso/utils/LogUtils.java | 121 +++++++ src/main/resources/sso.properties | 14 + 13 files changed, 978 insertions(+), 1 deletion(-) create mode 100644 JSD-9224-需求确认书V1.docx create mode 100644 plugin.xml create mode 100644 src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/BodyReaderHttpServletRequestWrapper.java create mode 100644 src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/CustomLogInOutEventProvider.java create mode 100644 src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/LRGT.java create mode 100644 src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/LoginFilter.java create mode 100644 src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/PluginConstants.java create mode 100644 src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/conf/AuthSsoConfig.java create mode 100644 src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/CommonUtils.java create mode 100644 src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/HttpUtil.java create mode 100644 src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/LogUtils.java create mode 100644 src/main/resources/sso.properties diff --git a/JSD-9224-需求确认书V1.docx b/JSD-9224-需求确认书V1.docx new file mode 100644 index 0000000000000000000000000000000000000000..fe102636565f26f5c271ce60444668b871bb8b8a GIT binary patch literal 58781 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 C@!GTi literal 0 HcmV?d00001 diff --git a/README.md b/README.md index fbe949b..58313bb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # open-JSD-9224 -JSD-9224 OAuth2单点 \ No newline at end of file +JSD-9224 OAuth2单点\ +免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ +仅作为开发者学习参考使用!禁止用于任何商业用途!\ +为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。 \ No newline at end of file diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..c29a40b --- /dev/null +++ b/plugin.xml @@ -0,0 +1,30 @@ + + + com.fr.plugin.xxxx.gjdbjj.sso + + yes + 1.10 + 10.0 + 2018-07-31 + fr.open + + + [2021-12-27]【1.1】适配原始app。
+ [2021-12-27]【1.2】去掉移动端。
+ [2021-12-27]【1.3】接口修改。
+ [2021-12-28]【1.4】增加接口授权。
+ [2021-12-28]【1.5】接口加载修改。
+ [2021-12-29]【1.6】修改引用。
+ [2021-12-29]【1.7】用户排除和接口返回修改。
+ [2021-12-29]【1.8】返回字段修改。
+ [2021-12-29]【1.9】替换请求数据。
+ [2022-01-10]【1.10】接口修改。
+ ]]>
+ + + + + + +
\ No newline at end of file diff --git a/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/BodyReaderHttpServletRequestWrapper.java b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/BodyReaderHttpServletRequestWrapper.java new file mode 100644 index 0000000..e87b3c9 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/BodyReaderHttpServletRequestWrapper.java @@ -0,0 +1,65 @@ +package com.fr.plugin.xxxx.gjdbjj.sso; + +import com.fr.log.FineLoggerFactory; +import com.fr.third.jodd.io.StreamUtil; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * @author fr.open + * @date 2019/7/2 + */ +public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { + private BufferedReader br; + private byte[] body; + public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + //this.br = request.getReader(); + body = StreamUtil.readBytes(request.getReader(), "UTF-8"); + FineLoggerFactory.getLogger().info("data is {}",new String(body)); + } + + public void setBody(byte[] body){ + this.body =body; + } + + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() { + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + + @Override + public int read() throws IOException { + return bais.read(); + } + }; + } +} diff --git a/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/CustomLogInOutEventProvider.java b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/CustomLogInOutEventProvider.java new file mode 100644 index 0000000..2d208c4 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/CustomLogInOutEventProvider.java @@ -0,0 +1,39 @@ +package com.fr.plugin.xxxx.gjdbjj.sso; + +import com.fr.decision.fun.impl.AbstractLogInOutEventProvider; +import com.fr.decision.webservice.login.LogInOutResultInfo; +import com.fr.general.PropertiesUtils; +import com.fr.plugin.xxxx.gjdbjj.sso.utils.LogUtils; +import com.fr.stable.StringUtils; +import com.fr.third.springframework.web.util.WebUtils; + +import javax.servlet.http.Cookie; +import java.util.Properties; + +import static com.fr.plugin.xxxx.gjdbjj.sso.utils.CommonUtils.getProperty; + + +/** + * @Author fr.open + * @since 2021/8/24 + **/ +public class CustomLogInOutEventProvider extends AbstractLogInOutEventProvider { + + @Override + public String logoutAction(LogInOutResultInfo result) { + Cookie token = WebUtils.getCookie(result.getRequest(), "ACCESS_TOKEN"); + if (token != null) { + Properties props = PropertiesUtils.getProperties("sso"); + String logoutURL = getProperty(props, "api.logout", true); + String redirectUri = getProperty(props, "api.redirectUri", true); + if (StringUtils.isBlank(logoutURL)) { + return StringUtils.EMPTY; + } + String format = String.format("%s?redirect_uri=%s&access_token=%s", logoutURL, redirectUri, token.getValue()); + LogUtils.debug4plugin("get logout URL is {}", format); + return format; + } + + return StringUtils.EMPTY; + } +} diff --git a/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/LRGT.java b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/LRGT.java new file mode 100644 index 0000000..8b2b462 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/LRGT.java @@ -0,0 +1,28 @@ +package com.fr.plugin.xxxx.gjdbjj.sso; + +import com.fr.plugin.context.PluginContext; +import com.fr.plugin.xxxx.gjdbjj.sso.conf.AuthSsoConfig; +import com.fr.plugin.observer.inner.AbstractPluginLifecycleMonitor; + +/** + * 配置信息初始化 + */ + +public class LRGT extends AbstractPluginLifecycleMonitor { + @Override + public void afterRun(PluginContext pluginContext) { + AuthSsoConfig.getInstance(); + } + + @Override + public void beforeStop(PluginContext pluginContext) { + } + + @Override + public void beforeUninstall(PluginContext pluginContext) { + } + + @Override + public void afterInstall(PluginContext var1) { + } +} \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/LoginFilter.java b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/LoginFilter.java new file mode 100644 index 0000000..9a3977e --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/LoginFilter.java @@ -0,0 +1,162 @@ +package com.fr.plugin.xxxx.gjdbjj.sso; + +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.decision.privilege.TransmissionTool; +import com.fr.decision.webservice.bean.authentication.LoginRequestInfoBean; +import com.fr.general.PropertiesUtils; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.context.PluginContexts; +import com.fr.plugin.xxxx.gjdbjj.sso.utils.HttpUtil; +import com.fr.plugin.xxxx.gjdbjj.sso.utils.LogUtils; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.stable.StringUtils; +import com.fr.stable.fun.Authorize; +import com.fr.third.org.apache.commons.codec.digest.DigestUtils; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; + +import static com.fr.plugin.xxxx.gjdbjj.sso.utils.CommonUtils.next; + + +/** + * @author fr.open + * @since 2021/12/04 + */ +@FunctionRecorder +@Authorize(callSignKey = PluginConstants.PLUGIN_ID) +public class LoginFilter extends AbstractGlobalRequestFilterProvider { + + private String passLogin; + + private String headerAuth; + + private String extraUser; + + private String tokenInfo; + + private void initParams() { + Properties props = PropertiesUtils.getProperties("sso"); + this.passLogin = props.getProperty("api.passLogin"); + LogUtils.debug4plugin("get passLogin config is {}", passLogin); + this.headerAuth = props.getProperty("api.headerAuth"); + LogUtils.debug4plugin("get headerAuth config is {}", headerAuth); + this.extraUser = props.getProperty("api.extraUser"); + LogUtils.debug4plugin("get extraUser config is {}", extraUser); + this.tokenInfo = props.getProperty("api.tokenInfo"); + LogUtils.debug4plugin("get tokenInfo config is {}", tokenInfo); + + } + + @Override + public void init(FilterConfig filterConfig) { + super.init(filterConfig); + initParams(); + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { + if (request.getMethod().equals("POST") && + request.getRequestURI().equals("/webroot/decision/login") + && WebUtils.getDevice(request).isMobile() + ) { + try { + LogUtils.debug4plugin("current request is mobile request"); + BodyReaderHttpServletRequestWrapper wrapper = new BodyReaderHttpServletRequestWrapper(request); + executeLogin(wrapper, response); + next(wrapper, response, chain); + return; + } catch (IOException e) { + LogUtils.error(e.getMessage(), e); + } + } + next(request, response, chain); + } + + private void executeLogin(BodyReaderHttpServletRequestWrapper request, HttpServletResponse response) { + LoginRequestInfoBean info = getLoginInfo(request); + if (StringUtils.isNotBlank(extraUser) && Stream.of(extraUser.split(",")).anyMatch(e -> e.equals(info.getUsername()))) { + return; + } + Map header = new HashMap<>(); + header.put("Content-Type", "application/x-www-form-urlencoded"); + header.put("Authorization", "Basic " + headerAuth); + HashMap params = new HashMap<>(); + params.put("password", DigestUtils.md5Hex(info.getPassword()).toLowerCase()); + params.put("username", info.getUsername()); + params.put("grant_type", "password"); + String res = HttpUtil.doFormPost(passLogin, header, params, "UTF-8"); + LogUtils.debug4plugin("valid password res is {} by param {} to {}", res, params, passLogin); + if (StringUtils.isNotBlank(res)) { + JSONObject object = new JSONObject(res); + if (object.getJSONObject("datas").get("access_token") != null) { + String pwd = getLogin(object.getJSONObject("datas").getString("access_token")); + info.setPassword(TransmissionTool.encrypt(pwd)); + request.setBody(JSONObject.mapFrom(info).toString().getBytes(StandardCharsets.UTF_8)); + } + } + } + + private String getLogin(String token) { + String url = String.format("%s?token=%s", tokenInfo, token); + String res = HttpUtil.sendGet(url, null, null); + LogUtils.debug4plugin("token info res is {} by {}", res, url); + if (StringUtils.isNotBlank(res)) { + JSONObject object = new JSONObject(res); + if (object.getJSONObject("datas").get("frPwd") != null) { + return object.getJSONObject("datas").getString("frPwd"); + } + } + return StringUtils.EMPTY; + } + + public LoginRequestInfoBean getLoginInfo(HttpServletRequest req) { + try { + BufferedReader br = req.getReader(); + String str = ""; + String listString = ""; + while ((str = br.readLine()) != null) { + listString += str; + } + JSONObject jsonObject = new JSONObject(listString); + LoginRequestInfoBean info = jsonObject.mapTo(LoginRequestInfoBean.class); + info.setPassword(TransmissionTool.decrypt(info.isEncrypted(), info.getPassword())); + return info; + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + return null; + } + + @Override + public String filterName() { + return "gjdbjj"; + } + + @Override + public String[] urlPatterns() { + if (PluginContexts.currentContext().isAvailable()) { + return new String[]{ + //"/decision/", + "/decision/login", + //"/decision", + //"/decision/view/report", + //"/decision/view/form" + }; + }else { + return new String[0]; + } + } + +} diff --git a/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/PluginConstants.java b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/PluginConstants.java new file mode 100644 index 0000000..e0a2ec7 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/PluginConstants.java @@ -0,0 +1,13 @@ +package com.fr.plugin.xxxx.gjdbjj.sso; + +/** + * @author fr.open + * @since 2021/12/04 + */ +public class PluginConstants { + + public static final String PLUGIN_ID = "com.fr.plugin.xxxx.gjdbjj.sso"; + + public static final String PLUGIN_NAME = "Oauth2"; + +} diff --git a/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/conf/AuthSsoConfig.java b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/conf/AuthSsoConfig.java new file mode 100644 index 0000000..9ad2eaa --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/conf/AuthSsoConfig.java @@ -0,0 +1,43 @@ +package com.fr.plugin.xxxx.gjdbjj.sso.conf; + +import com.fr.config.*; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; + + +/** + * @author fr.open + * @since 2021/12/04 + */ +@Visualization(category = "Oauth2配置") +public class AuthSsoConfig extends DefaultConfiguration { + + private static volatile AuthSsoConfig config = null; + + public static AuthSsoConfig getInstance() { + if (config == null) { + config = ConfigContext.getConfigInstance(AuthSsoConfig.class); + } + return config; + } + + @Identifier(value = "debugSwitch", name = "插件调试开关", description = "日志调试模式", status = Status.SHOW) + private Conf debugSwitch = Holders.simple(true); + + public Boolean getDebugSwitch() { + return this.debugSwitch.get(); + } + + public void setDebugSwitch(Boolean debugSwitch) { + this.debugSwitch.set(debugSwitch); + } + + @Override + public Object clone() throws CloneNotSupportedException { + AuthSsoConfig cloned = (AuthSsoConfig) super.clone(); + cloned.debugSwitch = (Conf) debugSwitch.clone(); + return cloned; + } + + +} diff --git a/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/CommonUtils.java b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/CommonUtils.java new file mode 100644 index 0000000..0e598b7 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/CommonUtils.java @@ -0,0 +1,132 @@ +package com.fr.plugin.xxxx.gjdbjj.sso.utils; + +import com.fr.data.NetworkHelper; +import com.fr.decision.authority.data.User; +import com.fr.decision.mobile.terminal.TerminalHandler; +import com.fr.decision.webservice.utils.DecisionServiceConstants; +import com.fr.decision.webservice.utils.DecisionStatusService; +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.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.stable.web.Device; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; +import java.util.Properties; + +/** + * @author fr.open + * @since 2021/8/24 + */ +public class CommonUtils { + + public static String getProperty(Properties props, String key, String defaultValue, boolean allowBlank) { + String value = props.getProperty(key); + if (StringUtils.isNotBlank(value)) { + return value; + } else { + if (allowBlank) { + LogUtils.warn("Property[" + key + "] value is blank."); + return defaultValue; + } else { + throw new IllegalArgumentException("Property[" + key + "] cann't be blank."); + } + } + } + + public static String getProperty(Properties props, String key, boolean allowBlank) { + return getProperty(props, key, null, allowBlank); + } + + public static String getProperty(Properties props, String key) { + return getProperty(props, key, null, true); + } + + public static boolean isLogin(HttpServletRequest request) { + String oldToken = TokenResource.COOKIE.getToken(request); + return oldToken != null && checkTokenValid(request, (String) oldToken); + } + + private static boolean checkTokenValid(HttpServletRequest req, String token) { + try { + Device device = NetworkHelper.getDevice(req); + LoginService.getInstance().loginStatusValid(token, TerminalHandler.getTerminal(req, device)); + return true; + } catch (Exception ignore) { + } + return false; + } + + /** + * 跳转到过滤器链中的下一个过滤器 + * + * @param request + * @param response + * @param chain + */ + public static void next(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { + try { + chain.doFilter(request, response); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(),e); + } + } + + public static void login(String username, HttpServletRequest request, HttpServletResponse response) { + try { + User user = UserService.getInstance().getUserByUserName(username); + if (user == null) { + throw new RuntimeException("系统未授权, 当前用户是\"" + username + "\""); + } + String token = LoginService.getInstance().login(request, response, user.getUserName()); + request.setAttribute(DecisionServiceConstants.FINE_AUTH_TOKEN_NAME, token); + } catch (Exception e) { + FineLoggerFactory.getLogger().error("sso >> Failed to login with[" + username + "]", e); + throw new RuntimeException("用户\"" + username + "\"登录失败"); + } + } + + public static boolean isMobileDevice(HttpServletRequest request) { + if (WebUtils.getDevice(request).isMobile()) { + LogUtils.debug4plugin("current request is is mobile request ,url is {}", request.getRequestURI()); + return true; + } + String requestHeader = request.getHeader("user-agent"); + String[] deviceArray = new String[]{"android", "iphone", "ipad", "ios", "windows phone", "wechat"}; + if (requestHeader == null) { + return false; + } + requestHeader = requestHeader.toLowerCase(); + for (int i = 0; i < deviceArray.length; i++) { + if (requestHeader.toLowerCase().contains(deviceArray[i])) { + LogUtils.debug4plugin("current request:{} is mobile request!", request.getRequestURI()); + return true; + } + } + String op = WebUtils.getHTTPRequestParameter(request, "op"); + return StringUtils.isNotBlank(op) && StringUtils.equals("h5", op); + } + + public static void cacheParams(String key, Map values) { + try { + DecisionStatusService.originUrlStatusService().put(key, values); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static String getCachedParam(String key, String name) { + try { + Map values = DecisionStatusService.originUrlStatusService().get(key); + return values.get(name); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/HttpUtil.java b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/HttpUtil.java new file mode 100644 index 0000000..b75a141 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/HttpUtil.java @@ -0,0 +1,327 @@ +package com.fr.plugin.xxxx.gjdbjj.sso.utils; + +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.third.org.apache.http.HttpResponse; +import com.fr.third.org.apache.http.HttpStatus; +import com.fr.third.org.apache.http.NameValuePair; +import com.fr.third.org.apache.http.client.HttpClient; +import com.fr.third.org.apache.http.client.entity.UrlEncodedFormEntity; +import com.fr.third.org.apache.http.client.methods.HttpPost; +import com.fr.third.org.apache.http.config.Registry; +import com.fr.third.org.apache.http.config.RegistryBuilder; +import com.fr.third.org.apache.http.conn.socket.ConnectionSocketFactory; +import com.fr.third.org.apache.http.conn.socket.PlainConnectionSocketFactory; +import com.fr.third.org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import com.fr.third.org.apache.http.impl.client.HttpClients; +import com.fr.third.org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import com.fr.third.org.apache.http.message.BasicNameValuePair; +import com.fr.third.org.apache.http.util.EntityUtils; + +import javax.net.ssl.*; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @Author fr.open + * @Date 2020/12/05 + * @Description + **/ +public class HttpUtil { + + private static HostnameVerifier hv = new HostnameVerifier() { + @Override + public boolean verify(String urlHostName, SSLSession session) { + System.out.println("Warning: URL Host: " + urlHostName + " vs. " + + session.getPeerHost()); + return true; + } + }; + + /** + * 发送get请求 + * + * @param url + * @param param + * @param header + * @return + * @throws IOException + */ + public static String sendGet(String url, Map param, Map header) { + String result = ""; + BufferedReader in = null; + String urlNameString = url; + try { + if (param != null) { + urlNameString += "?"; + urlNameString += param.entrySet() + .stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining("&")); + } + + URL realUrl = new URL(urlNameString); + // 打开和URL之间的连接 + HttpURLConnection connection; + if (url.startsWith("https")) { + trustAllHttpsCertificates(); + HttpsURLConnection.setDefaultHostnameVerifier(hv); + connection = (HttpURLConnection) realUrl.openConnection(); + } else { + connection = (HttpURLConnection) realUrl.openConnection(); + } + //设置超时时间 + connection.setDoInput(true); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(15000); + // 设置通用的请求属性 + if (header != null) { + Iterator> it = header.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + System.out.println(entry.getKey() + ":::" + entry.getValue()); + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } + } + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + // 建立实际的连接 + connection.connect(); + // 定义 BufferedReader输入流来读取URL的响应,设置utf8防止中文乱码 + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8")); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + if (in != null) { + in.close(); + } + }catch (Exception e){ + FineLoggerFactory.getLogger().error(e,"get url error ,url is:{},error is {}",urlNameString,e.getMessage()); + } + return result; + } + + public static String doFormPost(String url, Map header, Map map, String chartset) { + //声明返回结果 + String result = ""; + UrlEncodedFormEntity entity = null; + HttpResponse httpResponse = null; + HttpClient httpClient = null; + try { + // 创建连接 + httpClient = HttpClients.createDefault(); + ; + if (url.startsWith("https")) { + SSLContext sslcontext = createIgnoreVerifySSL(); + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.INSTANCE) + .register("https", new SSLConnectionSocketFactory(sslcontext)) + .build(); + PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + HttpClients.custom().setConnectionManager(connManager); + httpClient = HttpClients.custom().setConnectionManager(connManager).build(); + } + // 设置请求头和报文 + HttpPost httpPost = new HttpPost(url); + if (header != null) { + header.forEach((k, v) -> { + httpPost.setHeader(k, String.valueOf(v)); + }); + } + //设置参数 + List list = new ArrayList(); + Iterator iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry elem = (Map.Entry) iterator.next(); + list.add(new BasicNameValuePair(elem.getKey(), elem.getValue())); + } + entity = new UrlEncodedFormEntity(list, chartset == null ? "UTF-8" : chartset); + httpPost.setEntity(entity); + //执行发送,获取相应结果 + httpResponse = httpClient.execute(httpPost); + if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + result = EntityUtils.toString(httpResponse.getEntity()); + } else { + FineLoggerFactory.getLogger().error("Http post form code is {},message is {}", httpResponse.getStatusLine().getStatusCode(), EntityUtils.toString(httpResponse.getEntity())); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + return result; + + } + + public static String sendPost(String url,Map header, JSONObject body) { + PrintWriter out = null; + BufferedReader in = null; + String result = StringUtils.EMPTY; + String res = StringUtils.EMPTY; + try { + String urlNameString = url; + + URL realUrl = new URL(urlNameString); + // 打开和URL之间的连接 + HttpURLConnection conn; + if (url.startsWith("https")) { + trustAllHttpsCertificates(); + HttpsURLConnection.setDefaultHostnameVerifier(hv); + conn = (HttpURLConnection) realUrl.openConnection(); + } else { + conn = (HttpURLConnection) realUrl.openConnection(); + } + // 设置通用的请求属性 + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + if(body != null && !body.isEmpty()){ + conn.setRequestProperty("Content-Type","application/json;;charset=UTF-8"); + } + if(header != null){ + header.forEach((k, v) -> { + conn.setRequestProperty(k, v); + }); + } + // 发送POST请求必须设置如下两行 + conn.setDoOutput(true); + conn.setDoInput(true); + //获取请求头 + + // 获取URLConnection对象对应的输出流 + out = new PrintWriter(conn.getOutputStream()); + StringBuffer buffer = new StringBuffer(); + + // 发送请求参数 + if(body != null){ + out.print(body.toString()); + } + // flush输出流的缓冲 + out.flush(); + // 定义BufferedReader输入流来读取URL的响应 + in = new BufferedReader( + new InputStreamReader(conn.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + result += line; + } + res = result; + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(),e); + } + //使用finally块来关闭输出流、输入流 + finally{ + try{ + if(out!=null){ + out.close(); + } + if(in!=null){ + in.close(); + } + } + catch(IOException e){ + FineLoggerFactory.getLogger().error(e.getMessage(),e); + } + } + return res; + } + + private static void trustAllHttpsCertificates() throws Exception { + javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1]; + javax.net.ssl.TrustManager tm = new miTM(); + trustAllCerts[0] = tm; + javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("SSL","SunJSSE"); + sc.init(null, trustAllCerts, null); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } + + + /** + * encode url by UTF-8 + * @param url url before encoding + * @return url after encoding + */ + public static String encodeUrl(String url){ + String eurl = url; + try { + eurl = URLEncoder.encode(url,"UTF-8"); + } catch (UnsupportedEncodingException e) { + } + return eurl; + } + + private static class miTM implements javax.net.ssl.TrustManager, + javax.net.ssl.X509TrustManager { + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public boolean isServerTrusted( + java.security.cert.X509Certificate[] certs) { + return true; + } + + public boolean isClientTrusted( + java.security.cert.X509Certificate[] certs) { + return true; + } + + @Override + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) + throws java.security.cert.CertificateException { + return; + } + + @Override + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) + throws java.security.cert.CertificateException { + return; + } + } + + public static SSLContext createIgnoreVerifySSL() { + try { + SSLContext sc = SSLContext.getInstance("SSLv3"); + + // 实现一个X509TrustManager接口,用于绕过验证,不用修改里面的方法 + X509TrustManager trustManager = new X509TrustManager() { + @Override + public void checkClientTrusted( + java.security.cert.X509Certificate[] paramArrayOfX509Certificate, + String paramString) throws CertificateException { + } + + @Override + public void checkServerTrusted( + java.security.cert.X509Certificate[] paramArrayOfX509Certificate, + String paramString) throws CertificateException { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + }; + + sc.init(null, new TrustManager[]{trustManager}, null); + return sc; + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + return null; + } +} diff --git a/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/LogUtils.java b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/LogUtils.java new file mode 100644 index 0000000..18e6392 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/gjdbjj/sso/utils/LogUtils.java @@ -0,0 +1,121 @@ +package com.fr.plugin.xxxx.gjdbjj.sso.utils; + +import com.fr.log.FineLoggerFactory; +import com.fr.log.FineLoggerProvider; +import com.fr.plugin.context.PluginContexts; +import com.fr.plugin.xxxx.gjdbjj.sso.conf.AuthSsoConfig; +import com.fr.stable.StringUtils; + +/** + * @author fr.open + * @since 2021/12/04 + */ +public final class LogUtils { + private static final String DEBUG_PREFIX = "[插件调试] "; + private static String LOG_PREFIX = "[OAUTH2单点登陆] "; + 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 void debug4plugin(String s) { + if (AuthSsoConfig.getInstance().getDebugSwitch()) { + LOGGER.error(DEBUG_PREFIX + LOG_PREFIX + s); + } else { + LOGGER.debug(LOG_PREFIX + s); + } + } + + public static void debug4plugin(String s, Object... objects) { + if (AuthSsoConfig.getInstance().getDebugSwitch()) { + LOGGER.error(DEBUG_PREFIX + LOG_PREFIX + s, objects); + } else { + LOGGER.debug(LOG_PREFIX + s, objects); + } + } + + public static void debug4plugin(String s, Throwable throwable) { + if (AuthSsoConfig.getInstance().getDebugSwitch()) { + LOGGER.error(DEBUG_PREFIX + LOG_PREFIX + s, throwable); + } else { + 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/resources/sso.properties b/src/main/resources/sso.properties new file mode 100644 index 0000000..852ca72 --- /dev/null +++ b/src/main/resources/sso.properties @@ -0,0 +1,14 @@ +api.client_id=xxxxxx +api.client_secret=xxxxxx +api.authorize=http://127.0.0.1:8080/idp/oauth2/authorize +api.get-token=http://127.0.0.1:8080/idp/oauth2/getToken +api.get-user=http://127.0.0.1:8080/idp/oauth2/getUserInfo +api.redirectUri=http://127.0.0.1:8075/webroot/decision +api.logout=http://127.0.0.1:8080/idp/profile/OAUTH2/Redirect/GLO +api.scope=app +## app login url +api.passLogin= +## login header Auth +api.headerAuth= +##not sso users +api.extraUser=