Commit 3afbe93c authored by Jason Song's avatar Jason Song Committed by GitHub

Merge pull request #1562 from codepiano/ldap_support_ad

add active directory support
parents c601f57b 815162b4
...@@ -2,7 +2,6 @@ package com.ctrip.framework.apollo.portal; ...@@ -2,7 +2,6 @@ package com.ctrip.framework.apollo.portal;
import com.ctrip.framework.apollo.common.ApolloCommonConfig; import com.ctrip.framework.apollo.common.ApolloCommonConfig;
import com.ctrip.framework.apollo.openapi.PortalOpenApiConfig; import com.ctrip.framework.apollo.openapi.PortalOpenApiConfig;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
......
package com.ctrip.framework.apollo.portal.entity.bo;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import javax.naming.Name;
/**
* @author xm.lin xm.lin@anxincloud.com
* @Description
* @date 18-8-9 下午4:43
*/
@Entry(base = "cn=Manager",objectClasses = {"inetOrgPerson"})
public class LdapUserInfo {
@Id
private Name id;
@Attribute(name = "cn")
private String username;
@Attribute(name = "sn")
private String realName;
@Attribute(name = "userPassword")
private String userPassword;
@Attribute(name = "mail")
private String mail;
public Name getId() {
return id;
}
public void setId(Name id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
}
...@@ -43,12 +43,14 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; ...@@ -43,12 +43,14 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.LdapShaPasswordEncoder; import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.provisioning.JdbcUserDetailsManager; import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
@Configuration @Configuration
public class AuthConfiguration { public class AuthConfiguration {
...@@ -128,10 +130,8 @@ public class AuthConfiguration { ...@@ -128,10 +130,8 @@ public class AuthConfiguration {
casValidationFilter.setOrder(3); casValidationFilter.setOrder(3);
return casValidationFilter; return casValidationFilter;
} }
@Bean @Bean
public FilterRegistrationBean assertionHolder() { public FilterRegistrationBean assertionHolder() {
FilterRegistrationBean assertionHolderFilter = new FilterRegistrationBean(); FilterRegistrationBean assertionHolderFilter = new FilterRegistrationBean();
...@@ -168,7 +168,6 @@ public class AuthConfiguration { ...@@ -168,7 +168,6 @@ public class AuthConfiguration {
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("instance filter fail", e); throw new RuntimeException("instance filter fail", e);
} }
} }
private EventListener listener(String className) { private EventListener listener(String className) {
...@@ -191,10 +190,8 @@ public class AuthConfiguration { ...@@ -191,10 +190,8 @@ public class AuthConfiguration {
public SsoHeartbeatHandler ctripSsoHeartbeatHandler() { public SsoHeartbeatHandler ctripSsoHeartbeatHandler() {
return new CtripSsoHeartbeatHandler(); return new CtripSsoHeartbeatHandler();
} }
} }
/** /**
* spring.profiles.active = auth * spring.profiles.active = auth
*/ */
...@@ -287,8 +284,10 @@ public class AuthConfiguration { ...@@ -287,8 +284,10 @@ public class AuthConfiguration {
@Profile("ldap") @Profile("ldap")
@EnableConfigurationProperties(LdapProperties.class) @EnableConfigurationProperties(LdapProperties.class)
static class SpringSecurityLDAPAuthAutoConfiguration { static class SpringSecurityLDAPAuthAutoConfiguration {
@Autowired @Autowired
private LdapProperties properties; private LdapProperties properties;
@Autowired @Autowired
private Environment environment; private Environment environment;
...@@ -333,9 +332,10 @@ public class AuthConfiguration { ...@@ -333,9 +332,10 @@ public class AuthConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(LdapOperations.class) @ConditionalOnMissingBean(LdapOperations.class)
public LdapTemplate ldapTemplate(ContextSource contextSource) { public LdapTemplate ldapTemplate(ContextSource contextSource) {
return new LdapTemplate(contextSource); LdapTemplate ldapTemplate = new LdapTemplate(contextSource);
ldapTemplate.setIgnorePartialResultException(true);
return ldapTemplate;
} }
} }
@Order(99) @Order(99)
...@@ -350,6 +350,27 @@ public class AuthConfiguration { ...@@ -350,6 +350,27 @@ public class AuthConfiguration {
@Autowired @Autowired
private LdapContextSource ldapContextSource; private LdapContextSource ldapContextSource;
@Bean
public FilterBasedLdapUserSearch userSearch() {
FilterBasedLdapUserSearch filterBasedLdapUserSearch = new FilterBasedLdapUserSearch("",
ldapProperties.getSearchFilter(), ldapContextSource);
filterBasedLdapUserSearch.setSearchSubtree(true);
return filterBasedLdapUserSearch;
}
@Bean
public LdapAuthenticationProvider ldapAuthProvider() {
BindAuthenticator bindAuthenticator = new BindAuthenticator(ldapContextSource);
bindAuthenticator.setUserSearch(userSearch());
DefaultLdapAuthoritiesPopulator defaultAuthAutoConfiguration = new DefaultLdapAuthoritiesPopulator(
ldapContextSource, null);
defaultAuthAutoConfiguration.setIgnorePartialResultException(true);
defaultAuthAutoConfiguration.setSearchSubtree(true);
LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(
bindAuthenticator, defaultAuthAutoConfiguration);
return ldapAuthenticationProvider;
}
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); http.csrf().disable();
...@@ -367,12 +388,7 @@ public class AuthConfiguration { ...@@ -367,12 +388,7 @@ public class AuthConfiguration {
@Override @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication() auth.authenticationProvider(ldapAuthProvider());
.userDnPatterns(ldapProperties.getUserDnPatterns())
.contextSource(ldapContextSource)
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
} }
} }
......
...@@ -4,7 +4,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties; ...@@ -4,7 +4,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
...@@ -43,13 +42,16 @@ public class LdapProperties { ...@@ -43,13 +42,16 @@ public class LdapProperties {
*/ */
private boolean anonymousReadOnly; private boolean anonymousReadOnly;
/**
* User search filter
*/
private String searchFilter;
/** /**
* LDAP specification settings. * LDAP specification settings.
*/ */
private final Map<String, String> baseEnvironment = new HashMap<>(); private final Map<String, String> baseEnvironment = new HashMap<>();
private String userDnPatterns;
public String[] getUrls() { public String[] getUrls() {
return this.urls; return this.urls;
} }
...@@ -90,12 +92,12 @@ public class LdapProperties { ...@@ -90,12 +92,12 @@ public class LdapProperties {
this.anonymousReadOnly = anonymousReadOnly; this.anonymousReadOnly = anonymousReadOnly;
} }
public String getUserDnPatterns() { public String getSearchFilter() {
return userDnPatterns; return searchFilter;
} }
public void setUserDnPatterns(String userDnPatterns) { public void setSearchFilter(String searchFilter) {
this.userDnPatterns = userDnPatterns; this.searchFilter = searchFilter;
} }
public Map<String, String> getBaseEnvironment() { public Map<String, String> getBaseEnvironment() {
......
package com.ctrip.framework.apollo.portal.spi.ldap; package com.ctrip.framework.apollo.portal.spi.ldap;
import com.ctrip.framework.apollo.portal.entity.bo.LdapUserInfo; import static org.springframework.ldap.query.LdapQueryBuilder.query;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserService; import com.ctrip.framework.apollo.portal.spi.UserService;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Lists; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.query.ContainerCriteria; import org.springframework.ldap.query.ContainerCriteria;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.query.SearchScope; import org.springframework.ldap.query.SearchScope;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
...@@ -20,61 +24,71 @@ import org.springframework.util.CollectionUtils; ...@@ -20,61 +24,71 @@ import org.springframework.util.CollectionUtils;
*/ */
public class LdapUserService implements UserService { public class LdapUserService implements UserService {
@Value("${ldap.mapping.objectClass}")
private String objectClassAttrName;
@Value("${ldap.mapping.loginId}")
private String loginIdAttrName;
@Value("${ldap.mapping.userDisplayName}")
private String userDisplayNameAttrName;
@Value("${ldap.mapping.email}")
private String emailAttrName;
@Value("#{'${ldap.filter.memberOf:}'.split('\\|')}")
private String[] memberOf;
@Autowired @Autowired
private LdapTemplate ldapTemplate; private LdapTemplate ldapTemplate;
private static final String MEMBER_OF_ATTR_NAME = "memberOf";
private ContextMapper<UserInfo> ldapUserInfoMapper = (ctx) -> {
DirContextAdapter contextAdapter = (DirContextAdapter) ctx;
UserInfo userInfo = new UserInfo();
userInfo.setUserId(contextAdapter.getStringAttribute(loginIdAttrName));
userInfo.setName(contextAdapter.getStringAttribute(userDisplayNameAttrName));
userInfo.setEmail(contextAdapter.getStringAttribute(emailAttrName));
return userInfo;
};
private ContainerCriteria ldapQueryCriteria() {
ContainerCriteria criteria = query()
.searchScope(SearchScope.SUBTREE)
.where("objectClass").is(objectClassAttrName);
if (memberOf.length > 0 && !StringUtils.isEmpty(memberOf[0])) {
ContainerCriteria memberOfFilters = query().where(MEMBER_OF_ATTR_NAME).is(memberOf[0]);
Arrays.stream(memberOf).skip(1)
.forEach(filter -> memberOfFilters.or(MEMBER_OF_ATTR_NAME).is(filter));
criteria.and(memberOfFilters);
}
return criteria;
}
@Override @Override
public List<UserInfo> searchUsers(String keyword, int offset, int limit) { public List<UserInfo> searchUsers(String keyword, int offset, int limit) {
List<LdapUserInfo> ldapUserInfoList = null; ContainerCriteria criteria = ldapQueryCriteria();
if (Strings.isNullOrEmpty(keyword)) { if (!Strings.isNullOrEmpty(keyword)) {
ldapUserInfoList = ldapTemplate.findAll(LdapUserInfo.class); criteria.and(query().where(loginIdAttrName).like(keyword + "*").or(userDisplayNameAttrName)
} else { .like(keyword + "*"));
ContainerCriteria criteria = LdapQueryBuilder
.query().searchScope(SearchScope.SUBTREE)
.where("cn").like(keyword + "*")
.or("sn").like(keyword + "*");
ldapUserInfoList = ldapTemplate.find(criteria, LdapUserInfo.class);
} }
return convertLdapUserToUserInfo(ldapUserInfoList); return ldapTemplate.search(criteria, ldapUserInfoMapper);
} }
@Override @Override
public UserInfo findByUserId(String userId) { public UserInfo findByUserId(String userId) {
ContainerCriteria criteria = LdapQueryBuilder.query().where("cn").is(userId); return ldapTemplate
LdapUserInfo ldapUser = ldapTemplate.findOne(criteria, LdapUserInfo.class); .searchForObject(ldapQueryCriteria().and(loginIdAttrName).is(userId), ldapUserInfoMapper);
UserInfo userInfo = new UserInfo();
userInfo.setUserId(ldapUser.getUsername());
userInfo.setName(ldapUser.getRealName());
userInfo.setEmail(ldapUser.getMail());
return userInfo;
} }
@Override @Override
public List<UserInfo> findByUserIds(List<String> userIds) { public List<UserInfo> findByUserIds(List<String> userIds) {
if (!CollectionUtils.isEmpty(userIds)) { if (CollectionUtils.isEmpty(userIds)) {
LdapQueryBuilder ldapQueryBuilder = LdapQueryBuilder.query() return null;
.searchScope(SearchScope.SUBTREE); } else {
ContainerCriteria criteria = ldapQueryBuilder.where("cn").is(userIds.get(0)); ContainerCriteria criteria = ldapQueryCriteria()
userIds.stream().skip(1).forEach(userId -> { .and(query().where(loginIdAttrName).is(userIds.get(0)));
criteria.or("cn").is(userId); userIds.stream().skip(1).forEach(userId -> criteria.or(loginIdAttrName).is(userId));
}); return ldapTemplate.search(criteria, ldapUserInfoMapper);
List<LdapUserInfo> ldapUserInfoList = ldapTemplate.find(criteria, LdapUserInfo.class);
return convertLdapUserToUserInfo(ldapUserInfoList);
} }
return null;
} }
private List<UserInfo> convertLdapUserToUserInfo(List<LdapUserInfo> ldapUserInfoList) {
List<UserInfo> userInfoList = Lists.newArrayList();
if (!CollectionUtils.isEmpty(ldapUserInfoList)) {
ldapUserInfoList.stream().map(p -> {
UserInfo userInfo = new UserInfo();
userInfo.setUserId(p.getUsername());
userInfo.setName(p.getRealName());
userInfo.setEmail(p.getMail());
return userInfo;
}).forEach(userInfoList::add);
}
return userInfoList;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment