Spring 引导从 LDAP 获取角色

Spring Boot getting Roles from LDAP

我正在尝试使用 LDAP 进行 spring 身份验证和授权。尽管我已成功验证用户身份,但我还无法从 LDAP 获取角色。

dn: ou=Roles,dc=example,dc=org
objectClass: top
objectClass: organizationalUnit
ou: Roles

dn: ou=Users,dc=example,dc=org
objectClass: organizationalUnit
ou: Users

dn: cn=user2,ou=Users,dc=example,dc=org
objectClass: person
objectClass: top
cn: user1
sn: user1
userPassword:: bGRhcFBhc3N3b3Jk

dn: cn=user2,ou=Users,dc=example,dc=org
objectClass: person
objectClass: top
cn: user2
sn: user2
userPassword:: bGRhcFBhc3N3b3Jk

dn: cn=ROLE_ACL_ADMIN,ou=Roles,dc=example,dc=org
objectClass: groupOfUniqueNames
cn: ROLE_ACL_ADMIN
uniqueMember: cn=user1,ou=Users,dc=example,dc=org
uniqueMember: cn=user2,ou=Users,dc=example,dc=org

这是我的 spring 配置。我想我在 groupSearchBase 中输入了错误的值,但我不确定我还能在那里输入什么(也试过 dc=example,dc=org)。

我可以在终端上使用此 ldap 查询搜索角色 ldapsearch -xLLL -b "dc=example,dc=org" "(&(cn=*)(objectClass=groupOfUniqueNames)(uniqueMember=cn=user1,ou=Users,dc=example,dc=org))" -D "cn=admin,dc=example,dc=org" -w admin

auth.ldapAuthentication()
                    .userDetailsContextMapper(userDetailsContextMapper())
                    .userDnPatterns("cn={0},ou=Users,dc=example,dc=org")
                    .userSearchFilter("(cn={0})")
                    .groupSearchBase("ou=Roles,dc=example,dc=org") 
                    .groupSearchFilter("(&(cn=*)(objectClass=groupOfUniqueNames)(uniqueMember=cn={0}))")
                    .contextSource()
                    .url("ldap://127.0.0.1:389/dc=example,dc=org")
                    .managerDn("cn=admin,dc=example,dc=org")
                    .managerPassword("admin");

错误日志

org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - No Such Object]; nested exception is javax.naming.NameNotFoundException: [LDAP: error code 32 - No Such Object]; remaining name 'cn=*,ou=Roles,dc=example,dc=org'
    at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:183) ~[spring-ldap-core-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:376) ~[spring-ldap-core-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:328) ~[spring-ldap-core-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:629) ~[spring-ldap-core-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:570) ~[spring-ldap-core-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForMultipleAttributeValues(SpringSecurityLdapTemplate.java:237) ~[spring-security-ldap-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForSingleAttributeValues(SpringSecurityLdapTemplate.java:164) ~[spring-security-ldap-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator.getGroupMembershipRoles(DefaultLdapAuthoritiesPopulator.java:241) ~[spring-security-ldap-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator.getGrantedAuthorities(DefaultLdapAuthoritiesPopulator.java:210) ~[spring-security-ldap-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.ldap.authentication.LdapAuthenticationProvider.loadUserAuthorities(LdapAuthenticationProvider.java:213) ~[spring-security-ldap-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.authenticate(AbstractLdapAuthenticationProvider.java:89) ~[spring-security-ldap-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175) ~[spring-security-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:195) ~[spring-security-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:95) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at com.nerodata.commons.web.security.filter.CredentialsExpiredRedirectFilter.doFilterInternal(CredentialsExpiredRedirectFilter.java:79) ~[commons-web-2.8.0-SNAPSHOT.jar:na]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at com.nerodata.commons.web.security.filter.CsrfHeaderFilter.doFilterInternal(CsrfHeaderFilter.java:67) ~[commons-web-2.8.0-SNAPSHOT.jar:na]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:141) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:171) ~[spring-session-1.3.5.RELEASE.jar:na]
    at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80) ~[spring-session-1.3.5.RELEASE.jar:na]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_301]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_301]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.27.jar:9.0.27]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_301]
Caused by: javax.naming.NameNotFoundException: [LDAP: error code 32 - No Such Object]
    at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3284) ~[na:1.8.0_301]
    at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:3205) ~[na:1.8.0_301]
    at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2996) ~[na:1.8.0_301]
    at com.sun.jndi.ldap.LdapCtx.searchAux(LdapCtx.java:1875) ~[na:1.8.0_301]
    at com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1798) ~[na:1.8.0_301]
    at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(ComponentDirContext.java:392) ~[na:1.8.0_301]
    at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:358) ~[na:1.8.0_301]
    at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:341) ~[na:1.8.0_301]
    at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:267) ~[na:1.8.0_301]
    at org.springframework.ldap.core.LdapTemplate.executeSearch(LdapTemplate.java:322) ~[spring-ldap-core-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:363) ~[spring-ldap-core-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    ... 68 common frames omitted

有人知道这里有什么问题吗?如果有任何指点,我将不胜感激。

  • 组搜索过滤器中的第二个条件有问题,应该是 (uniqueMember={0})cn= 是替换 dn 的一部分)。此外,(cn=*) 不是必需的,因为 cn 是所有 groupOfUniqueNames 条目的强制属性。

  • 由于上下文源 url 包含用于搜索和验证用户的基本 DN (dc=example,dc=org),因此您无需在 user/group 搜索参数(相对于该基数)。

  • 使用 userDnPatterns
  • userSearchFilter() 不是必需的(@see

auth.ldapAuthentication()
        .userDetailsContextMapper(userDetailsContextMapper())
        .userDnPatterns("cn={0},ou=Users")
        .groupSearchBase("ou=Roles") 
        .groupSearchFilter("(&(objectClass=groupOfUniqueNames)(uniqueMember={0}))")
        .contextSource()
        .url("ldap://127.0.0.1:389/dc=example,dc=org")
        .managerDn("cn=admin,dc=example,dc=org")
        .managerPassword("admin");