diff --git a/JSD-8277 -需求确认书V1.1.docx b/JSD-8277 -需求确认书V1.1.docx new file mode 100644 index 0000000..22b11a7 Binary files /dev/null and b/JSD-8277 -需求确认书V1.1.docx differ diff --git a/README.md b/README.md index 624bf42..2dca85f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # open-JSD-8277 -JSD-8277 开源任务材料 \ No newline at end of file +JSD-8277 开源任务材料\ +免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ +仅作为开发者学习参考使用!禁止用于任何商业用途!\ +为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。 \ No newline at end of file diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..90f16e9 --- /dev/null +++ b/plugin.xml @@ -0,0 +1,17 @@ + + + com.fr.plugin.xxxx.sso + + yes + 1.1 + 10.0 + 2018-07-31 + fr.open + + + + + + + + \ No newline at end of file diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..ae69d05 --- /dev/null +++ b/readme.txt @@ -0,0 +1,6 @@ +此次完成功能如下 + a、单点登录 + +1、将压缩文件解压后的tengyuejz.properties配置文件拷贝至 %部署路径%/WEB-INF/resources +2、安装本插件,插件安装见连接http://help.finereport.com/doc-view-2198.html +3、进入系统测试单点登录,访问地址为http://ip:port/webroot/decision \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/xxxx/sso/CommonUtils.java b/src/main/java/com/fr/plugin/xxxx/sso/CommonUtils.java new file mode 100644 index 0000000..8521820 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/sso/CommonUtils.java @@ -0,0 +1,90 @@ +package com.fr.plugin.xxxx.sso; + +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.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 javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Properties; + +/** + * @author fr.open + * @since 2021/7/12 + */ +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) { + 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) { + throw new RuntimeException(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, username); + 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 +"\"登录失败"); + } + } + +} diff --git a/src/main/java/com/fr/plugin/xxxx/sso/CustomLogInOutEventProvider.java b/src/main/java/com/fr/plugin/xxxx/sso/CustomLogInOutEventProvider.java new file mode 100644 index 0000000..bd0f346 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/sso/CustomLogInOutEventProvider.java @@ -0,0 +1,41 @@ +package com.fr.plugin.xxxx.sso; + +import com.fr.decision.fun.impl.AbstractLogInOutEventProvider; +import com.fr.decision.webservice.login.LogInOutResultInfo; +import com.fr.general.PropertiesUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Properties; + +/** + * @Author fr.open + * @since 2021/7/12 + **/ +public class CustomLogInOutEventProvider extends AbstractLogInOutEventProvider { + + private final String apiClientId; + + private final String logoutURL; + + private final String logoutRedirectURL; + + public CustomLogInOutEventProvider() { + Properties props = PropertiesUtils.getProperties("xxxx"); + this.apiClientId = CommonUtils.getProperty(props, "api.client-id", false); + this.logoutURL = CommonUtils.getProperty(props, "api.logout", false); + this.logoutRedirectURL = CommonUtils.getProperty(props, "api.logout-redirct-url", "http://www.baidu.com", true); + } + + @Override + public String logoutAction(LogInOutResultInfo result) { + try { + return String.format("%s?redirctToUrl=%s&redirectToLogin=true&entityId=%s", + logoutURL, + URLEncoder.encode(logoutRedirectURL, "utf-8"), + apiClientId); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/fr/plugin/xxxx/sso/SsoFilter.java b/src/main/java/com/fr/plugin/xxxx/sso/SsoFilter.java new file mode 100644 index 0000000..f9b73a9 --- /dev/null +++ b/src/main/java/com/fr/plugin/xxxx/sso/SsoFilter.java @@ -0,0 +1,165 @@ +package com.fr.plugin.tengyuejz.sso; + +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.general.PropertiesUtils; +import com.fr.general.http.HttpToolbox; +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 javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; + +import static com.fr.plugin.tengyuejz.sso.CommonUtils.*; + +/** + * @author fr.open + * @since 2021/7/12 + */ +@FunctionRecorder +public class SsoFilter extends AbstractGlobalRequestFilterProvider { + + private static String[] NOT_FILTER = { + "/decision/file", + "/decision/resources", + "/system", + "/materials.min.js.map", + "/remote", + "/login", + "/login/config" + }; + + private final String apiClientId; + + private final String apiClientSecret; + + private final String apiAuthorize; + + private final String apiGetToken; + + private final String apiGetUser; + + private final String errorOutput; + + private final String systemName; + + private final String bipName; + + private final String telephone; + + public SsoFilter() { + Properties props = PropertiesUtils.getProperties("xxxx"); + this.apiClientId = getProperty(props, "api.client-id", false); + this.apiClientSecret = getProperty(props, "api.client-secret", false); + this.apiAuthorize = getProperty(props, "api.authorize", false); + this.apiGetToken = getProperty(props, "api.get-token", false); + this.apiGetUser = getProperty(props, "api.get-user", false); + this.errorOutput = getProperty(props, "api.error-output", false); + this.systemName = getProperty(props, "info.system-name", false); + this.bipName = getProperty(props, "info.bip-name", false); + this.telephone = getProperty(props, "info.telephone", false); + } + + @Override + public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { + if (isAccept(request)) { + next(request, response, chain); + return; + } + + try { + String code = request.getParameter("code"); + if (StringUtils.isBlank(code)) { + String redirectURL = URLEncoder.encode(request.getRequestURL().toString(), "utf-8"); + String authorizeURL = String.format("%s?client_id=%s&redirect_uri=%s&response_type=code", apiAuthorize, apiClientId, redirectURL); + FineLoggerFactory.getLogger().info("没有在参数中找到code值,跳转到认证登录地址, 认证地址为\"{}\"", authorizeURL); + response.sendRedirect(authorizeURL); + return; + } + login(getUsername(getToken(code)), request, response); + next(request, response, chain); + } catch (Exception e) { + FineLoggerFactory.getLogger().error("单点登录处理失败.", e); + setError(response, e.getMessage()); + } + } + + @Override + public String filterName() { + return "sso"; + } + + @Override + public String[] urlPatterns() { + return new String[]{"/*"}; + } + + private boolean isAccept(HttpServletRequest request) { + String url = request.getRequestURL().toString(); + return isLogin(request) || Stream.of(NOT_FILTER).anyMatch(url::contains); + } + + private String getToken(String code) throws IOException { + Map params = new HashMap<>(); + params.put("client_id", apiClientId); + params.put("client_secret", apiClientSecret); + params.put("code", code); + params.put("grant_type", "authorization_code"); + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + String res = HttpToolbox.post(apiGetToken, params, headers); + FineLoggerFactory.getLogger().info("获取Token接口返回内容: \"{}\"", res); + JSONObject body = new JSONObject(res); + if (body.has("access_token")) { + String token = body.getString("access_token"); + FineLoggerFactory.getLogger().info("获取到的token值为: \"{}\"", token); + return token; + } + throw new RuntimeException("获取token失败"); + } + + private String getUsername(String token) throws IOException { + String api = String.format("%s?access_token=%s&client_id=%s", apiGetUser, token, apiClientId); + FineLoggerFactory.getLogger().info("获取用户信息接口访问地址为: \"{}\"", api); + String res = HttpToolbox.get(api); + FineLoggerFactory.getLogger().info("获取用户信息接口返回内容: \"{}\"", res); + JSONObject body = new JSONObject(res); + if (body.has("spRoleList")) { + JSONArray roles = body.getJSONArray("spRoleList"); + if (roles.size() > 0) { + String username = roles.getString(0); + FineLoggerFactory.getLogger().info("获取到的用户名为: \"{}\"", username); + return username; + } else { + throw new RuntimeException("系统未授权, 当前用户是\"" + body.getString("displayName") + "\""); + } + } + throw new RuntimeException("访问获取用户信息接口失败"); + } + + private void setError(HttpServletResponse res, String reason) { + try { + String URL = String.format( + "%s?systemName=%s&bipName=%s&telephone=%s&reason=%s", + errorOutput, + URLEncoder.encode(systemName, "utf-8"), + URLEncoder.encode(bipName, "utf-8"), + URLEncoder.encode(telephone, "utf-8"), + URLEncoder.encode(reason, "utf-8") + ); + res.sendRedirect(URL); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + +} diff --git a/src/main/resources/xxxx.properties b/src/main/resources/xxxx.properties new file mode 100644 index 0000000..12cac66 --- /dev/null +++ b/src/main/resources/xxxx.properties @@ -0,0 +1,23 @@ +# ͳһ֤ƽ̨֤¼ַ +api.authorize=https://xxx:8443/idp/oauth2/authorize +# ȡaccess_tokenĽӿڵַ +api.get-token=https://xxx:8443/idp/oauth2/getToken +# ȡûϢĽӿڵַ +api.get-user=https://xxx:8443/idp/oauth2/getUserInfo +# ע¼Ľӿڵַ +api.logout=https://xxx:8443/idp/profile/SAML2/Redirect/GLO +# ע¼תĵַ +api.logout-redirct-url=http://xxx +# Ϣַ +api.error-output=https://xxx:8443/idp/themes/error/defpage-403.html +# ֤ƽ̨client_id +api.client-id=xxx +# ֤ƽ̨client_secret +api.client-secret=xxxx + +# ϵͳ +info.system-name=xxxx +# άԱ +info.bip-name=xxxx +# άԱ绰 +info.telephone=12345678901 \ No newline at end of file diff --git a/xxxx.properties b/xxxx.properties new file mode 100644 index 0000000..12cac66 --- /dev/null +++ b/xxxx.properties @@ -0,0 +1,23 @@ +# ͳһ֤ƽ̨֤¼ַ +api.authorize=https://xxx:8443/idp/oauth2/authorize +# ȡaccess_tokenĽӿڵַ +api.get-token=https://xxx:8443/idp/oauth2/getToken +# ȡûϢĽӿڵַ +api.get-user=https://xxx:8443/idp/oauth2/getUserInfo +# ע¼Ľӿڵַ +api.logout=https://xxx:8443/idp/profile/SAML2/Redirect/GLO +# ע¼תĵַ +api.logout-redirct-url=http://xxx +# Ϣַ +api.error-output=https://xxx:8443/idp/themes/error/defpage-403.html +# ֤ƽ̨client_id +api.client-id=xxx +# ֤ƽ̨client_secret +api.client-secret=xxxx + +# ϵͳ +info.system-name=xxxx +# άԱ +info.bip-name=xxxx +# άԱ绰 +info.telephone=12345678901 \ No newline at end of file