From b4b2d0baf7bc7613f9c4994f9cdd27beb74909d7 Mon Sep 17 00:00:00 2001 From: "Cloud.Liu" Date: Thu, 25 Jul 2019 10:44:22 +0800 Subject: [PATCH 1/3] =?UTF-8?q?DEC-8585=20LDAPS=E8=AE=A4=E8=AF=81=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + LICENSE | 8 + build.xml | 130 ++++++ lib/report/.gitkeep | 0 plugin.xml | 20 + pom.xml | 18 + readme.md | 16 + .../passport/ldaps/LdapsAuthenticBean.java | 165 +++++++ .../passport/ldaps/LdapsPassport.java | 407 ++++++++++++++++++ .../passport/ldaps/LdapsPassportProvider.java | 27 ++ .../ldaps/LdapsWebResourceProvider.java | 33 ++ .../decision/passport/ldaps/js/plugin.js | 340 +++++++++++++++ 12 files changed, 1169 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 build.xml create mode 100644 lib/report/.gitkeep create mode 100644 plugin.xml create mode 100644 pom.xml create mode 100644 src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsAuthenticBean.java create mode 100644 src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassport.java create mode 100644 src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassportProvider.java create mode 100644 src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsWebResourceProvider.java create mode 100644 src/main/resources/com/fr/plugin/decision/passport/ldaps/js/plugin.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d91e065 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.iml +.idea/ +lib/report/*.jar +.DS_Store +.classpath \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..472ac23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +MIT License +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..3ebd058 --- /dev/null +++ b/build.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/report/.gitkeep b/lib/report/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..865bdfa --- /dev/null +++ b/plugin.xml @@ -0,0 +1,20 @@ + + + com.fr.plugin.passport.ldaps + + yes + 1.1 + 10.0~ + 2019-05-20 + Cloud.Liu + + + [2019-07-19]根据需求,使域上没有,平台有的用户走平台认证。
+ ]]>
+ + + + + +
\ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..17a81e0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + + com.fr.plugin + starter + 10.0 + + jar + plugin-decision-ldaps-passport + + + ${project.basedir}/../webroot/WEB-INF/plugins/plugin-decision-ldaps-passport-1.0/classes + + \ No newline at end of file diff --git a/readme.md b/readme.md index e69de29..00ed5ef 100644 --- a/readme.md +++ b/readme.md @@ -0,0 +1,16 @@ +LDAPS认证插件说明 + +1 设置项示例 + 设置项 示例 + URL ldaps://192.168.120.128:636 + 检索位置 ou=users,dc=cloud,dc=local + 是否将检索位置作为baseDN 不勾选 + 认证方式 simple + 前后关系 com.sun.jndi.ldap.LdapCtxFactory + 转诊 follow + LDAPS证书地址 C:\YFCO_BASE64.cer + 用户名后缀 可不填 + 管理员名称 cn=Manager,dc=cloud,dc=local + 管理员密码 上面账号对应的LDAPS域密码 +2 会在WEB-INF/plugins下生成ldaps.keystore,不可删除。若想更换证书,删除该文件,在平台重新配置证书路径,保存。 +3 根据客户需求,如果用户在LDAPS域上不存在,走平台默认认证。 \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsAuthenticBean.java b/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsAuthenticBean.java new file mode 100644 index 0000000..45d3fe8 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsAuthenticBean.java @@ -0,0 +1,165 @@ +package com.fr.plugin.decision.passport.ldaps; + +import com.fr.decision.authorize.Passport; +import com.fr.decision.config.FSConfig; +import com.fr.decision.webservice.bean.authentication.PassportBean; +import com.fr.decision.webservice.utils.DecisionServiceConstants; +import com.fr.decision.webservice.utils.WebServiceUtils; +import com.fr.stable.StringUtils; +import com.fr.third.fasterxml.jackson.annotation.JsonSubTypes; + +/** + * LDAP认证方式 + * Created by zhouping on 2018/3/4. + */ +@JsonSubTypes.Type(value = LdapsAuthenticBean.class, name = "LdapAuthenticBean") +public class LdapsAuthenticBean extends PassportBean { + private static final long serialVersionUID = -5233155996986308766L; + + //认证URL + private String url; + //检索位置 + private String searchBase; + //是否将BaseDN作为检索位置,是的话就加到url中,否则加到search的参数中 + private boolean retrieveLocAsBaseDN; + //LDAP认证方式种类,none,simple,strong + private String authentication; + //context初始化的Factory,一般用这个默认值 + private String contextFactory; + //指定将如何处理服务提供者遇到的引用的环境属性名称,follow, ignore, throw + private String referral; + //ldap用户后缀,如@finereport.com + private String principalSuffix; + //ldap系统的管理员姓名 + private String systemName; + //ldap系统的管理员密码 + private String systemPassword; + //ldaps证书路径 + private String certPath; + + public LdapsAuthenticBean() { + } + + public String getAuthentication() { + return authentication; + } + + public void setAuthentication(String authentication) { + this.authentication = authentication; + } + + public String getContextFactory() { + return contextFactory; + } + + public void setContextFactory(String contextFactory) { + this.contextFactory = contextFactory; + } + + public String getPrincipalSuffix() { + return principalSuffix; + } + + public void setPrincipalSuffix(String principalSuffix) { + this.principalSuffix = principalSuffix; + } + + public String getReferral() { + return referral; + } + + public void setReferral(String referral) { + this.referral = referral; + } + + public String getSearchBase() { + return searchBase; + } + + public void setSearchBase(String searchBase) { + this.searchBase = searchBase; + } + + public String getSystemPassword() { + return systemPassword; + } + + public void setSystemPassword(String systemPassword) { + this.systemPassword = systemPassword; + } + + public String getSystemName() { + return systemName; + } + + public void setSystemName(String systemName) { + this.systemName = systemName; + } + + public boolean isRetrieveLocAsBaseDN() { + return retrieveLocAsBaseDN; + } + + public void setRetrieveLocAsBaseDN(boolean retrieveLocAsBaseDN) { + this.retrieveLocAsBaseDN = retrieveLocAsBaseDN; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getCertPath() { + return certPath; + } + + public void setCertPath(String certPath) { + this.certPath = certPath; + } + + @Override + public String markType() { + return LdapsPassportProvider.PASSPORT_TYPE; + } + + @Override + public PassportBean createPassportBean(LdapsPassport passport) { + this.setUrl(passport.getLdapUrl()); + this.setSearchBase(passport.getLdapSearchBase()); + this.setRetrieveLocAsBaseDN(passport.isRetrieveLocAsBaseDN()); + this.setAuthentication(passport.getAuthentication()); + this.setContextFactory(passport.getContextFactory()); + this.setReferral(passport.getReferral()); + this.setPrincipalSuffix(passport.getPrincipalSuffix()); + this.setSystemName(passport.getLdapSystemName()); + if (StringUtils.isNotEmpty(passport.getLdapSystemPassword())) { + this.setSystemPassword(DecisionServiceConstants.DEFAULT_PASSWORD); + } + this.setCertPath(passport.getCertPath()); + return this; + } + + @Override + public Passport createPassport() { + LdapsPassport passport = new LdapsPassport(); + passport.setLdapUrl(getUrl()); + passport.setLdapSearchBase(getSearchBase()); + passport.setRetrieveLocAsBaseDN(isRetrieveLocAsBaseDN()); + passport.setAuthentication(getAuthentication()); + passport.setContextFactory(getContextFactory()); + passport.setReferral(getReferral()); + passport.setPrincipalSuffix(getPrincipalSuffix()); + passport.setLdapSystemName(getSystemName()); + if (WebServiceUtils.isDefaultPassword(this.systemPassword)) { + String oldPassword = FSConfig.getInstance().getPassport(LdapsPassport.class).getLdapSystemPassword(); + passport.setLdapSystemPassword(oldPassword); + } else { + passport.setLdapSystemPassword(WebServiceUtils.getBase64DecodeStr(this.systemPassword)); + } + passport.setCertPath(getCertPath()); + return passport; + } +} diff --git a/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassport.java b/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassport.java new file mode 100644 index 0000000..3e6764d --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassport.java @@ -0,0 +1,407 @@ +package com.fr.plugin.decision.passport.ldaps; + +import com.fr.config.Identifier; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; +import com.fr.decision.authority.data.User; +import com.fr.decision.authorize.Passport; +import com.fr.decision.authorize.impl.AbstractPassport; +import com.fr.decision.authorize.impl.DefaultPassport; +import com.fr.decision.privilege.encrpt.PasswordValidator; +import com.fr.decision.webservice.utils.UserSourceFactory; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.general.ComparatorUtils; +import com.fr.io.utils.ResourceIOUtils; +import com.fr.log.FineLoggerFactory; +import com.fr.security.SecurityToolbox; +import com.fr.stable.AssistUtils; +import com.fr.stable.StringUtils; +import com.fr.third.javax.annotation.Nullable; + +import javax.naming.AuthenticationException; +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.Control; +import javax.naming.ldap.InitialLdapContext; +import javax.naming.ldap.LdapContext; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.util.Properties; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + + +public class LdapsPassport extends AbstractPassport { + private static final long serialVersionUID = -1848712022102261769L; + + + private static final String AUTH_SIMPLE = "simple"; + + private static final String REFERRAL_FOLLOW = "follow"; + + private static final String SUN_DEFAULT_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; + + private Control[] connCtls = null; + + private ThreadLocal threadLocal = new ThreadLocal(); + + /** + * 缓存上次匹配的结果字段 + */ + private String lastTimeMatchWord = StringUtils.EMPTY; + + + @Identifier("ldapsUrl") + private Conf ldapUrl = Holders.simple(StringUtils.EMPTY); + + @Identifier("ldapsSearchBase") + private Conf ldapSearchBase = Holders.simple(StringUtils.EMPTY); + + //是否使用检索位置作为baseDN,是的话就加到url中,否则加到search的参数中 + @Identifier("ldapsRetrieveLocAsBaseDN") + private Conf retrieveLocAsBaseDN = Holders.simple(true); + //认证方式 + @Identifier("ldapsAuthentication") + private Conf authentication = Holders.simple(AUTH_SIMPLE);// none,simple,strong + //context初始化的Factory,一般用这个默认值 + @Identifier("ldapsContextFactory") + private Conf contextFactory = Holders.simple(SUN_DEFAULT_CONTEXT_FACTORY); + //指定将如何处理服务提供者遇到的引用的环境属性名称 + @Identifier("ldapsReferral") + private Conf referral = Holders.simple(REFERRAL_FOLLOW);// follow, ignore, throw + //ldap用户后缀,如@finereport.com + @Identifier("ldapsPprincipalSuffix") + private Conf principalSuffix = Holders.simple(StringUtils.EMPTY); + //ldap系统的管理员姓名 + @Identifier("ldapsSystemName") + private Conf ldapSystemName = Holders.simple(StringUtils.EMPTY); + //ldap系统的管理员密码 + @Identifier("ldapsSystemPassword") + private Conf ldapSystemPassword = Holders.simple(StringUtils.EMPTY); + //ldaps证书路径 + @Identifier("ldapsCertPath") + private Conf certPath = Holders.simple(StringUtils.EMPTY); + + + public LdapsPassport() { + + } + + public String getLdapUrl() { + return ldapUrl.get(); + } + + public void setLdapUrl(String ldapUrl) { + this.ldapUrl.set(ldapUrl); + } + + public String getLdapSearchBase() { + return ldapSearchBase.get(); + } + + public void setLdapSearchBase(String ldapSearchBase) { + this.ldapSearchBase.set(ldapSearchBase); + } + + public String getAuthentication() { + return authentication.get(); + } + + public void setAuthentication(String authentication) { + this.authentication.set(authentication); + } + + public String getContextFactory() { + return contextFactory.get(); + } + + public void setContextFactory(String contextFactory) { + this.contextFactory.set(contextFactory); + } + + public String getReferral() { + return referral.get(); + } + + public void setReferral(String referral) { + this.referral.set(referral); + } + + public String getPrincipalSuffix() { + return principalSuffix.get(); + } + + public void setPrincipalSuffix(String principalSuffix) { + this.principalSuffix.set(principalSuffix); + } + + public String getLdapSystemName() { + return ldapSystemName.get(); + } + + public void setLdapSystemName(String ldapSystemName) { + this.ldapSystemName.set(ldapSystemName); + } + + public String getLdapSystemPassword() { + return StringUtils.isEmpty(ldapSystemPassword.get()) ? null : SecurityToolbox.decrypt(ldapSystemPassword.get()); + } + + public void setLdapSystemPassword(String ldapSystemPassword) { + this.ldapSystemPassword.set(SecurityToolbox.encrypt(ldapSystemPassword)); + } + + public boolean isRetrieveLocAsBaseDN() { + return retrieveLocAsBaseDN.get(); + } + + public void setRetrieveLocAsBaseDN(boolean retrieveLocAsBaseDN) { + this.retrieveLocAsBaseDN.set(retrieveLocAsBaseDN); + } + + public String getCertPath() { + return certPath.get(); + } + + public void setCertPath(String certPath) { + this.certPath.set(certPath); + } + + @Override + public String markType() { + return LdapsPassportProvider.PASSPORT_TYPE; + } + + @Override + public Object clone() throws CloneNotSupportedException { + LdapsPassport cloned = (LdapsPassport) super.clone(); + return cloned; + } + + @Override + public int hashCode() { + return AssistUtils.hashCode(ldapUrl.get(), ldapSearchBase.get(), ldapSystemName.get(), ldapSystemPassword.get(), + referral.get(), retrieveLocAsBaseDN.get(), authentication.get(), contextFactory.get(), certPath.get()); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof LdapsPassport)) { + return false; + } + LdapsPassport target = (LdapsPassport) obj; + return ComparatorUtils.equals(target.getPrincipalSuffix(), this.getPrincipalSuffix()) + && ComparatorUtils.equals(target.getReferral(), this.getReferral()) + && ComparatorUtils.equals(target.getLdapUrl(), this.getLdapUrl()) + && ComparatorUtils.equals(target.getContextFactory(), this.getContextFactory()) + && ComparatorUtils.equals(target.getAuthentication(), this.getAuthentication()) + && ComparatorUtils.equals(target.getLdapSystemName(), this.getLdapSystemName()) + && ComparatorUtils.equals(target.getLdapSearchBase(), this.getLdapSearchBase()) + && ComparatorUtils.equals(target.getLdapSystemPassword(), this.getLdapSystemPassword()) + && ComparatorUtils.equals(target.getCertPath(), this.getCertPath()); + } + @Override + public boolean checkTicket(String username, String inputPassword, String savedPassword, String hashPassword) { + if (StringUtils.isNotEmpty(principalSuffix.get())) { + username += principalSuffix.get(); + } + return connect(username, inputPassword); + } + + public boolean connect(final String username, final String password) { + FutureTask futureTask = new FutureTask(new Callable() { + @Override + public Boolean call() { + LdapContext ctx = null; + boolean isValid; + try { + // 封装到线程中 + threadLocal.set(lastTimeMatchWord); + //初次连接,无管理员账号时是匿名登录 + ctx = connectLdap(); + //再次连接 + isValid = authenticate(ctx, username, password); + } catch (Exception e) { + return false; + } finally { + closeContext(ctx); + threadLocal.remove(); + } + return isValid; + } + }); + futureTask.run(); + try { + return futureTask.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + return false; + } + return false; + } + + @Nullable + private LdapContext connectLdap() { + Properties props = new Properties(); + props.put(Context.INITIAL_CONTEXT_FACTORY, getContextFactory()); + String url = getLdapUrl(); + if (StringUtils.isNotBlank(url)) { + if (!url.endsWith("/")) { + url += "/"; + } + } + if (isRetrieveLocAsBaseDN()) { + //检索位置作为BaseDN,那url中加上检索位置,search中则不用检索位置作为BaseDN + url += getLdapSearchBase(); + } + props.put(Context.PROVIDER_URL, url); + props.put(Context.SECURITY_AUTHENTICATION, getAuthentication()); + props.put(Context.REFERRAL, getReferral()); + //-----------------LDAPS-------------------- + try { + InputStream certStream = new FileInputStream(certPath.get()); + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Certificate certificate = certificateFactory.generateCertificate(certStream); + KeyStore ks = KeyStore.getInstance("jks"); + ks.load(null, null); + ks.setCertificateEntry("LDAP_ALIAS", certificate); + + if (!ResourceIOUtils.exist("/plugins/ldaps.keystore")) { + ResourceIOUtils.createFile("/plugins/ldaps.keystore"); + File keyStoreFile = new File(ResourceIOUtils.getRealPath("/plugins/ldaps.keystore")); + FileOutputStream fos = new FileOutputStream(keyStoreFile); + ks.store(fos, "LdapsKeyStorePwd".toCharArray()); + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error("LDAPS: Exception during reading key file and writing keyStore" + e.toString()); + } + System.setProperty("javax.net.ssl.trustStore", ResourceIOUtils.getRealPath("/plugins/ldaps.keystore")); + System.setProperty("javax.net.ssl.keyStorePassword", "LdapsKeyStorePwd"); + //java 1.8_181的安全性改动,开启了对于LDAP的端点标识,导致证书里的CN和ldaps服务器域名对不上就抛错 + System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true"); + //-----------------LDAPS-------------------- + //有管理员账号时,是显示连接 + if (StringUtils.isNotBlank(getLdapSystemName()) + && StringUtils.isNotBlank(getLdapSystemPassword())) { + props.put(Context.SECURITY_PRINCIPAL, getLdapSystemName()); + props.put(Context.SECURITY_CREDENTIALS, getLdapSystemPassword()); + } + try { + return new InitialLdapContext(props, connCtls); + } catch (javax.naming.AuthenticationException e) { + FineLoggerFactory.getLogger().error("LDAPS: AuthenticationException,Authentication failed: " + e.toString()); + } catch (Exception e) { + FineLoggerFactory.getLogger().error("LDAPS: Exception,Something wrong while authenticating: " + e.toString()); + } + return null; + } + + private boolean authenticate(LdapContext ctx, String username, String password) { + boolean isValid = true; + String userDN = recurseGetUserDN(ctx, username); + if (StringUtils.isEmpty(userDN)) { + //未正确获取到DN,代表域上没有这个用户而平台有,客户需求:转而用平台登录 + FineLoggerFactory.getLogger().debug("LDAPS: No such user on ldap server, authenticating with platform passport"); + try { + User user = UserService.getInstance().getUserByUserName(username); + if (user == null) { + return false; + } + PasswordValidator passwordValidator = UserSourceFactory.getInstance().getUserSource(user).getPasswordValidator(); + String hashPassword = passwordValidator.encode(user.getUserName(), password); + Passport passport = new DefaultPassport(); + return passport.checkTicket(user.getUserName(), password, user.getPassword(), hashPassword); + } catch (Exception e) { + FineLoggerFactory.getLogger().error("LDAPS: Exception during logging with platform default passport"); + return false; + } + } + try { + ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, userDN); + ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password); + ctx.reconnect(connCtls); + } catch (AuthenticationException e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + isValid = false; + } catch (NamingException e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + isValid = false; + } + return isValid; + } + + private String recurseGetUserDN(LdapContext ctx, String name) { + String lastTimeMatchWordShadow = threadLocal.get(); + // 前一次匹配成功的字段作为优先匹配的字段 + if (StringUtils.isNotEmpty(lastTimeMatchWordShadow)) { + FineLoggerFactory.getLogger().debug("LDAPS: Using cached word " + lastTimeMatchWordShadow); + String userDN = getUserDN(ctx, name, lastTimeMatchWordShadow); + if (StringUtils.isNotBlank(userDN)) { + return userDN; + } + } + String[] fWords = {"sAMAccountName", "cn", "userPrincipalName", "uid", "displayName", "name", "sn",}; + for (String fWord : fWords) { + if (StringUtils.isNotEmpty(lastTimeMatchWordShadow) && ComparatorUtils.equals(lastTimeMatchWordShadow, fWord)) { + continue; + } + String userDN = getUserDN(ctx, name, fWord); + if (StringUtils.isNotBlank(userDN)) { + // 缓存匹配字段 + FineLoggerFactory.getLogger().debug("LDAPS: Cached word updated to " + fWord); + lastTimeMatchWord = fWord; + return userDN; + } + } + return StringUtils.EMPTY; + } + + private String getUserDN(LdapContext ctx, String name, String word) { + String userDN = StringUtils.EMPTY; + try { + SearchControls constraints = new SearchControls(); + constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); + String filter = "(&(" + word + "=" + name + "))"; + String baseDN = StringUtils.EMPTY; + if (!isRetrieveLocAsBaseDN()) { + //检索位置不作为BaseDN,那url中不加上检索位置,search中则用检索位置作为BaseDN + baseDN = getLdapSearchBase(); + } + NamingEnumeration en = ctx.search(baseDN, filter, constraints); + while (en != null && en.hasMoreElements()) { + if (en.hasMoreElements()) { + SearchResult sr = (SearchResult) en.nextElement(); + userDN = sr.getNameInNamespace(); + if (StringUtils.isNotBlank(userDN) && userDN.contains(name)) { + break; + } + } + } + } catch (Exception e) { + FineLoggerFactory.getLogger().error("LDAPS: error during getUserDN ", e); + } + FineLoggerFactory.getLogger().debug("LDAPS: Using word " + word + " name "+ name +" and found userDN " + userDN); + return userDN; + } + + private void closeContext(LdapContext ctx) { + try { + if (ctx != null) { + ctx.close(); + } + } catch (NamingException e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + +} diff --git a/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassportProvider.java b/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassportProvider.java new file mode 100644 index 0000000..3e94478 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassportProvider.java @@ -0,0 +1,27 @@ +package com.fr.plugin.decision.passport.ldaps; + +import com.fr.decision.authorize.Passport; +import com.fr.decision.fun.impl.AbstractPassportProvider; +import com.fr.decision.webservice.bean.authentication.PassportBean; +import com.fr.plugin.transform.FunctionRecorder; + + +@FunctionRecorder +public class LdapsPassportProvider extends AbstractPassportProvider { + public static final String PASSPORT_TYPE = "Ldaps"; + + @Override + public String passportType() { + return PASSPORT_TYPE; + } + + @Override + public Class classForPassportBean() { + return LdapsAuthenticBean.class; + } + + @Override + public Class classForPassportConfig() { + return LdapsPassport.class; + } +} diff --git a/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsWebResourceProvider.java b/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsWebResourceProvider.java new file mode 100644 index 0000000..9b8a772 --- /dev/null +++ b/src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsWebResourceProvider.java @@ -0,0 +1,33 @@ +package com.fr.plugin.decision.passport.ldaps; + +import com.fr.decision.fun.impl.AbstractWebResourceProvider; +import com.fr.decision.web.MainComponent; +import com.fr.web.struct.Atom; +import com.fr.web.struct.Component; +import com.fr.web.struct.browser.RequestClient; +import com.fr.web.struct.category.ScriptPath; +import com.fr.web.struct.category.StylePath; + +/** + * Created by zhouping on 2019/1/16. + */ +public class LdapsWebResourceProvider extends AbstractWebResourceProvider { + @Override + public Atom attach() { + return MainComponent.KEY; + } + + @Override + public Atom client() { + return new Component() { + @Override + public ScriptPath script(RequestClient requestClient) { + return ScriptPath.build("/com/fr/plugin/decision/passport/ldaps/js/plugin.js"); + } + @Override + public StylePath style(RequestClient requestClient) { + return StylePath.EMPTY; + } + }; + } +} diff --git a/src/main/resources/com/fr/plugin/decision/passport/ldaps/js/plugin.js b/src/main/resources/com/fr/plugin/decision/passport/ldaps/js/plugin.js new file mode 100644 index 0000000..4c6c036 --- /dev/null +++ b/src/main/resources/com/fr/plugin/decision/passport/ldaps/js/plugin.js @@ -0,0 +1,340 @@ +BI.config("dec.user.setting.authentications", function (items) { + items.push({ + value: "Ldaps", + text: "LDAPS认证", + "@class": "com.fr.plugin.decision.passport.ldaps.LdapsAuthenticBean", + component: { + type: "dec.plugin.custom_authentication" + } + }); + return items; +}); + +var WIDTH = 125; + +var Ldaps = BI.inherit(BI.Widget, { + + props: { + baseCls: "", + configs: { + url:"", + searchBase:"", + retrieveLocAsBaseDN:"false", + authentication:"simple", + contextFactory:"com.sun.jndi.ldap.LdapCtxFactory", + referral:"follow", + certPath:"", + principalSuffix:"", + systemName:"", + systemPassword:"" + } + }, + + render: function () { + var self = this, o = this.options; + return { + type: "bi.vertical", + bgap: 15, + items: [ + { + type: "bi.vertical_adapt", + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "URL", + width: 120 + }, { + type: "bi.editor", + ref: function (_ref) { + self.url = _ref; + }, + width: 400, + height: 24, + watermark: "ldaps://192.168.255.26:636", + value: o.configs.url + } + ] + }, { + type: "bi.vertical_adapt", + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "检索位置", + width: 120 + }, { + type: "bi.editor", + ref: function (_ref) { + self.searchBase = _ref; + }, + width: 400, + height: 24, + watermark: "DC=finereport", + value: o.configs.searchBase + } + ] + }, { + type: "bi.vertical_adapt", + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + width: 120 + }, { + type: "bi.multi_select_item", + text: "不将检索位置作为baseDN", + ref: function (_ref) { + self.retrieveLocAsBaseDN = _ref; + }, + width: 400, + height: 24, + value: o.configs.retrieveLocAsBaseDN + } + ] + }, { + type: "bi.htape", + height: 24, + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.vertical", + items: [ + { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "认证方式" + } + ], + width: 120 + }, { + type: "bi.text_value_combo", + ref: function (_ref) { + self.authentication = _ref; + }, + items: [ + { + text: "simple", + value: "simple" + }, { + text: "strong", + value: "strong" + }, { + text: "none", + value: "none" + } + ], + width: 400, + value: o.configs.authentication + } + ] + }, { + type: "bi.htape", + height: 24, + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.vertical", + items: [ + { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "前后关系" + } + ], + width: 120 + }, { + type: "bi.text_value_combo", + ref: function (_ref) { + self.contextFactory = _ref; + }, + items: [ + { + text: "com.sun.jndi.ldap.LdapCtxFactory", + value: "com.sun.jndi.ldap.LdapCtxFactory" + }, { + text: "com.sun.jndi.ldap.connect.pool", + value: "com.sun.jndi.ldap.connect.pool" + } + ], + width: 400, + value: o.configs.contextFactory + } + ] + }, { + type: "bi.htape", + height: 24, + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.vertical", + items: [ + { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "转诊" + } + ], + width: 120 + }, { + type: "bi.text_value_combo", + ref: function (_ref) { + self.referral = _ref; + }, + items: [ + { + text: "follow", + value: "follow" + }, { + text: "ignore", + value: "ignore" + }, { + text: "throw", + value: "throw" + } + ], + width: 400, + value: o.configs.referral + } + ] + }, { + type: "bi.vertical_adapt", + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "LDAPS证书地址", + width: 120 + }, { + type: "bi.editor", + ref: function (_ref) { + self.certPath = _ref; + }, + width: 400, + height: 24, + watermark: "C:/ldaps.cert", + value: o.configs.certPath + } + ] + }, { + type: "bi.vertical_adapt", + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "用户名后缀", + width: 120 + }, { + type: "bi.editor", + ref: function (_ref) { + self.principalSuffix = _ref; + }, + allowBlank:true, + width: 400, + height: 24, + watermark: "@fanruan.com", + value: o.configs.principalSuffix + } + ] + }, { + type: "bi.vertical_adapt", + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "管理员名称", + width: 120 + }, { + type: "bi.editor", + ref: function (_ref) { + self.systemName = _ref; + }, + width: 400, + height: 24, + value: o.configs.systemName + } + ] + }, { + type: "bi.vertical_adapt", + items: [ + { + type: "bi.layout", + width: WIDTH + }, { + type: "bi.label", + textAlign: "left", + cls: "dec-font-weight-bold", + text: "管理员密码", + width: 120 + }, { + type: "bi.editor", + inputType: "password", + ref: function (_ref) { + self.systemPassword = _ref; + }, + width: 400, + height: 24, + value: o.configs.systemPassword + } + ] + } + ] + + }; + }, + + getValue: function () { + var pass; + if (this.systemPassword.getValue() === '********') { + pass = '********'; + } else { + pass = BI.encode(this.systemPassword.getValue()); + } + return { + url: this.url.getValue(), + searchBase: this.searchBase.getValue(), + retrieveLocAsBaseDN: this.retrieveLocAsBaseDN.isSelected(), + authentication: this.authentication.getValue()[0], + contextFactory: this.contextFactory.getValue()[0], + referral: this.referral.getValue()[0], + certPath: this.certPath.getValue(), + principalSuffix: this.principalSuffix.getValue(), + systemName: this.systemName.getValue(), + systemPassword: pass + }; + } +}); +BI.shortcut("dec.plugin.custom_authentication", Ldaps); \ No newline at end of file From 9095ee7d9e341498cceffde72cdaa77e10d582e9 Mon Sep 17 00:00:00 2001 From: "Cloud.Liu" Date: Thu, 25 Jul 2019 10:57:57 +0800 Subject: [PATCH 2/3] =?UTF-8?q?DEC-8585=20readme.md=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/readme.md b/readme.md index 00ed5ef..49f9968 100644 --- a/readme.md +++ b/readme.md @@ -1,16 +1,18 @@ -LDAPS认证插件说明 +######LDAPS认证插件说明 -1 设置项示例 - 设置项 示例 - URL ldaps://192.168.120.128:636 - 检索位置 ou=users,dc=cloud,dc=local - 是否将检索位置作为baseDN 不勾选 - 认证方式 simple - 前后关系 com.sun.jndi.ldap.LdapCtxFactory - 转诊 follow - LDAPS证书地址 C:\YFCO_BASE64.cer - 用户名后缀 可不填 - 管理员名称 cn=Manager,dc=cloud,dc=local - 管理员密码 上面账号对应的LDAPS域密码 -2 会在WEB-INF/plugins下生成ldaps.keystore,不可删除。若想更换证书,删除该文件,在平台重新配置证书路径,保存。 -3 根据客户需求,如果用户在LDAPS域上不存在,走平台默认认证。 \ No newline at end of file +设置项 | 示例 +----- | ----- +URL | ldaps://192.168.120.128:636 +检索位置 | ou=users,dc=cloud,dc=local +是否将检索位置作为baseDN | 不勾选 +认证方式 | simple +前后关系 | com.sun.jndi.ldap.LdapCtxFactory +转诊 | follow +LDAPS证书地址 | C:\abc.cer +用户名后缀 | 可不填 +管理员名称 | cn=Manager,dc=cloud,dc=local +管理员密码 | 上面账号对应的LDAPS域密码 + +1. 会在WEB-INF/plugins下生成ldaps.keystore,不可删除。若想更换证书,删除该文件,在平台重新配置证书路径,保存。 + +2. 根据客户需求,如果用户在LDAPS域上不存在,走平台默认认证。 \ No newline at end of file From 94a34415d6a81f020f99fbebeaadb6480bd22911 Mon Sep 17 00:00:00 2001 From: "Cloud.Liu" Date: Thu, 25 Jul 2019 10:59:37 +0800 Subject: [PATCH 3/3] =?UTF-8?q?DEC-8585=20readme.md=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 49f9968..1aaa796 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -######LDAPS认证插件说明 +LDAPS认证插件说明 设置项 | 示例 ----- | -----