Browse Source

DEC-8585 LDAPS认证插件

master
Cloud.Liu 5 years ago
parent
commit
b4b2d0baf7
  1. 5
      .gitignore
  2. 8
      LICENSE
  3. 130
      build.xml
  4. 0
      lib/report/.gitkeep
  5. 20
      plugin.xml
  6. 18
      pom.xml
  7. 16
      readme.md
  8. 165
      src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsAuthenticBean.java
  9. 407
      src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassport.java
  10. 27
      src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsPassportProvider.java
  11. 33
      src/main/java/com/fr/plugin/decision/passport/ldaps/LdapsWebResourceProvider.java
  12. 340
      src/main/resources/com/fr/plugin/decision/passport/ldaps/js/plugin.js

5
.gitignore vendored

@ -0,0 +1,5 @@
*.iml
.idea/
lib/report/*.jar
.DS_Store
.classpath

8
LICENSE

@ -0,0 +1,8 @@
MIT License
Copyright (c) <year> <copyright holders>
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.

130
build.xml

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project basedir="." default="jar" name="ldaps">
<!-- JDK路径,根据自己机器上实际位置修改-->
<property name="jdk.home" value="C:\Program Files\Java\jdk1.8.0_211"/>
<property name="libs" value="${basedir}/lib"/>
<property name="publicLibs" value=""/>
<property name="reportLibs" value="${basedir}/../webroot/WEB-INF/lib"/>
<property name="destLoc" value="."/>
<property name="classes" value="classes"/>
<xmlproperty file="${basedir}/plugin.xml"/>
<property name="current-version" value="${plugin.version}"/>
<!-- 插件版本-->
<property name="plugin-version" value="${current-version}"/>
<!-- 插件名字-->
<property name="plugin-name" value="decision-ldaps-passport"/>
<property name="plugin-jar" value="fr-plugin-${plugin-name}-${plugin-version}.jar"/>
<target name="prepare">
<delete dir="${classes}"/>
<delete dir="fr-plugin-${plugin-name}-${plugin-version}"/>
<xmlproperty file="${basedir}/plugin.xml"/>
<delete dir="${destLoc}/${plugin.name}"/>
</target>
<path id="compile.classpath">
<fileset dir="${libs}">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${publicLibs}">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${reportLibs}">
<include name="**/*.jar"/>
</fileset>
</path>
<patternset id="resources4Jar">
<exclude name="**/.settings/**"/>
<exclude name=".classpath"/>
<exclude name=".project"/>
<exclude name="**/*.java"/>
<exclude name="**/*.db"/>
<exclude name="**/*.g"/>
<exclude name="**/package.html"/>
</patternset>
<target name="copy_resources">
<echo message="从${resources_from}拷贝图片,JS,CSS等资源文件"/>
<delete dir="tmp"/>
<copy todir="tmp">
<fileset dir="${resources_from}/src/main/resources">
<patternset refid="resources4Jar"/>
</fileset>
</copy>
<copy todir="${classes}">
<fileset dir="tmp"/>
</copy>
<delete dir="tmp"/>
</target>
<target name="compile_javas">
<echo message="编译${compile_files}下的Java文件"/>
<javac destdir="${classes}" debug="false" optimize="on" source="${source_jdk_version}"
target="${target_jdk_version}"
fork="true" memoryMaximumSize="512m" listfiles="false" srcdir="${basedir}"
executable="${compile_jdk_version}/bin/javac">
<src path="${basedir}/src/main/java"/>
<exclude name="**/.svn/**"/>
<compilerarg line="-encoding UTF8 "/>
<classpath refid="compile.classpath"/>
</javac>
<taskdef name="pretreatment" classname="com.fr.plugin.pack.PluginPretreatmentTask">
<classpath refid="compile.classpath"/>
</taskdef>
<pretreatment baseDir="${basedir}"/>
</target>
<target name="jar_classes">
<echo message="打Jar包:${jar_name}"/>
<delete file="${basedir}/${jar_name}"/>
<jar jarfile="${basedir}/${jar_name}">
<fileset dir="${classes}">
</fileset>
</jar>
</target>
<target name="super_jar" depends="prepare">
<antcall target="copy_resources">
<param name="resources_from" value="${basedir}"/>
</antcall>
<antcall target="compile_javas">
<param name="source_jdk_version" value="1.6"/>
<param name="target_jdk_version" value="1.6"/>
<param name="compile_jdk_version" value="${jdk.home}"/>
<param name="compile_files" value="${basedir}/src"/>
</antcall>
<echo message="compile plugin success!"/>
<antcall target="jar_classes">
<param name="jar_name" value="${plugin-jar}"/>
</antcall>
<delete dir="${classes}"/>
</target>
<target name="jar" depends="super_jar">
<antcall target="zip"/>
</target>
<target name="zip">
<property name="plugin-folder" value="fr-plugin-${plugin-name}-${plugin-version}"/>
<echo message="----------zip files----------"/>
<mkdir dir="${plugin-folder}"/>
<copy todir="${plugin-folder}">
<fileset dir=".">
<include name="${plugin-jar}"/>
<include name="plugin.xml"/>
</fileset>
<fileset dir="${libs}">
<include name="*.jar"/>
<include name="*.dll"/>
</fileset>
</copy>
<zip destfile="${basedir}/${plugin-folder}.zip" basedir=".">
<include name="${plugin-folder}/*.jar"/>
<include name="${plugin-folder}/*.dll"/>
<include name="${plugin-folder}/plugin.xml"/>
</zip>
<move file="${plugin-folder}.zip" todir="${destLoc}/install"/>
</target>
</project>

0
lib/report/.gitkeep

20
plugin.xml

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<plugin>
<id>com.fr.plugin.passport.ldaps</id>
<name><![CDATA[LDAPS认证插件]]></name>
<active>yes</active>
<version>1.1</version>
<env-version>10.0~</env-version>
<jartime>2019-05-20</jartime>
<vendor>Cloud.Liu</vendor>
<description><![CDATA[LDAPS认证]]></description>
<change-notes><![CDATA[
[2019-07-12]若需更换秘钥,请删除WEB-INF/plugins下ldaps.keystore文件并在平台指定新的秘钥路径。<br/>
[2019-07-19]根据需求,使域上没有,平台有的用户走平台认证。<br/>
]]></change-notes>
<extra-decision>
<PassportProvider class="com.fr.plugin.decision.passport.ldaps.LdapsPassportProvider"/>
<WebResourceProvider class="com.fr.plugin.decision.passport.ldaps.LdapsWebResourceProvider"/>
</extra-decision>
<function-recorder class="com.fr.plugin.decision.passport.ldaps.LdapsPassportProvider"/>
</plugin>

18
pom.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.fr.plugin</groupId>
<artifactId>starter</artifactId>
<version>10.0</version>
</parent>
<packaging>jar</packaging>
<artifactId>plugin-decision-ldaps-passport</artifactId>
<build>
<!---如果要更改调试插件,改这里的配置就可以了-->
<outputDirectory>${project.basedir}/../webroot/WEB-INF/plugins/plugin-decision-ldaps-passport-1.0/classes</outputDirectory>
</build>
</project>

16
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域上不存在,走平台默认认证。

165
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<LdapsPassport> {
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<LdapsPassport> 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;
}
}

407
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<String> threadLocal = new ThreadLocal<String>();
/**
* 缓存上次匹配的结果字段
*/
private String lastTimeMatchWord = StringUtils.EMPTY;
@Identifier("ldapsUrl")
private Conf<String> ldapUrl = Holders.simple(StringUtils.EMPTY);
@Identifier("ldapsSearchBase")
private Conf<String> ldapSearchBase = Holders.simple(StringUtils.EMPTY);
//是否使用检索位置作为baseDN,是的话就加到url中,否则加到search的参数中
@Identifier("ldapsRetrieveLocAsBaseDN")
private Conf<Boolean> retrieveLocAsBaseDN = Holders.simple(true);
//认证方式
@Identifier("ldapsAuthentication")
private Conf<String> authentication = Holders.simple(AUTH_SIMPLE);// none,simple,strong
//context初始化的Factory,一般用这个默认值
@Identifier("ldapsContextFactory")
private Conf<String> contextFactory = Holders.simple(SUN_DEFAULT_CONTEXT_FACTORY);
//指定将如何处理服务提供者遇到的引用的环境属性名称
@Identifier("ldapsReferral")
private Conf<String> referral = Holders.simple(REFERRAL_FOLLOW);// follow, ignore, throw
//ldap用户后缀,如@finereport.com
@Identifier("ldapsPprincipalSuffix")
private Conf<String> principalSuffix = Holders.simple(StringUtils.EMPTY);
//ldap系统的管理员姓名
@Identifier("ldapsSystemName")
private Conf<String> ldapSystemName = Holders.simple(StringUtils.EMPTY);
//ldap系统的管理员密码
@Identifier("ldapsSystemPassword")
private Conf<String> ldapSystemPassword = Holders.simple(StringUtils.EMPTY);
//ldaps证书路径
@Identifier("ldapsCertPath")
private Conf<String> 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<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
@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);
}
}
}

27
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<? extends PassportBean> classForPassportBean() {
return LdapsAuthenticBean.class;
}
@Override
public Class<? extends Passport> classForPassportConfig() {
return LdapsPassport.class;
}
}

33
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;
}
};
}
}

340
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);
Loading…
Cancel
Save