1.下载LDAP server 并安装, liferay支持如下的server,推荐使用apacheds,或openLDAP
2.下载一个LDAP客户端工具,官方网站都有提供 ,推荐使用 jxplorer,测试能否正常连接。
3. Enterprise Admin->setting->authentication->LDAP,Enable使用LDAP验证,Required是否必须通过LDAP验证,如果不勾选通过liferay验证即可。
/** * LDAP验证 * @param companyId * @param emailAddress * @param screenName * @param userId * @param password * @return * @throws Exception */ protected int authenticate( long companyId, String emailAddress, String screenName, long userId, String password) throws Exception { //是否需要LDAP验证 if (!PortalLDAPUtil.isAuthEnabled(companyId)) { if (_log.isDebugEnabled()) { _log.debug("Authenticator is not enabled"); } return SUCCESS; } if (_log.isDebugEnabled()) { _log.debug("Authenticator is enabled"); } // Make exceptions for omniadmins so that if they break the LDAP // configuration, they can still login to fix the problem //如果此用户是管理员的角色就返回成功 if (authenticateOmniadmin(companyId, emailAddress, userId) == SUCCESS) { return SUCCESS; } //相对DN String baseDN = PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_BASE_DN); LdapContext ctx = PortalLDAPUtil.getContext(companyId); if (ctx == null) { //查看LDAP验证是否是必须的 return authenticateRequired( companyId, userId, emailAddress, FAILURE); } // Process LDAP auth search filter String filter = PortalLDAPUtil.getAuthSearchFilter( companyId, emailAddress, screenName, String.valueOf(userId)); try { SearchControls cons = new SearchControls( SearchControls.SUBTREE_SCOPE, 1, 0, null, false, false); NamingEnumeration<SearchResult> enu = ctx.search( baseDN, filter, cons); if (enu.hasMore()) { if (_log.isDebugEnabled()) { _log.debug("Search filter returned at least one result"); } SearchResult result = enu.next(); String fullUserDN = PortalLDAPUtil.getNameInNamespace( companyId, result); Attributes attrs = PortalLDAPUtil.getAttributes( ctx, fullUserDN); LDAPAuthResult ldapAuthResult = authenticate( ctx, companyId, attrs, fullUserDN, password); // Process LDAP failure codes String errorMessage = ldapAuthResult.getErrorMessage(); if (errorMessage != null) { if (errorMessage.indexOf(PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_ERROR_USER_LOCKOUT)) != -1) { throw new UserLockoutException(); } else if (errorMessage.indexOf(PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_ERROR_PASSWORD_EXPIRED)) != -1) { throw new PasswordExpiredException(); } } if (!ldapAuthResult.isAuthenticated()) { //查看LDAP验证是否是必须的 return authenticateRequired( companyId, userId, emailAddress, FAILURE); } // Get user or create from LDAP User user = PortalLDAPUtil.importLDAPUser( companyId, ctx, attrs, password, true); // Process LDAP success codes String resultCode = ldapAuthResult.getResponseControl(); if (resultCode.equals(LDAPAuth.RESULT_PASSWORD_RESET)) { UserLocalServiceUtil.updatePasswordReset( user.getUserId(), true); } else if ( resultCode.equals(LDAPAuth.RESULT_PASSWORD_EXP_WARNING)) { UserLocalServiceUtil.updatePasswordReset( user.getUserId(), true); } } else { if (_log.isDebugEnabled()) { _log.debug("Search filter did not return any results"); } //查看LDAP验证是否是必须的 return authenticateRequired( companyId, userId, emailAddress, DNE); } } catch (Exception e) { _log.error("Problem accessing LDAP server: " + e.getMessage()); //查看LDAP验证是否是必须的 if (authenticateRequired( companyId, userId, emailAddress, FAILURE) == FAILURE) { throw e; } } finally { ctx.close(); } return SUCCESS; }
4.Default Values选择你使用的DS服务 。
5.测试连接能否成功。这些参数要根据自已的服务来填写。
测试成功
/** * 获得连接上下文 * @param companyId * @return * @throws Exception */ public static LdapContext getContext(long companyId) throws Exception { //连接地址(优先取数据库中) String baseProviderURL = PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_BASE_PROVIDER_URL); //用户地址(优先取数据库中) String pricipal = PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_SECURITY_PRINCIPAL); //连接密码(优先取数据库中) String credentials = PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_SECURITY_CREDENTIALS); return getContext(companyId, baseProviderURL, pricipal, credentials); } public static LdapContext getContext( long companyId, String providerURL, String pricipal, String credentials) throws Exception { Properties env = new Properties(); env.put( Context.INITIAL_CONTEXT_FACTORY, PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_FACTORY_INITIAL)); env.put(Context.PROVIDER_URL, providerURL); env.put(Context.SECURITY_PRINCIPAL, pricipal); env.put(Context.SECURITY_CREDENTIALS, credentials); LogUtil.debug(_log, env); LdapContext ctx = null; try { ctx = new InitialLdapContext(env, null); } catch (Exception e) { if (_log.isWarnEnabled()) { _log.warn("Failed to bind to the LDAP server"); } if (_log.isDebugEnabled()) { _log.debug(e); } } return ctx; }
6.测试用户:Authentication search Filter验证过滤语句,现在是过滤email,Test LDAP Users这个按钮是不会调用这个过滤的,Import Search Filter测试用户过滤语句。下面是liferay数据与ldap数据一对应的关系。
测试成功
/** * 测试取用户列表 * @param companyId * @param ctx * @param maxResults * @return * @throws Exception */ public static NamingEnumeration<SearchResult> getUsers( long companyId, LdapContext ctx, int maxResults) throws Exception { String baseDN = PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_BASE_DN); String userFilter = PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_IMPORT_USER_SEARCH_FILTER); return getUsers(companyId, ctx, maxResults, baseDN, userFilter); } public static NamingEnumeration<SearchResult> getUsers( long companyId, LdapContext ctx, int maxResults, String baseDN, String userFilter) throws Exception { SearchControls cons = new SearchControls( SearchControls.SUBTREE_SCOPE, maxResults, 0, null, false, false); return ctx.search(baseDN, userFilter, cons); }
7.测试取得用户组织,基本上同上就不详解,仅贴出成功画面和相关代码
/** * 测试用户组织 * @param companyId * @param ctx * @param maxResults * @return * @throws Exception */ public static NamingEnumeration<SearchResult> getGroups( long companyId, LdapContext ctx, int maxResults) throws Exception { String baseDN = PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_BASE_DN); String groupFilter = PrefsPropsUtil.getString( companyId, PropsUtil.LDAP_IMPORT_GROUP_SEARCH_FILTER); return getGroups(companyId, ctx, maxResults, baseDN, groupFilter); } public static NamingEnumeration<SearchResult> getGroups( long companyId, LdapContext ctx, int maxResults, String baseDN, String groupFilter) throws Exception { SearchControls cons = new SearchControls( SearchControls.SUBTREE_SCOPE, maxResults, 0, null, false, false); return ctx.search(baseDN, groupFilter, cons); }
8.导入导出配置:Import Enabled:是否导入,导入时密码为空 。 Import on Startup Enabled:启动导入。Import Interval:导入间隔时间,注意时间太短会影响性能。Export Enabled:是否导出, 在用户登陆时和修改用户信息时都会导出至LDAP但不导出密码,在修改时如果修改了密码才会把密码也导出进去,如果密码未导入至LDAP并且前面的 Required也勾选了,那么LDAP验证不会通过 。后面三个为导出的设置。
/** * 从LDAP中导入用户 * @param companyId * @param ctx * @param attrs * @param password * @param importGroupMembership * @return * @throws Exception */ public static User importLDAPUser( long companyId, LdapContext ctx, Attributes attrs, String password, boolean importGroupMembership) throws Exception { AttributesTransformer attrsTransformer = AttributesTransformerFactory.getInstance(); attrs = attrsTransformer.transformUser(attrs); Properties userMappings = getUserMappings(companyId); LogUtil.debug(_log, userMappings); User defaultUser = UserLocalServiceUtil.getDefaultUser(companyId); boolean autoPassword = false; boolean updatePassword = true; if (password.equals(StringPool.BLANK)) { autoPassword = true; updatePassword = false; } long creatorUserId = 0; boolean passwordReset = false; boolean autoScreenName = false; String screenName = LDAPUtil.getAttributeValue( attrs, userMappings.getProperty("screenName")).toLowerCase(); String emailAddress = LDAPUtil.getAttributeValue( attrs, userMappings.getProperty("emailAddress")); Locale locale = defaultUser.getLocale(); String firstName = LDAPUtil.getAttributeValue( attrs, userMappings.getProperty("firstName")); String middleName = LDAPUtil.getAttributeValue( attrs, userMappings.getProperty("middleName")); String lastName = LDAPUtil.getAttributeValue( attrs, userMappings.getProperty("lastName")); if (Validator.isNull(firstName) || Validator.isNull(lastName)) { String fullName = LDAPUtil.getAttributeValue( attrs, userMappings.getProperty("fullName")); String[] names = LDAPUtil.splitFullName(fullName); firstName = names[0]; middleName = names[1]; lastName = names[2]; } int prefixId = 0; int suffixId = 0; boolean male = true; int birthdayMonth = Calendar.JANUARY; int birthdayDay = 1; int birthdayYear = 1970; String jobTitle = LDAPUtil.getAttributeValue( attrs, userMappings.getProperty("jobTitle")); long[] organizationIds = new long[0]; boolean sendEmail = false; if (_log.isDebugEnabled()) { _log.debug( "Screen name " + screenName + " and email address " + emailAddress); } if (Validator.isNull(screenName) || Validator.isNull(emailAddress)) { if (_log.isWarnEnabled()) { _log.warn( "Cannot add user because screen name and email address " + "are required"); } return null; } User user = null; try { // Find corresponding portal user String authType = PrefsPropsUtil.getString( companyId, PropsUtil.COMPANY_SECURITY_AUTH_TYPE, PropsValues.COMPANY_SECURITY_AUTH_TYPE); if (authType.equals(CompanyConstants.AUTH_TYPE_SN)) { user = UserLocalServiceUtil.getUserByScreenName( companyId, screenName); } else { user = UserLocalServiceUtil.getUserByEmailAddress( companyId, emailAddress); } // Skip if is default user if (user.isDefaultUser()) { return user; } // Skip import if user fields has been already synced and if // import is part of a scheduled import Date ldapUserModifiedDate = null; String modifiedDate = LDAPUtil.getAttributeValue( attrs, "modifyTimestamp"); try { DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); ldapUserModifiedDate = dateFormat.parse(modifiedDate); if (ldapUserModifiedDate.equals(user.getModifiedDate()) && autoPassword) { if (_log.isDebugEnabled()) { _log.debug( "User is already syncronized, skipping user " + user.getEmailAddress()); } return user; } } catch (ParseException pe) { if (_log.isDebugEnabled()) { _log.debug( "Unable to parse LDAP modify timestamp " + modifiedDate); } _log.debug(pe, pe); } Contact contact = user.getContact(); Calendar birthdayCal = CalendarFactoryUtil.getCalendar(); birthdayCal.setTime(contact.getBirthday()); birthdayMonth = birthdayCal.get(Calendar.MONTH); birthdayDay = birthdayCal.get(Calendar.DATE); birthdayYear = birthdayCal.get(Calendar.YEAR); // User exists so update user information if (updatePassword) { user = UserLocalServiceUtil.updatePassword( user.getUserId(), password, password, passwordReset, true); } user = UserLocalServiceUtil.updateUser( user.getUserId(), password, user.isPasswordReset(), screenName, emailAddress, user.getLanguageId(), user.getTimeZoneId(), user.getGreeting(), user.getComments(), firstName, middleName, lastName, contact.getPrefixId(), contact.getSuffixId(), contact.getMale(), birthdayMonth, birthdayDay, birthdayYear, contact.getSmsSn(), contact.getAimSn(), contact.getIcqSn(), contact.getJabberSn(), contact.getMsnSn(), contact.getSkypeSn(), contact.getYmSn(), jobTitle, user.getOrganizationIds()); if (ldapUserModifiedDate != null) { UserLocalServiceUtil.updateModifiedDate( user.getUserId(), ldapUserModifiedDate); } } catch (NoSuchUserException nsue) { // User does not exist so create } if (user == null) { try { if (_log.isDebugEnabled()) { _log.debug("Adding user to portal " + emailAddress); } user = UserLocalServiceUtil.addUser( creatorUserId, companyId, autoPassword, password, password, autoScreenName, screenName, emailAddress, locale, firstName, middleName, lastName, prefixId, suffixId, male, birthdayMonth, birthdayDay, birthdayYear, jobTitle, organizationIds, sendEmail); } catch (Exception e){ _log.error( "Problem adding user with screen name " + screenName + " and email address " + emailAddress, e); } } // Import user groups and membership if (importGroupMembership && (user != null)) { String userMappingsGroup = userMappings.getProperty("group"); if (userMappingsGroup != null) { Attribute attr = attrs.get(userMappingsGroup); if (attr != null){ _importGroupsAndMembershipFromLDAPUser( companyId, ctx, user.getUserId(), attr); } } } return user; }
/** * 将用户导出至LDAP * @param user * @throws Exception */ public static void exportToLDAP(User user) throws Exception { long companyId = user.getCompanyId(); if (!isAuthEnabled(companyId) || !isExportEnabled(companyId)) { return; } LdapContext ctx = getContext(companyId); if (ctx == null) { return; } Properties userMappings = getUserMappings(companyId); Binding binding = getUser(user.getCompanyId(), user.getScreenName()); String name = StringPool.BLANK; if (binding == null) { // Generate full DN based on user DN StringMaker sm = new StringMaker(); sm.append(userMappings.getProperty("screenName")); sm.append(StringPool.EQUAL); sm.append(user.getScreenName()); sm.append(StringPool.COMMA); sm.append(getUsersDN(companyId)); name = sm.toString(); // Create new user in LDAP LDAPUser ldapUser = (LDAPUser)Class.forName( PropsValues.LDAP_USER_IMPL).newInstance(); ldapUser.setUser(user); ctx.bind(name, ldapUser); } else { // Modify existing LDAP user record name = getNameInNamespace(companyId, binding); Modifications mods = Modifications.getInstance(); if (user.isPasswordModified() && Validator.isNotNull(user.getPasswordUnencrypted())) { mods.addItem( userMappings.getProperty("password"), user.getPasswordUnencrypted()); } mods.addItem( userMappings.getProperty("emailAddress"), user.getEmailAddress()); ModificationItem[] modItems = mods.getItems(); ctx.modifyAttributes(name, modItems); } ctx.close(); }
9.Use LDAP Password Policy:是使用LDAP的密码策略,这个主要用在验证方面,不推荐使用。