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;
import com.ctrip.framework.apollo.common.ApolloCommonConfig;
import com.ctrip.framework.apollo.openapi.PortalOpenApiConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
@Configuration
public class AuthConfiguration {
......@@ -128,10 +130,8 @@ public class AuthConfiguration {
casValidationFilter.setOrder(3);
return casValidationFilter;
}
@Bean
public FilterRegistrationBean assertionHolder() {
FilterRegistrationBean assertionHolderFilter = new FilterRegistrationBean();
......@@ -168,7 +168,6 @@ public class AuthConfiguration {
} catch (Exception e) {
throw new RuntimeException("instance filter fail", e);
}
}
private EventListener listener(String className) {
......@@ -191,10 +190,8 @@ public class AuthConfiguration {
public SsoHeartbeatHandler ctripSsoHeartbeatHandler() {
return new CtripSsoHeartbeatHandler();
}
}
/**
* spring.profiles.active = auth
*/
......@@ -287,8 +284,10 @@ public class AuthConfiguration {
@Profile("ldap")
@EnableConfigurationProperties(LdapProperties.class)
static class SpringSecurityLDAPAuthAutoConfiguration {
@Autowired
private LdapProperties properties;
@Autowired
private Environment environment;
......@@ -333,9 +332,10 @@ public class AuthConfiguration {
@Bean
@ConditionalOnMissingBean(LdapOperations.class)
public LdapTemplate ldapTemplate(ContextSource contextSource) {
return new LdapTemplate(contextSource);
LdapTemplate ldapTemplate = new LdapTemplate(contextSource);
ldapTemplate.setIgnorePartialResultException(true);
return ldapTemplate;
}
}
@Order(99)
......@@ -350,6 +350,27 @@ public class AuthConfiguration {
@Autowired
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
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
......@@ -367,12 +388,7 @@ public class AuthConfiguration {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userDnPatterns(ldapProperties.getUserDnPatterns())
.contextSource(ldapContextSource)
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
auth.authenticationProvider(ldapAuthProvider());
}
}
......
......@@ -4,7 +4,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import java.util.HashMap;
import java.util.Map;
......@@ -43,13 +42,16 @@ public class LdapProperties {
*/
private boolean anonymousReadOnly;
/**
* User search filter
*/
private String searchFilter;
/**
* LDAP specification settings.
*/
private final Map<String, String> baseEnvironment = new HashMap<>();
private String userDnPatterns;
public String[] getUrls() {
return this.urls;
}
......@@ -90,12 +92,12 @@ public class LdapProperties {
this.anonymousReadOnly = anonymousReadOnly;
}
public String getUserDnPatterns() {
return userDnPatterns;
public String getSearchFilter() {
return searchFilter;
}
public void setUserDnPatterns(String userDnPatterns) {
this.userDnPatterns = userDnPatterns;
public void setSearchFilter(String searchFilter) {
this.searchFilter = searchFilter;
}
public Map<String, String> getBaseEnvironment() {
......
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.spi.UserService;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import java.util.Arrays;
import java.util.List;
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.query.ContainerCriteria;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.query.SearchScope;
import org.springframework.util.CollectionUtils;
......@@ -20,61 +24,71 @@ import org.springframework.util.CollectionUtils;
*/
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
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
public List<UserInfo> searchUsers(String keyword, int offset, int limit) {
List<LdapUserInfo> ldapUserInfoList = null;
if (Strings.isNullOrEmpty(keyword)) {
ldapUserInfoList = ldapTemplate.findAll(LdapUserInfo.class);
} else {
ContainerCriteria criteria = LdapQueryBuilder
.query().searchScope(SearchScope.SUBTREE)
.where("cn").like(keyword + "*")
.or("sn").like(keyword + "*");
ldapUserInfoList = ldapTemplate.find(criteria, LdapUserInfo.class);
ContainerCriteria criteria = ldapQueryCriteria();
if (!Strings.isNullOrEmpty(keyword)) {
criteria.and(query().where(loginIdAttrName).like(keyword + "*").or(userDisplayNameAttrName)
.like(keyword + "*"));
}
return convertLdapUserToUserInfo(ldapUserInfoList);
return ldapTemplate.search(criteria, ldapUserInfoMapper);
}
@Override
public UserInfo findByUserId(String userId) {
ContainerCriteria criteria = LdapQueryBuilder.query().where("cn").is(userId);
LdapUserInfo ldapUser = ldapTemplate.findOne(criteria, LdapUserInfo.class);
UserInfo userInfo = new UserInfo();
userInfo.setUserId(ldapUser.getUsername());
userInfo.setName(ldapUser.getRealName());
userInfo.setEmail(ldapUser.getMail());
return userInfo;
return ldapTemplate
.searchForObject(ldapQueryCriteria().and(loginIdAttrName).is(userId), ldapUserInfoMapper);
}
@Override
public List<UserInfo> findByUserIds(List<String> userIds) {
if (!CollectionUtils.isEmpty(userIds)) {
LdapQueryBuilder ldapQueryBuilder = LdapQueryBuilder.query()
.searchScope(SearchScope.SUBTREE);
ContainerCriteria criteria = ldapQueryBuilder.where("cn").is(userIds.get(0));
userIds.stream().skip(1).forEach(userId -> {
criteria.or("cn").is(userId);
});
List<LdapUserInfo> ldapUserInfoList = ldapTemplate.find(criteria, LdapUserInfo.class);
return convertLdapUserToUserInfo(ldapUserInfoList);
}
if (CollectionUtils.isEmpty(userIds)) {
return null;
} else {
ContainerCriteria criteria = ldapQueryCriteria()
.and(query().where(loginIdAttrName).is(userIds.get(0)));
userIds.stream().skip(1).forEach(userId -> criteria.or(loginIdAttrName).is(userId));
return ldapTemplate.search(criteria, ldapUserInfoMapper);
}
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