Skip to content

Commit

Permalink
增强LDAP用户服务,支持按组查找用户 (#1794)
Browse files Browse the repository at this point in the history
enhance ldap user service to support filtering users by group
  • Loading branch information
idefav authored and nobodyiam committed Feb 13, 2019
1 parent daa1839 commit 682de9c
Show file tree
Hide file tree
Showing 6 changed files with 535 additions and 19 deletions.
21 changes: 21 additions & 0 deletions apollo-portal/src/main/config/application-ldap-apacheds-sample.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
spring:
ldap:
base: "dc=example,dc=com"
username: "uid=admin,ou=system" # 配置管理员账号,用于搜索、匹配用户
password: "password"
searchFilter: "(uid={0})" # 用户过滤器,登录的时候用这个过滤器来搜索用户
urls:
- "ldap://localhost:10389"

ldap:
mapping: # 配置 ldap 属性
objectClass: "inetOrgPerson" # ldap 用户 objectClass 配置
loginId: "uid" # ldap 用户惟一 id,用来作为登录的 id
rdnKey: "cn" # ldap rdn key
userDisplayName: "displayName" # ldap 用户名,用来作为显示名
email: "mail" # ldap 邮箱属性
group: # 配置ldap group
objectClass: "groupOfNames" # 配置groupClassName
groupBase: "ou=group" # group search base
groupSearch: "(&(cn=apollo-admins)(&(member=*)))" # group filter
groupMembership: "member" # group memberShip eg. member or memberUid
20 changes: 20 additions & 0 deletions apollo-portal/src/main/config/application-ldap-openldap-sample.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
spring:
ldap:
base: "dc=example,dc=com"
username: "cn=Manager,dc=example,dc=com" # 配置管理员账号,用于搜索、匹配用户
password: "password"
searchFilter: "(uid={0})" # 用户过滤器,登录的时候用这个过滤器来搜索用户
urls:
- "ldap://localhost:389"

ldap:
mapping: # 配置 ldap 属性
objectClass: "inetOrgPerson" # ldap 用户 objectClass 配置
loginId: "uid" # ldap 用户惟一 id,用来作为登录的 id
rdnKey: "uid" # ldap rdn key
userDisplayName: "displayName" # ldap 用户名,用来作为显示名
email: "mail" # ldap 邮箱属性
group: # 配置ldap group
groupBase: "ou=Group" # group search base
groupSearch: "(&(cn=apollo-admins))" # group filter
groupMembership: "memberUid" # group memberShip
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ctrip.framework.apollo.portal.spi.configuration;

import com.ctrip.framework.apollo.common.condition.ConditionalOnMissingProfile;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import com.ctrip.framework.apollo.portal.spi.LogoutHandler;
import com.ctrip.framework.apollo.portal.spi.SsoHeartbeatHandler;
Expand All @@ -14,6 +15,7 @@
import com.ctrip.framework.apollo.portal.spi.defaultimpl.DefaultSsoHeartbeatHandler;
import com.ctrip.framework.apollo.portal.spi.defaultimpl.DefaultUserInfoHolder;
import com.ctrip.framework.apollo.portal.spi.defaultimpl.DefaultUserService;
import com.ctrip.framework.apollo.portal.spi.ldap.FilterLdapByGroupUserSearch;
import com.ctrip.framework.apollo.portal.spi.ldap.LdapUserService;
import com.ctrip.framework.apollo.portal.spi.springsecurity.SpringSecurityUserInfoHolder;
import com.ctrip.framework.apollo.portal.spi.springsecurity.SpringSecurityUserService;
Expand Down Expand Up @@ -285,7 +287,7 @@ protected void configure(HttpSecurity http) throws Exception {
*/
@Configuration
@Profile("ldap")
@EnableConfigurationProperties(LdapProperties.class)
@EnableConfigurationProperties({LdapProperties.class,LdapExtendProperties.class})
static class SpringSecurityLDAPAuthAutoConfiguration {

private final LdapProperties properties;
Expand Down Expand Up @@ -353,17 +355,33 @@ static class SpringSecurityLDAPConfigurer extends WebSecurityConfigurerAdapter {
private final LdapProperties ldapProperties;
private final LdapContextSource ldapContextSource;

public SpringSecurityLDAPConfigurer(final LdapProperties ldapProperties, final LdapContextSource ldapContextSource) {
private final LdapExtendProperties ldapExtendProperties;

public SpringSecurityLDAPConfigurer(final LdapProperties ldapProperties,
final LdapContextSource ldapContextSource,
final LdapExtendProperties ldapExtendProperties) {
this.ldapProperties = ldapProperties;
this.ldapContextSource = ldapContextSource;
this.ldapExtendProperties = ldapExtendProperties;
}

@Bean
public FilterBasedLdapUserSearch userSearch() {
FilterBasedLdapUserSearch filterBasedLdapUserSearch = new FilterBasedLdapUserSearch("",
ldapProperties.getSearchFilter(), ldapContextSource);
filterBasedLdapUserSearch.setSearchSubtree(true);
return filterBasedLdapUserSearch;
if (ldapExtendProperties.getGroup() == null || StringUtils
.isBlank(ldapExtendProperties.getGroup().getGroupSearch())) {
FilterBasedLdapUserSearch filterBasedLdapUserSearch = new FilterBasedLdapUserSearch("",
ldapProperties.getSearchFilter(), ldapContextSource);
filterBasedLdapUserSearch.setSearchSubtree(true);
return filterBasedLdapUserSearch;
} else {
FilterLdapByGroupUserSearch filterLdapByGroupUserSearch = new FilterLdapByGroupUserSearch(
ldapProperties.getBase(), ldapProperties.getSearchFilter(), ldapExtendProperties.getGroup().getGroupBase(),
ldapContextSource, ldapExtendProperties.getGroup().getGroupSearch(),
ldapExtendProperties.getMapping().getRdnKey(),
ldapExtendProperties.getGroup().getGroupMembership(),ldapExtendProperties.getMapping().getLoginId());
filterLdapByGroupUserSearch.setSearchSubtree(true);
return filterLdapByGroupUserSearch;
}
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright (c) 2019 www.ceair.com Inc. All rights reserved.
*/

package com.ctrip.framework.apollo.portal.spi.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* the LdapExtendProperties description.
*
* @author wuzishu
*/
@ConfigurationProperties(prefix = "ldap")
public class LdapExtendProperties {

private LdapMappingProperties mapping;
private LdapGroupProperties group;

public LdapMappingProperties getMapping() {
return mapping;
}

public void setMapping(LdapMappingProperties mapping) {
this.mapping = mapping;
}

public LdapGroupProperties getGroup() {
return group;
}

public void setGroup(LdapGroupProperties group) {
this.group = group;
}
}
class LdapMappingProperties{

/**
* user ldap objectClass
*/
private String objectClass;

/**
* user login Id
*/
private String loginId;

/**
* user rdn key
*/
private String rdnKey;

/**
* user display name
*/
private String userDisplayName;

/**
* email
*/
private String email;

public String getObjectClass() {
return objectClass;
}

public void setObjectClass(String objectClass) {
this.objectClass = objectClass;
}

public String getLoginId() {
return loginId;
}

public void setLoginId(String loginId) {
this.loginId = loginId;
}

public String getRdnKey() {
return rdnKey;
}

public void setRdnKey(String rdnKey) {
this.rdnKey = rdnKey;
}

public String getUserDisplayName() {
return userDisplayName;
}

public void setUserDisplayName(String userDisplayName) {
this.userDisplayName = userDisplayName;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}
class LdapGroupProperties{

/**
* group search base
*/
private String groupBase;

/**
* group search filter
*/
private String groupSearch;

/**
* group membership prop
*/
private String groupMembership;

public String getGroupBase() {
return groupBase;
}

public void setGroupBase(String groupBase) {
this.groupBase = groupBase;
}

public String getGroupSearch() {
return groupSearch;
}

public void setGroupSearch(String groupSearch) {
this.groupSearch = groupSearch;
}

public String getGroupMembership() {
return groupMembership;
}

public void setGroupMembership(String groupMembership) {
this.groupMembership = groupMembership;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@


package com.ctrip.framework.apollo.portal.spi.ldap;

import static org.springframework.ldap.query.LdapQueryBuilder.query;

import javax.naming.Name;
import javax.naming.directory.SearchControls;
import javax.naming.ldap.LdapName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;

/**
* the FilterLdapByGroupUserSearch description.
*
* @author wuzishu
*/
public class FilterLdapByGroupUserSearch extends FilterBasedLdapUserSearch {

private static final Logger logger = LoggerFactory.getLogger(FilterLdapByGroupUserSearch.class);
private static final String MEMBER_UID_ATTR_NAME = "memberUid";
private String searchBase;
private String groupBase;
private String groupSearch;
private String rdnKey;
private String groupMembershipAttrName;
private String loginIdAttrName;

private final SearchControls searchControls = new SearchControls();

private BaseLdapPathContextSource contextSource;


public FilterLdapByGroupUserSearch(String searchBase, String searchFilter,
String groupBase, BaseLdapPathContextSource contextSource, String groupSearch,
String rdnKey, String groupMembershipAttrName, String loginIdAttrName) {
super(searchBase, searchFilter, contextSource);
this.searchBase = searchBase;
this.groupBase = groupBase;
this.groupSearch = groupSearch;
this.contextSource = contextSource;
this.rdnKey = rdnKey;
this.groupMembershipAttrName = groupMembershipAttrName;
this.loginIdAttrName = loginIdAttrName;
}

private Name searchUserById(String userId) {
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(this.contextSource);
template.setSearchControls(searchControls);
return template.searchForObject(query().where(this.loginIdAttrName).is(userId),
ctx -> ((DirContextAdapter) ctx).getDn());
}


@Override
public DirContextOperations searchForUser(String username) {
if (logger.isDebugEnabled()) {
logger.debug("Searching for user '" + username + "', with user search " + this);
}
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(this.contextSource);
template.setSearchControls(searchControls);
return template
.searchForObject(groupBase, groupSearch, ctx -> {
if (!MEMBER_UID_ATTR_NAME.equals(groupMembershipAttrName)) {
String[] members = ((DirContextAdapter) ctx)
.getStringAttributes(groupMembershipAttrName);
for (String item : members) {
LdapName memberDn = LdapUtils.newLdapName(item);
LdapName memberRdn = LdapUtils
.removeFirst(memberDn, LdapUtils.newLdapName(searchBase));
String rdnValue = LdapUtils.getValue(memberRdn, rdnKey).toString();
if (rdnValue.equalsIgnoreCase(username)) {
return new DirContextAdapter(memberRdn.toString());
}
}
throw new UsernameNotFoundException("User " + username + " not found in directory.");
} else {
String[] memberUids = ((DirContextAdapter) ctx)
.getStringAttributes(groupMembershipAttrName);
for (String memberUid : memberUids) {
if (memberUid.equalsIgnoreCase(username)) {
Name name = searchUserById(memberUid);
LdapName ldapName = LdapUtils.newLdapName(name);
LdapName ldapRdn = LdapUtils
.removeFirst(ldapName, LdapUtils.newLdapName(searchBase));
return new DirContextAdapter(ldapRdn);
}
}
}
throw new UsernameNotFoundException("User " + username + " not found in directory.");
});
}
}
Loading

0 comments on commit 682de9c

Please sign in to comment.