Spring Security LDAP配置单元测试编写及空指针问题求助
Spring Security LDAP认证配置单元测试空指针问题解决指导
我是Spring Security新手,以下是我的LDAP认证配置代码:
@Override protected void configure(AuthenticationManagerBuilder authBuilder) throws Exception { if (ldap) { authBuilder.ldapAuthentication() .userDetailsContextMapper(customUserDetailsCxtMapper()) .userSearchFilter("(&(objectClass=user)(" + userDnPatterns + "))") .contextSource() .url(ldapProviderUrl + ldapBaseDn.trim().replaceAll(" ", "%20")) .managerDn(ldapSecurityPrincipal) .managerPassword(ldapPrincipalPassword); } else { super.configure(auth); } } @Bean public UserDetailsContextMapper customUserDetailsContextMapper() { return new UserDetailsContextMapper() { @Override public UserDetails mapUserFromContext(DirContextOperations context, String uName, Collection<? extends GrantedAuthority> authorities) { ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>(); Collection<? extends GrantedAuthority> authorities = extractAuthorityFromMemberOf(context); grantedAuthorities.addAll(authorities); UserDetailsContextMapper contextMapper = new LdapUserDetailsMapper(); return contextMapper.mapUserFromContext(context, uName, grantedAuthorities); } @Override public void mapUserToContext(UserDetails userDetails, DirContextAdapter dirContextAdapter) { } }; }
我想编写单元测试验证该LDAP配置,但执行userDetailsContextMapper(customUserDetailsCxtMapper())时总是出现空指针,不清楚如何mockuserDetailsContextMapper(customUserDetailsCxtMapper()).userSearchFilter("(&(objectClass=user)(" + userDnPatterns + "))").contextSource()这些代码,寻求解决指导。
1. 先修正代码中的潜在问题
你的customUserDetailsContextMapper()方法存在变量重定义问题:方法参数的authorities和内部声明的authorities重名,这会导致逻辑混乱,先修正这个问题:
@Bean public UserDetailsContextMapper customUserDetailsContextMapper() { return new UserDetailsContextMapper() { @Override public UserDetails mapUserFromContext(DirContextOperations context, String uName, Collection<? extends GrantedAuthority> authorities) { ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>(); // 重命名变量避免冲突 Collection<? extends GrantedAuthority> ldapAuthorities = extractAuthorityFromMemberOf(context); grantedAuthorities.addAll(ldapAuthorities); UserDetailsContextMapper contextMapper = new LdapUserDetailsMapper(); return contextMapper.mapUserFromContext(context, uName, grantedAuthorities); } @Override public void mapUserToContext(UserDetails userDetails, DirContextAdapter dirContextAdapter) { } }; }
2. 优先选择集成测试(避免复杂Mock)
LDAP配置涉及多组件协作,纯单元测试Mock成本高,推荐用嵌入式LDAP服务器做集成测试,更贴近真实场景:
测试依赖准备
确保测试包中包含:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.unboundid</groupId> <artifactId>unboundid-ldapsdk</artifactId> <scope>test</scope> </dependency>
编写集成测试类
@SpringBootTest @EnableConfigurationProperties public class LdapAuthenticationConfigTest { @Autowired private AuthenticationManager authenticationManager; private InMemoryDirectoryServer ldapServer; @BeforeEach void setupLdapServer() throws Exception { // 初始化嵌入式LDAP服务器,添加测试用户和权限数据 ldapServer = new InMemoryDirectoryServer("dc=example,dc=com"); ldapServer.add("ou=users,dc=example,dc=com", "objectClass", "organizationalUnit"); ldapServer.add("cn=testUser,ou=users,dc=example,dc=com", "objectClass", "user", "sAMAccountName", "testUser", "userPassword", "testPass", "memberOf", "cn=adminGroup,ou=groups,dc=example,dc=com"); ldapServer.startListening(33389); // 同步配置类中的LDAP地址为嵌入式服务器地址 System.setProperty("ldap.provider.url", "ldap://localhost:33389/"); } @AfterEach void shutdownLdapServer() { ldapServer.shutDown(true); } @Test void testLdapAuthenticationSuccess() throws AuthenticationException { // 构造认证请求 Authentication authRequest = UsernamePasswordAuthenticationToken.unauthenticated("testUser", "testPass"); // 执行认证 Authentication authResult = authenticationManager.authenticate(authRequest); // 验证认证结果 assert authResult.isAuthenticated(); assert authResult.getName().equals("testUser"); // 验证权限是否正确提取 assert authResult.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN")); } }
3. 纯单元测试方案(Mock链式调用)
如果一定要做纯单元测试,需要逐层Mock链式调用的返回对象,避免空指针:
@ExtendWith(MockitoExtension.class) public class LdapAuthenticationConfigUnitTest { @InjectMocks private YourSecurityConfig securityConfig; // 替换成你的实际配置类名 @Mock private AuthenticationManagerBuilder authBuilder; @Mock private LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapConfigurer; @Mock private ContextSourceBuilder contextSourceBuilder; @BeforeEach void setupMockChains() { // 配置链式调用的Mock返回值,确保每一步都不返回null when(authBuilder.ldapAuthentication()).thenReturn(ldapConfigurer); when(ldapConfigurer.userDetailsContextMapper(any(UserDetailsContextMapper.class))).thenReturn(ldapConfigurer); when(ldapConfigurer.userSearchFilter(anyString())).thenReturn(ldapConfigurer); when(ldapConfigurer.contextSource()).thenReturn(contextSourceBuilder); when(contextSourceBuilder.url(anyString())).thenReturn(contextSourceBuilder); when(contextSourceBuilder.managerDn(anyString())).thenReturn(contextSourceBuilder); when(contextSourceBuilder.managerPassword(anyString())).thenReturn(contextSourceBuilder); } @Test void testConfigureLdapAuthentication() throws Exception { // 开启LDAP开关 securityConfig.setLdap(true); // 假设你的配置类有ldap属性的setter方法 // 调用配置方法 securityConfig.configure(authBuilder); // 验证所有配置方法都被正确调用 verify(authBuilder).ldapAuthentication(); verify(ldapConfigurer).userDetailsContextMapper(any(UserDetailsContextMapper.class)); verify(ldapConfigurer).userSearchFilter("(&(objectClass=user)(your-dn-pattern))"); // 替换成实际的userDnPatterns值 verify(contextSourceBuilder).url(anyString()); verify(contextSourceBuilder).managerDn(anyString()); verify(contextSourceBuilder).managerPassword(anyString()); } }
关键注意点
- 空指针的核心原因是Mock链式调用时,某一步返回了null,导致后续方法调用失败,必须确保每一步链式调用的Mock返回值都正确设置。
- 集成测试能更真实地验证LDAP认证的完整流程,包括用户信息、权限提取等逻辑,比纯单元测试更有价值。
- 检查配置类中的
ldap、userDnPatterns等配置属性,确保测试环境中这些属性被正确注入,避免因属性为null导致搜索过滤器拼接出错。
内容的提问来源于stack exchange,提问作者cNgamba
相关产品推荐
相关产品推荐

