如何基于Spring Security LDAP自定义登录流程,实现创建用户会话前从数据库获取用户允许的区域代码列表
如何基于Spring Security LDAP自定义登录流程,实现创建用户会话前从数据库获取用户允许的区域代码列表
嗨,我来帮你搞定这个需求!其实Spring Security的LDAP登录流程预留了很灵活的扩展点,咱们要做的就是在用户通过LDAP认证后、会话正式创建前,插入自定义逻辑去数据库拉取区域权限列表,再把这些信息绑定到用户认证对象里。下面一步步来:
1. 扩展UserDetails,存储区域代码
默认的User类没有咱们需要的区域代码字段,所以先自定义一个CustomUserDetails来扩展它:
import org.springframework.security.core.userdetails.User; import java.util.List; public class CustomUserDetails extends User { private final List<String> regionCodes; public CustomUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, List<GrantedAuthority> authorities, List<String> regionCodes) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); this.regionCodes = regionCodes; } public List<String> getRegionCodes() { return regionCodes; } }
2. 自定义UserDetailsContextMapper,注入数据库查询逻辑
这个接口是Spring Security用来把LDAP返回的用户信息转换成UserDetails的关键入口,咱们在这里注入数据库服务,根据LDAP里的用户职位去查区域代码:
import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.DirContextOperations; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.ldap.userdetails.UserDetailsContextMapper; import org.springframework.stereotype.Component; import java.util.List; @Component public class CustomLdapUserDetailsMapper implements UserDetailsContextMapper { // 注入你的数据库服务,用来根据职位查区域代码 private final RegionCodeService regionCodeService; public CustomLdapUserDetailsMapper(RegionCodeService regionCodeService) { this.regionCodeService = regionCodeService; } @Override public UserDetails mapUserFromContext(DirContextOperations ctx, String username, List<GrantedAuthority> authorities) { // 从LDAP上下文获取用户的position属性(这里要和你LDAP里的属性名对应) String position = ctx.getStringAttribute("position"); // 调用数据库服务,根据职位获取允许的区域代码列表 List<String> allowedRegionCodes = regionCodeService.getAllowedCodesByPosition(position); // 构建自定义的UserDetails对象,把区域代码传进去 return new CustomUserDetails( username, // LDAP认证一般不需要明文密码,这里可以传空或者用LDAP返回的加密密码 "", true, true, true, true, authorities, allowedRegionCodes ); } @Override public void mapUserToContext(UserDetails user, DirContextAdapter ctx) { // 如果不需要把用户信息写回LDAP,这个方法留空就行 } }
3. 配置Spring Security,启用自定义的Mapper
接下来在SecurityFilterChain里配置LDAP认证时,指定咱们的自定义UserDetailsContextMapper:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { private final CustomLdapUserDetailsMapper customLdapUserDetailsMapper; // 注入LDAP上下文配置(根据你实际的LDAP服务器信息配置) private final BaseLdapPathContextSource contextSource; public SecurityConfig(CustomLdapUserDetailsMapper customLdapUserDetailsMapper, BaseLdapPathContextSource contextSource) { this.customLdapUserDetailsMapper = customLdapUserDetailsMapper; this.contextSource = contextSource; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .formLogin(form -> form.permitAll()) .ldapAuthentication(ldap -> ldap .userSearchFilter("(uid={0})") // 根据你LDAP的用户过滤规则调整 .userSearchBase("ou=users") // 用户所在的LDAP节点 .contextSource(contextSource) // 关键:指定咱们的自定义UserDetailsMapper .userDetailsContextMapper(customLdapUserDetailsMapper) ); return http.build(); } }
4. 后续使用区域代码
当用户登录成功后,你可以在任何需要的地方(比如控制器、服务类)获取用户的区域代码列表:
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DocumentController { private final DocumentService documentService; public DocumentController(DocumentService documentService) { this.documentService = documentService; } @GetMapping("/documents") public List<Document> getUserDocuments() { // 获取当前认证的用户对象 CustomUserDetails currentUser = (CustomUserDetails) SecurityContextHolder.getContext() .getAuthentication() .getPrincipal(); // 获取允许的区域代码 List<String> allowedRegions = currentUser.getRegionCodes(); // 根据区域代码查询文档 return documentService.getDocumentsByRegionCodes(allowedRegions); } }
一些注意事项
- 确保LDAP里的
position属性名和你代码里的getStringAttribute("position")一致,别写错了! - 数据库查询的异常要处理好,比如如果用户的职位在数据库里没有对应记录,你可以返回空列表或者抛出认证异常,根据你的业务需求来。
- 如果你的LDAP认证不需要密码校验(比如用证书或者其他方式),可以调整
CustomUserDetails的密码参数,不过一般LDAP登录会自动处理密码验证。
备注:内容来源于stack exchange,提问作者Mahdi
相关产品推荐
相关产品推荐

