From 0c22e8f1740a362be32c1a8d30519b47e2627fe4 Mon Sep 17 00:00:00 2001 From: "LAPTOP-SB56SG4Q\\86185" Date: Thu, 28 Oct 2021 11:58:08 +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-8486-需求文档V1.docx | Bin 0 -> 63761 bytes README.md | 5 +- conf.properties | 17 ++ plugin.xml | 22 ++ readme.txt | 7 + .../com/fr/plugin/idm/sso/BaseAction.java | 62 +++++ .../com/fr/plugin/idm/sso/IdmResponse.java | 100 +++++++ .../fr/plugin/idm/sso/PluginConstants.java | 11 + .../idm/sso/RecordDBAccessProvider.java | 69 +++++ .../java/com/fr/plugin/idm/sso/SsoFilter.java | 216 +++++++++++++++ .../com/fr/plugin/idm/sso/SyncException.java | 12 + .../fr/plugin/idm/sso/action/OrgAction.java | 35 +++ .../fr/plugin/idm/sso/action/UserAction.java | 83 ++++++ .../com/fr/plugin/idm/sso/dao/OrgDao.java | 21 ++ .../com/fr/plugin/idm/sso/dao/UserDao.java | 22 ++ .../com/fr/plugin/idm/sso/dao/UserOrgDao.java | 21 ++ .../fr/plugin/idm/sso/entity/OrgEntity.java | 90 +++++++ .../plugin/idm/sso/entity/UserDTOEntity.java | 75 ++++++ .../fr/plugin/idm/sso/entity/UserEntity.java | 73 ++++++ .../plugin/idm/sso/entity/UserOrgEntity.java | 69 +++++ .../fr/plugin/idm/sso/util/CommonUtils.java | 132 ++++++++++ .../com/fr/plugin/idm/sso/util/HttpUtil.java | 246 ++++++++++++++++++ src/main/resources/conf.properties | 17 ++ 23 files changed, 1404 insertions(+), 1 deletion(-) create mode 100644 JSD-8486-需求文档V1.docx create mode 100644 conf.properties create mode 100644 plugin.xml create mode 100644 readme.txt create mode 100644 src/main/java/com/fr/plugin/idm/sso/BaseAction.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/IdmResponse.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/PluginConstants.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/RecordDBAccessProvider.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/SsoFilter.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/SyncException.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/action/OrgAction.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/action/UserAction.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/dao/OrgDao.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/dao/UserDao.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/dao/UserOrgDao.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/entity/OrgEntity.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/entity/UserDTOEntity.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/entity/UserEntity.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/entity/UserOrgEntity.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/util/CommonUtils.java create mode 100644 src/main/java/com/fr/plugin/idm/sso/util/HttpUtil.java create mode 100644 src/main/resources/conf.properties diff --git a/JSD-8486-需求文档V1.docx b/JSD-8486-需求文档V1.docx new file mode 100644 index 0000000000000000000000000000000000000000..26c8209f8e6e1958d82854ebebd41620196933c6 GIT binary patch literal 63761 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 + com.fr.plugin.idm.sso + com.fr.plugin.idm.sso + + yes + 1.8 + 10.0 + 2018-07-31 + holger + + + + + + + + + + \ No newline at end of file diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..8191d54 --- /dev/null +++ b/readme.txt @@ -0,0 +1,7 @@ +此次完成功能如下 + a、单点登录 + +1、将压缩文件解压后的conf.properties配置文件拷贝至 %部署路径%/WEB-INF/resources, 并修改相应的属性配置 +2、安装本插件,插件安装见连接http://help.finereport.com/doc-view-2198.html +3、进入系统测试单点登录 + PC端访问地址为http://ip:port/webroot/decision \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/idm/sso/BaseAction.java b/src/main/java/com/fr/plugin/idm/sso/BaseAction.java new file mode 100644 index 0000000..9cc69de --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/BaseAction.java @@ -0,0 +1,62 @@ +package com.fr.plugin.idm.sso; + +import com.fr.general.IOUtils; +import com.fr.json.JSONObject; +import com.fr.json.revise.EmbedJson; +import com.fr.stable.StringUtils; +import com.fr.stable.db.entity.BaseEntity; +import com.fr.third.fasterxml.jackson.databind.DeserializationFeature; +import com.fr.third.fasterxml.jackson.databind.ObjectMapper; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @Author fr.open + * @Date 2020/8/18 + * @Description + **/ +public abstract class BaseAction { + + /** + * 执行的业务方法 + * @param req + * @param res + * @return + */ + public abstract IdmResponse handel(HttpServletRequest req, HttpServletResponse res) throws Exception; + + public T getBody(HttpServletRequest req,Class t) throws IOException, SyncException { + String body = IOUtils.inputStream2String(req.getInputStream()); + if(StringUtils.isBlank(body)){ + throw new SyncException("body_is_null"); + } + JSONObject jsonObject = new JSONObject(body); + if(jsonObject.isEmpty()){ + throw new SyncException("json_is_null"); + } + return (T) getEntity(jsonObject,t); + + + } + + public JSONObject getBody(HttpServletRequest req) throws IOException, SyncException { + String body = IOUtils.inputStream2String(req.getInputStream()); + if(StringUtils.isBlank(body)){ + throw new SyncException("body_is_null"); + } + JSONObject jsonObject = new JSONObject(body); + if(jsonObject.isEmpty()){ + throw new SyncException("json_is_null"); + } + return jsonObject; + + + } + + protected T getEntity(JSONObject object, Class t) { + ObjectMapper mapper = EmbedJson.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper.convertValue(object, t); + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/IdmResponse.java b/src/main/java/com/fr/plugin/idm/sso/IdmResponse.java new file mode 100644 index 0000000..82c4fdb --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/IdmResponse.java @@ -0,0 +1,100 @@ +package com.fr.plugin.idm.sso; + +import com.fr.third.fasterxml.jackson.annotation.JsonInclude; + +import java.io.Serializable; +import java.util.HashMap; + +/** + * @Author fr.open + * @Date 2020/12/10 + * @Description + **/ +@JsonInclude(JsonInclude.Include.NON_DEFAULT) +public class IdmResponse implements Serializable { + private int status; + private String errorCode; + private String errorMsg; + private Object result; + + public IdmResponse() { + } + + public IdmResponse status(int var1) { + this.status = var1; + return this; + } + + public IdmResponse errorCode(String var1) { + this.errorCode = var1; + return this; + } + + public IdmResponse errorMsg(String var1) { + this.errorMsg = var1; + return this; + } + + public IdmResponse data(Object var1) { + this.result = var1; + return this; + } + + public Object getResult() { + return this.result; + } + + public void setResult(Object var1) { + this.result = var1; + } + + public String getErrorCode() { + return this.errorCode; + } + + public void setErrorCode(String var1) { + this.errorCode = var1; + } + + public String getErrorMsg() { + return this.errorMsg; + } + + public void setErrorMsg(String var1) { + this.errorMsg = var1; + } + + public int getStatus() { + return this.status; + } + + public void setStatus(int var1) { + this.status = var1; + } + + private static IdmResponse create() { + return new IdmResponse(); + } + + public static IdmResponse ok(Object var0) { + return create().data(var0); + } + + public static IdmResponse success() { + return ok("0"); + } + + public static IdmResponse success(int var0) { + HashMap var1 = new HashMap(); + var1.put("count", var0); + return ok(var1); + } + + public static IdmResponse error(int var0, String var1, String var2) { + return create().status(var0).errorCode(var1).errorMsg(var2); + } + + public static IdmResponse error(String var0, String var1) { + return create().errorCode(var0).errorMsg(var1); + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/PluginConstants.java b/src/main/java/com/fr/plugin/idm/sso/PluginConstants.java new file mode 100644 index 0000000..09cb172 --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/PluginConstants.java @@ -0,0 +1,11 @@ +package com.fr.plugin.idm.sso; + +/** + * @Author fr.open + * @Date 2021/9/15 + * @Description + **/ +public class PluginConstants { + + public static final String PLUGIN_ID = "com.fr.plugin.idm.sso"; +} diff --git a/src/main/java/com/fr/plugin/idm/sso/RecordDBAccessProvider.java b/src/main/java/com/fr/plugin/idm/sso/RecordDBAccessProvider.java new file mode 100644 index 0000000..09c23f7 --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/RecordDBAccessProvider.java @@ -0,0 +1,69 @@ +package com.fr.plugin.idm.sso; + +import com.fr.db.fun.impl.AbstractDBAccessProvider; +import com.fr.plugin.idm.sso.dao.OrgDao; +import com.fr.plugin.idm.sso.dao.UserDao; +import com.fr.plugin.idm.sso.dao.UserOrgDao; +import com.fr.plugin.idm.sso.entity.OrgEntity; +import com.fr.plugin.idm.sso.entity.UserEntity; +import com.fr.plugin.idm.sso.entity.UserOrgEntity; +import com.fr.stable.db.accessor.DBAccessor; +import com.fr.stable.db.dao.BaseDAO; +import com.fr.stable.db.dao.DAOProvider; + +/** + * @Author fr.open + * @Date 2020/5/24 + **/ +public class RecordDBAccessProvider extends AbstractDBAccessProvider { + + private static DBAccessor dbAccessor = null; + + public static DBAccessor getDbAccessor() { + return dbAccessor; + } + + @Override + public DAOProvider[] registerDAO() { + return new DAOProvider[]{ + new DAOProvider() { + @Override + public Class getEntityClass() { + return UserEntity.class; + } + + @Override + public Class getDAOClass() { + return UserDao.class; + } + }, + new DAOProvider() { + @Override + public Class getEntityClass() { + return OrgEntity.class; + } + + @Override + public Class getDAOClass() { + return OrgDao.class; + } + }, + new DAOProvider() { + @Override + public Class getEntityClass() { + return UserOrgEntity.class; + } + + @Override + public Class getDAOClass() { + return UserOrgDao.class; + } + } + }; + } + + @Override + public void onDBAvailable(DBAccessor dbAccessor) { + RecordDBAccessProvider.dbAccessor = dbAccessor; + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/SsoFilter.java b/src/main/java/com/fr/plugin/idm/sso/SsoFilter.java new file mode 100644 index 0000000..0cd02c9 --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/SsoFilter.java @@ -0,0 +1,216 @@ +package com.fr.plugin.idm.sso; + +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.decision.webservice.Response; +import com.fr.decision.webservice.utils.WebServiceUtils; +import com.fr.general.PropertiesUtils; +import com.fr.general.http.HttpToolbox; +import com.fr.intelli.record.Focus; +import com.fr.intelli.record.Original; +import com.fr.json.JSONObject; +import com.fr.locale.InterProviderFactory; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.context.PluginContexts; +import com.fr.plugin.idm.sso.action.OrgAction; +import com.fr.plugin.idm.sso.action.UserAction; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.stable.StringUtils; +import com.fr.stable.fun.Authorize; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.UUID; +import java.util.stream.Stream; + +import static com.fr.plugin.idm.sso.util.CommonUtils.*; + +/** + * @Author fr.open + * @Date 2020/8/18 + * @Description + **/ +@FunctionRecorder +@Authorize(callSignKey = PluginConstants.PLUGIN_ID) +public class SsoFilter extends AbstractGlobalRequestFilterProvider { + + private final static String[] NOT_FILTER = { + "/decision/file", + "/decision/resources", + "/decision/login/config", + "/decision/system", + "/decision/login/slider", + "/decision/remote", + "/decision/login" + }; + + private String apiClientId; + + private String apiClientSecret; + + private String apiAuthorize; + + private String apiGetToken; + + private String apiGetUser; + + private void initParams() { + Properties props = PropertiesUtils.getProperties("conf"); + this.apiClientId = getProperty(props, "api.client_id", 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.apiClientSecret = getProperty(props, "api.client_secret", false); + } + + @Override + public String filterName() { + return "sso"; + } + + @Override + public String[] urlPatterns() { + return new String[]{"/*"}; + } + + @Override + @Focus(id = PluginConstants.PLUGIN_ID, text = "idm单点", source = Original.PLUGIN) + public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) { + if (!PluginContexts.currentContext().isAvailable()) { + next(req, res, chain); + return; + } + + BaseAction action = null; + if (req.getRequestURI().endsWith("sync/org")) { + action = new OrgAction(); + } else if (req.getRequestURI().endsWith("sync/user")) { + action = new UserAction(); + } + if (action != null) { + executeAction(req, res, action); + return; + } + //执行单点登录 + if (isAccept(req)) { + next(req, res, chain); + return; + } + try { + initParams(); + String code = req.getParameter("code"); + if (StringUtils.isBlank(code)) { + jumpAuthorize(req, res); + return; + } + login(getUsername(getToken(code, req)), req, res); + String state = req.getParameter("state"); + if (StringUtils.isNotBlank(state)) { + String accessURL = getCachedParam(state, "accessURL"); + if (StringUtils.isNotBlank(accessURL)) { + res.sendRedirect(accessURL); + return; + } + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error("sso >>> 单点登陆处理失败", e); + setError(res, e.getMessage()); + } + + next(req, res, chain); + } + + private boolean isAccept(HttpServletRequest request) { + String url = request.getRequestURL().toString(); + if (Stream.of(NOT_FILTER).anyMatch(url::contains)) { + return true; + } + return isLogin(request); + } + + private String getUsername(String token) throws IOException { + HashMap params = new HashMap<>(); + params.put("access_token", token); + String res = HttpToolbox.get(apiGetUser, params); + JSONObject body = new JSONObject(res); + if (body.has("name")) { + return body.getString("name"); + } + throw new RuntimeException("获取用户信息失败,Cause by: " + res); + } + + private String getToken(String code, HttpServletRequest request) throws IOException { + HashMap params = new HashMap<>(); + params.put("grant_type", "authorization_code"); + params.put("code", code); + params.put("client_id", apiClientId); + params.put("client_secret", apiClientSecret); + params.put("redirect_uri", request.getRequestURL().toString()); + String res = HttpToolbox.get(apiGetToken, params); + JSONObject body = new JSONObject(res); + if (body.has("access_token")) { + return body.getString("access_token"); + } + throw new RuntimeException("获取access_token失败,Cause by: " + res); + } + + private void jumpAuthorize(HttpServletRequest request, HttpServletResponse response) throws IOException { + String state = UUID.randomUUID().toString(); + String accessURL = request.getRequestURI(); + if (StringUtils.isNotBlank(request.getQueryString())) { + accessURL += "?" + request.getQueryString(); + } + Map params = new HashMap<>(); + params.put("accessURL", accessURL); + cacheParams(state, params); + String address = String.format("%s?response_type=code&client_id=%s&state=%s&redirect_uri=%s", apiAuthorize, apiClientId, state, URLEncoder.encode(request.getRequestURL().toString(), "utf-8")); + FineLoggerFactory.getLogger().info("oauth2 >>> 请求中不包含code值,条转到登陆页面 >>> \"{}\"", address); + response.sendRedirect(address); + } + + private void executeAction(HttpServletRequest req, HttpServletResponse res, BaseAction action) { + try { + IdmResponse response = action.handel(req, res); + WebUtils.printAsJSON(res, JSONObject.mapFrom(response)); + } catch (SyncException e) { + try { + WebUtils.printAsJSON(res, JSONObject.mapFrom(Response.error(e.getMessage(), e.getMessage()))); + } catch (Exception exception) { + FineLoggerFactory.getLogger().error(e.getMessage(),e); + } + } catch (Exception e) { + try { + FineLoggerFactory.getLogger().error(e.getMessage(),e); + WebUtils.printAsJSON(res, JSONObject.mapFrom(Response.error("unknown_error", e.getMessage()))); + } catch (Exception exception) { + FineLoggerFactory.getLogger().error(e.getMessage(),e); + } + } + } + + private void setError(HttpServletResponse res, String reason) { + try { + PrintWriter printWriter = WebUtils.createPrintWriter(res); + Map map = new HashMap<>(); + map.put("result", InterProviderFactory.getProvider().getLocText("Fine-Engine_Error_Page_Result")); + map.put("reason", reason); + map.put("solution", InterProviderFactory.getProvider().getLocText("Fine-Engine_Please_Contact_Platform_Admin")); + String page = WebServiceUtils.parseWebPageResourceSafe("com/fr/web/controller/decision/entrance/resources/unavailable.html", map); + printWriter.write(page); + printWriter.flush(); + printWriter.close(); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + + +} diff --git a/src/main/java/com/fr/plugin/idm/sso/SyncException.java b/src/main/java/com/fr/plugin/idm/sso/SyncException.java new file mode 100644 index 0000000..b93dcab --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/SyncException.java @@ -0,0 +1,12 @@ +package com.fr.plugin.idm.sso; + +/** + * @Author fr.open + * @Date 2020/8/19 + * @Description + **/ +public class SyncException extends Exception { + public SyncException(String message) { + super(message); + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/action/OrgAction.java b/src/main/java/com/fr/plugin/idm/sso/action/OrgAction.java new file mode 100644 index 0000000..2321d5d --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/action/OrgAction.java @@ -0,0 +1,35 @@ +package com.fr.plugin.idm.sso.action; + +import com.fr.plugin.idm.sso.BaseAction; +import com.fr.plugin.idm.sso.IdmResponse; +import com.fr.plugin.idm.sso.RecordDBAccessProvider; +import com.fr.plugin.idm.sso.dao.OrgDao; +import com.fr.plugin.idm.sso.entity.OrgEntity; +import com.fr.stable.db.action.DBAction; +import com.fr.stable.db.dao.DAOContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.UUID; + +/** + * @Author fr.open + * @Date 2020/8/18 + * @Description + **/ +public class OrgAction extends BaseAction { + + @Override + public IdmResponse handel(HttpServletRequest req, HttpServletResponse res) throws Exception { + OrgEntity orgEntity = getBody(req,OrgEntity.class); + RecordDBAccessProvider.getDbAccessor().runDMLAction(new DBAction() { + @Override + public OrgEntity run(DAOContext daoContext) throws Exception { + orgEntity.setId(UUID.randomUUID().toString()); + daoContext.getDAO(OrgDao.class).add(orgEntity); + return null; + } + }); + return IdmResponse.ok("0"); + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/action/UserAction.java b/src/main/java/com/fr/plugin/idm/sso/action/UserAction.java new file mode 100644 index 0000000..cc6ad71 --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/action/UserAction.java @@ -0,0 +1,83 @@ +package com.fr.plugin.idm.sso.action; + +import com.fr.json.JSONArray; +import com.fr.json.JSONObject; +import com.fr.plugin.db.PluginDBManager; +import com.fr.plugin.idm.sso.BaseAction; +import com.fr.plugin.idm.sso.IdmResponse; +import com.fr.plugin.idm.sso.RecordDBAccessProvider; +import com.fr.plugin.idm.sso.dao.UserDao; +import com.fr.plugin.idm.sso.dao.UserOrgDao; +import com.fr.plugin.idm.sso.entity.OrgEntity; +import com.fr.plugin.idm.sso.entity.UserEntity; +import com.fr.plugin.idm.sso.entity.UserOrgEntity; +import com.fr.stable.db.action.DBAction; +import com.fr.stable.db.dao.DAOContext; +import com.fr.stable.db.session.DBSession; +import com.fr.stable.query.QueryFactory; +import com.fr.stable.query.restriction.RestrictionFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @Author fr.open + * @Date 2020/8/18 + * @Description + **/ +public class UserAction extends BaseAction { + + @Override + public IdmResponse handel(HttpServletRequest req, HttpServletResponse res) throws Exception { + JSONObject object = getBody(req); + JSONArray array = object.getJsonArray("userorgs"); + List list = new ArrayList<>(); + for (int i = 0; i < array.size();i++){ + JSONObject jsonObject = array.getJSONObject(i); + UserOrgEntity userOrgEntity = jsonObject.mapTo(UserOrgEntity.class); + list.add(userOrgEntity); + } + object.remove("userorgs"); + UserEntity user = this.getEntity(object,UserEntity.class); + RecordDBAccessProvider.getDbAccessor().runDMLAction(new DBAction() { + @Override + public OrgEntity run(DAOContext daoContext) throws Exception { + user.setId(UUID.randomUUID().toString()); + UserDao userDao = daoContext.getDAO(UserDao.class); + UserOrgDao userOrgDao = daoContext.getDAO(UserOrgDao.class); + userDao.addOrUpdate(user); + userOrgDao.remove(QueryFactory.create().addRestriction(RestrictionFactory.eq("uid",user.getUid()))); + for (UserOrgEntity userOrgEntity:list) { + userOrgEntity.setId(UUID.randomUUID().toString()); + userOrgDao.add(userOrgEntity); + } + return null; + } + }); + + + return IdmResponse.ok("0"); + } + + public void batchSubmit(List addData) throws Exception {//list里面的就是DBAccessProvider接口里面定义的entity对象 + DBSession session = PluginDBManager.getInstance().getDbContext().openSession(); + try { + session.beginTransaction();//开始事务 + if (addData != null) { + for (Object addO : addData) { + //添加更新的数据 + session.persist(addO); + } + } + session.commitTransaction();//提交 + // + session.closeSession(); + } catch (Exception e) { + session.rollbackTransaction();//回滚 + throw e; + } + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/dao/OrgDao.java b/src/main/java/com/fr/plugin/idm/sso/dao/OrgDao.java new file mode 100644 index 0000000..c44bbb9 --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/dao/OrgDao.java @@ -0,0 +1,21 @@ +package com.fr.plugin.idm.sso.dao; + +import com.fr.plugin.idm.sso.entity.OrgEntity; +import com.fr.stable.db.dao.BaseDAO; +import com.fr.stable.db.session.DAOSession; + +/** + * @Author fr.open + * @Date 2020/8/19 + * @Description + **/ +public class OrgDao extends BaseDAO { + public OrgDao(DAOSession daoSession) { + super(daoSession); + } + + @Override + protected Class getEntityClass() { + return OrgEntity.class; + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/dao/UserDao.java b/src/main/java/com/fr/plugin/idm/sso/dao/UserDao.java new file mode 100644 index 0000000..26f59ae --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/dao/UserDao.java @@ -0,0 +1,22 @@ +package com.fr.plugin.idm.sso.dao; + +import com.fr.plugin.idm.sso.entity.UserEntity; +import com.fr.stable.db.dao.BaseDAO; +import com.fr.stable.db.session.DAOSession; + +/** + * @Author fr.open + * @Date 2020/8/19 + * @Description + **/ +public class UserDao extends BaseDAO { + + public UserDao(DAOSession daoSession) { + super(daoSession); + } + + @Override + protected Class getEntityClass() { + return UserEntity.class; + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/dao/UserOrgDao.java b/src/main/java/com/fr/plugin/idm/sso/dao/UserOrgDao.java new file mode 100644 index 0000000..19e3797 --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/dao/UserOrgDao.java @@ -0,0 +1,21 @@ +package com.fr.plugin.idm.sso.dao; + +import com.fr.plugin.idm.sso.entity.UserOrgEntity; +import com.fr.stable.db.dao.BaseDAO; +import com.fr.stable.db.session.DAOSession; + +/** + * @Author fr.open + * @Date 2020/8/19 + * @Description + **/ +public class UserOrgDao extends BaseDAO { + public UserOrgDao(DAOSession daoSession) { + super(daoSession); + } + + @Override + protected Class getEntityClass() { + return UserOrgEntity.class; + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/entity/OrgEntity.java b/src/main/java/com/fr/plugin/idm/sso/entity/OrgEntity.java new file mode 100644 index 0000000..6433cac --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/entity/OrgEntity.java @@ -0,0 +1,90 @@ +package com.fr.plugin.idm.sso.entity; + +import com.fr.stable.db.entity.BaseEntity; +import com.fr.third.javax.persistence.Column; +import com.fr.third.javax.persistence.Entity; +import com.fr.third.javax.persistence.Table; + +/** + * @Author fr.open + * @Date 2020/8/18 + * @Description + **/ +@Entity +@Table(name = "plugin_sso_org") +public class OrgEntity extends BaseEntity { + + @Column(name = "orgtype") + private int orgtype; + @Column(name = "code") + private String code; + @Column(name = "name") + private String name; + @Column(name = "orderby") + private int orderby; + @Column(name = "oid") + private String oid; + @Column(name = "fullname") + private String fullname; + @Column(name = "poid") + private String poid; + + public OrgEntity() { + } + + public void setOrgtype(int orgtype) { + this.orgtype = orgtype; + } + + public int getOrgtype() { + return orgtype; + } + + public void setCode(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setOrderby(int orderby) { + this.orderby = orderby; + } + + public int getOrderby() { + return orderby; + } + + public void setOid(String oid) { + this.oid = oid; + } + + public String getOid() { + return oid; + } + + public void setFullname(String fullname) { + this.fullname = fullname; + } + + public String getFullname() { + return fullname; + } + + public void setPoid(String poid) { + this.poid = poid; + } + + public String getPoid() { + return poid; + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/entity/UserDTOEntity.java b/src/main/java/com/fr/plugin/idm/sso/entity/UserDTOEntity.java new file mode 100644 index 0000000..f761d37 --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/entity/UserDTOEntity.java @@ -0,0 +1,75 @@ +package com.fr.plugin.idm.sso.entity; + +import com.fr.stable.db.entity.BaseEntity; +import com.fr.third.javax.persistence.Column; +import com.fr.third.javax.persistence.Entity; +import com.fr.third.javax.persistence.Table; + +import java.util.List; + +/** + * @Author fr.open + * @Date 2020/8/18 + * @Description + **/ +public class UserDTOEntity extends BaseEntity { + + private String uid; + private int sex; + private List UserOrgs; + private String loginname; + private String mobilephone; + private String username; + + public UserDTOEntity() { + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getUid() { + return uid; + } + + public void setSex(int sex) { + this.sex = sex; + } + + public int getSex() { + return sex; + } + + public void setUserOrgs(List UserOrgs) { + this.UserOrgs = UserOrgs; + } + + public List getUserOrgs() { + return UserOrgs; + } + + public void setLoginname(String loginname) { + this.loginname = loginname; + } + + public String getLoginname() { + return loginname; + } + + public void setMobilephone(String mobilephone) { + this.mobilephone = mobilephone; + } + + public String getMobilephone() { + return mobilephone; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + +} diff --git a/src/main/java/com/fr/plugin/idm/sso/entity/UserEntity.java b/src/main/java/com/fr/plugin/idm/sso/entity/UserEntity.java new file mode 100644 index 0000000..e5d41a6 --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/entity/UserEntity.java @@ -0,0 +1,73 @@ +package com.fr.plugin.idm.sso.entity; + +import com.fr.stable.db.entity.BaseEntity; +import com.fr.third.javax.persistence.Column; +import com.fr.third.javax.persistence.Entity; +import com.fr.third.javax.persistence.Table; + +import java.util.List; + +/** + * @Author fr.open + * @Date 2020/8/18 + * @Description + **/ +@Entity +@Table(name = "plugin_sso_user") +public class UserEntity extends BaseEntity { + + @Column(name = "uid") + private String uid; + @Column(name = "sex") + private int sex; + @Column(name = "loginname") + private String loginname; + @Column(name = "mobilephone") + private String mobilephone; + @Column(name = "username") + private String username; + + public UserEntity() { + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getUid() { + return uid; + } + + public void setSex(int sex) { + this.sex = sex; + } + + public int getSex() { + return sex; + } + + public void setLoginname(String loginname) { + this.loginname = loginname; + } + + public String getLoginname() { + return loginname; + } + + public void setMobilephone(String mobilephone) { + this.mobilephone = mobilephone; + } + + public String getMobilephone() { + return mobilephone; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + +} diff --git a/src/main/java/com/fr/plugin/idm/sso/entity/UserOrgEntity.java b/src/main/java/com/fr/plugin/idm/sso/entity/UserOrgEntity.java new file mode 100644 index 0000000..f2f2bd1 --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/entity/UserOrgEntity.java @@ -0,0 +1,69 @@ +package com.fr.plugin.idm.sso.entity; + +import com.fr.stable.db.entity.BaseEntity; +import com.fr.third.javax.persistence.Column; +import com.fr.third.javax.persistence.Entity; +import com.fr.third.javax.persistence.Table; + +/** + * @Author fr.open + * @Date 2020/8/18 + * @Description + **/ +@Entity +@Table(name = "plugin_user_org") +public class UserOrgEntity extends BaseEntity { + @Column(name = "uid") + private String uid; + @Column(name = "positionids") + private String positionids; + @Column(name = "oid") + private String oid; + @Column(name = "orderby") + private int orderby; + @Column(name = "jobids") + private String jobids; + + public UserOrgEntity() { + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getUid() { + return uid; + } + + public void setPositionids(String positionids) { + this.positionids = positionids; + } + + public String getPositionids() { + return positionids; + } + + public void setOid(String oid) { + this.oid = oid; + } + + public String getOid() { + return oid; + } + + public void setOrderby(int orderby) { + this.orderby = orderby; + } + + public int getOrderby() { + return orderby; + } + + public void setJobids(String jobids) { + this.jobids = jobids; + } + + public String getJobids() { + return jobids; + } +} diff --git a/src/main/java/com/fr/plugin/idm/sso/util/CommonUtils.java b/src/main/java/com/fr/plugin/idm/sso/util/CommonUtils.java new file mode 100644 index 0000000..24517cf --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/util/CommonUtils.java @@ -0,0 +1,132 @@ +package com.fr.plugin.idm.sso.util; + +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/27 + */ +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) { + FineLoggerFactory.getLogger().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) { + 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 +"\"登录失败"); + } + } + + public static boolean isMobileDevice(HttpServletRequest request) { + if (WebUtils.getDevice(request).isMobile()) { + FineLoggerFactory.getLogger().info("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])) { + FineLoggerFactory.getLogger().info("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/idm/sso/util/HttpUtil.java b/src/main/java/com/fr/plugin/idm/sso/util/HttpUtil.java new file mode 100644 index 0000000..210138a --- /dev/null +++ b/src/main/java/com/fr/plugin/idm/sso/util/HttpUtil.java @@ -0,0 +1,246 @@ +package com.fr.plugin.idm.sso.util; + +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author fr.open + * @date 2019/4/2 + */ +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 sendPost(String url,Map header, Map param) { + PrintWriter out = null; + BufferedReader in = null; + String result = StringUtils.EMPTY; + String res = StringUtils.EMPTY; + try { + String urlNameString = url; + /*if (param != null && !param.isEmpty()) { + urlNameString += "?"; + urlNameString += param.entrySet() + .stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining("&")); + }*/ + + 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)"); + //conn.setRequestProperty("Content-Type","application/json;;charset=UTF-8"); + conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=----footfoodapplicationrequestnetwork"); + 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(); + param.forEach((k,v)->{ + buffer.append("------footfoodapplicationrequestnetwork\r\n"); + buffer.append("Content-Disposition: form-data; name=\""); + buffer.append(k); + buffer.append("\"\r\n\r\n"); + buffer.append(v); + buffer.append("\r\n"); + }); + buffer.append("------footfoodapplicationrequestnetwork--\r\n"); + out.print(buffer.toString()); + /*// 发送请求参数 + 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; + } + } +} diff --git a/src/main/resources/conf.properties b/src/main/resources/conf.properties new file mode 100644 index 0000000..dba9a45 --- /dev/null +++ b/src/main/resources/conf.properties @@ -0,0 +1,17 @@ +##\u8BF7\u6C42\u7528\u6237\u4FE1\u606F\u7684\u63A5\u53E3 +userinfourl= +##\u6821\u9A8Ctoken\u4FE1\u606F\u7684\u63A5\u53E3 +validationurl= +##\u670D\u52A1\u7F16\u53F7 +servicecode= +##\u670D\u52A1\u79D8\u94A5 +servicepwd= +# \u63A5\u53E3\u6240\u9700\u7684\u53C2\u6570\u503C +api.client_id=xxxx +api.client_secret=xxxx +# \u767B\u9646\u9875\u9762\u83B7\u53D6code\u5730\u5740 +api.authorize=https://xxxx/oauth2/authorize +# \u83B7\u53D6access_token\u63A5\u53E3 +api.get-token=https://xxxx/oauth2/token +# \u83B7\u53D6\u7528\u6237\u4FE1\u606F\u63A5\u53E3 +api.get-user=https://xxxx/oauth2/get_user_info \ No newline at end of file