Spring安全切换到Ldap身份validation和数据库权限

我为我的网页和Web服务实现了数据库身份validation。 它适用于两者,现在我必须添加Ldap身份validation。 我必须通过远程Ldap服务器进行身份validation(使用用户名和密码),如果用户存在,我必须使用我的数据库作为用户角色(在我的数据库用户名中是与Ldap相同的用户名)。 所以我必须从我的实际代码切换到Ldap和数据库身份validation,如上所述。 我的代码是:SecurityConfig类

@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("userDetailsService") UserDetailsService userDetailsService; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder(){ PasswordEncoder encoder = new BCryptPasswordEncoder(); return encoder; } @Configuration @Order(1) public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .antMatcher("/client/**") .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); } } @Configuration @Order(2) public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override public void configure(WebSecurity web) throws Exception { web //Spring Security ignores request to static resources such as CSS or JS files. .ignoring() .antMatchers("/static/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() //Authorize Request Configuration //the / and /register path are accepted without login //.antMatchers("/", "/register").permitAll() //the /acquisition/** need admin role //.antMatchers("/acquisition/**").hasRole("ADMIN") //.and().exceptionHandling().accessDeniedPage("/Access_Denied"); //all the path need authentication .anyRequest().authenticated() .and() //Login Form configuration for all others .formLogin() .loginPage("/login") //important because otherwise it goes in a loop because login page require authentication and authentication require login page .permitAll() .and() .logout() .logoutSuccessUrl("/login?logout") .permitAll(); // CSRF tokens handling } } 

MyUserDetailsS​​ervice类

 @Service("userDetailsService") public class MyUserDetailsService implements UserDetailsService { @Autowired private UserServices userServices; static final Logger LOG = LoggerFactory.getLogger(MyUserDetailsService.class); @Transactional(readOnly=true) @Override public UserDetails loadUserByUsername(final String username){ try{ com.domain.User user = userServices.findById(username); if (user==null) LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : User doesn't exist" ); else{ List authorities = buildUserAuthority(user.getUserRole()); return buildUserForAuthentication(user, authorities); } }catch(Exception e){ LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : " + ErrorExceptionBuilder.buildErrorResponse(e)); } return null; } // Converts com.users.model.User user to // org.springframework.security.core.userdetails.User private User buildUserForAuthentication(com.domain.User user, List authorities) { return new User(user.getUsername(), user.getPassword(), user.isEnabled(), true, true, true, authorities); } private List buildUserAuthority(Set userRoles) { Set setAuths = new HashSet(); // Build user's authorities for (UserRole userRole : userRoles) { setAuths.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole())); } List Result = new ArrayList(setAuths); return Result; } 

所以我必须要:

1)用户从登录页面访问网页以及用户名和密码进行Web服务。 这必须通过Ldap来完成。

2)用户需要数据库查询来validation用户的用户名。 你知道我怎么能实现这个吗? 谢谢

使用正确代码进行更新 :关注@M。 Deinum建议我创建MyAuthoritiesPopulator类而不是MyUserDetailsService并使用数据库和Ldap进行身份validation:

  @Service("myAuthPopulator") public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator { @Autowired private UserServices userServices; static final Logger LOG = LoggerFactory.getLogger(MyAuthoritiesPopulator.class); @Transactional(readOnly=true) @Override public Collection getGrantedAuthorities(DirContextOperations userData, String username) { Set authorities = new HashSet(); try{ com.domain.User user = userServices.findById(username); if (user==null) LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : User doesn't exist into ATS database" ); else{ for(UserRole userRole : user.getUserRole()) { authorities.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole())); } return authorities; } }catch(Exception e){ LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : " + ErrorExceptionBuilder.buildErrorResponse(e)); } return authorities; } } 

我更改了SecurityConfig,如下所示:

 @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("myAuthPopulator") LdapAuthoritiesPopulator myAuthPopulator; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.ldapAuthentication() .contextSource() .url("ldap://127.0.0.1:10389/dc=example,dc=com") // .managerDn("") // .managerPassword("") .and() .userSearchBase("ou=people") .userSearchFilter("(uid={0})") .ldapAuthoritiesPopulator(myAuthPopulator); } @Configuration @Order(1) public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .antMatcher("/client/**") .authorizeRequests() //Excluede send file from authentication because it doesn't work with spring authentication //TODO add java authentication to send method .antMatchers(HttpMethod.POST, "/client/file").permitAll() .anyRequest().authenticated() .and() .httpBasic(); } } @Configuration @Order(2) public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override public void configure(WebSecurity web) throws Exception { web //Spring Security ignores request to static resources such as CSS or JS files. .ignoring() .antMatchers("/static/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() //Authorize Request Configuration //the "/" and "/register" path are accepted without login //.antMatchers("/", "/register").permitAll() //the /acquisition/** need admin role //.antMatchers("/acquisition/**").hasRole("ADMIN") //.and().exceptionHandling().accessDeniedPage("/Access_Denied"); //all the path need authentication .anyRequest().authenticated() .and() //Login Form configuration for all others .formLogin() .loginPage("/login") //important because otherwise it goes in a loop because login page require authentication and authentication require login page .permitAll() .and() .logout() .logoutSuccessUrl("/login?logout") .permitAll(); } } } 

我在Apache directory studio中创建的LDAP开发环境

LDAP

Spring Security已经支持LDAP开箱即用。 它实际上有一整章 。

要使用和配置LDAP,请添加spring-security-ldap依赖项,然后使用AuthenticationManagerBuilder.ldapAuthentication进行配置。 LdapAuthenticationProviderConfigurer允许您设置所需的东西。

 @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.ldapAuthentication() .contextSource() .url(...) .port(...) .managerDn(...) .managerPassword(...) .and() .passwordEncoder(passwordEncoder()) .userSearchBase(...) .ldapAuthoritiesPopulator(new UserServiceLdapAuthoritiesPopulater(this.userService)); } 

这样的东西(它应该至少给你一个关于什么/如何配置东西的想法)有更多选项,但检查javadocs。 如果您不能按原样使用UserService来检索角色(因为只有角色在数据库中),那么为此实现您自己的LdapAuthoritiesPopulator

您需要创建一个实现AuthenticationProviderCustomAuthenticationProvider ,并覆盖authenticate方法,例如:

 @Component public class CustomAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); boolean authenticated = false; /** * Here implements the LDAP authentication * and return authenticated for example */ if (authenticated) { String usernameInDB = ""; /** * Here look for username in your database! * */ List grantedAuths = new ArrayList<>(); grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER")); Authentication auth = new UsernamePasswordAuthenticationToken(usernameInDB, password, grantedAuths); return auth; } else { return null; } } @Override public boolean supports(Class authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } } 

然后,在SecurityConfig中 ,您需要覆盖使用AuthenticationManagerBuilder 的配置

 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(this.authenticationProvider); } 

您可以自动assemblyCustomAuthenticationProvider:

 @Autowired private CustomAuthenticationProvider authenticationProvider; 

执行此操作,您可以覆盖默认的身份validation行为。

我还在本章中找到了Spring Docu Custom Authenicator,并在LDAP和我的数据库用户之间构建了自己的切换。 我可以毫不费力地在设置优先级的登录数据之间切换(在我的情况下, LDAP获胜)。

我已经为LDAP用户数据配置了一个带有yaml配置文件的LDAP,我在这里没有详细介绍。 这可以通过Spring Document LDAP配置轻松完成。

我从咔嗒声中删除了以下示例,例如logger / javadoc等,以突出显示重要部分。 @Order注释确定使用登录数据的优先级。 内存中的详细信息是硬编码的调试用户,仅用于开发目的。

SecurityWebConfiguration

 @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Inject private Environment env; @Inject private LdapConfiguration ldapConfiguration; @Inject private BaseLdapPathContextSource contextSource; @Inject private UserDetailsContextMapper userDetailsContextMapper; @Inject private DBAuthenticationProvider dbLogin; @Inject @Order(10) // the lowest number wins and is used first public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(new InMemoryUserDetailsManager(getInMemoryUserDetails())); } @Inject @Order(11) // the lowest number wins and is used first public void configureLDAP(AuthenticationManagerBuilder auth) throws Exception { if (ldapConfiguration.isLdapEnabled()) { auth.ldapAuthentication().userSearchBase(ldapConfiguration.getUserSearchBase()) .userSearchFilter(ldapConfiguration.getUserSearchFilter()) .groupSearchBase(ldapConfiguration.getGroupSearchBase()).contextSource(contextSource) .userDetailsContextMapper(userDetailsContextMapper); } } @Inject @Order(12) // the lowest number wins and is used first public void configureDB(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(dbLogin); } } 

DB身份validation器

 @Component public class DBAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String name = authentication.getName(); String password = authentication.getCredentials().toString(); // your code to compare to your DB } @Override public boolean supports(Class authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } /** * @param original mandatory - input to be hashed with SHA256 and HEX encoding * @return the hashed input */ private String sha256(String original) { MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new AuthException("The processing of your password failed. Contact support."); } if (false == Strings.isNullOrEmpty(original)) { md.update(original.getBytes()); } byte[] digest = md.digest(); return new String(Hex.encodeHexString(digest)); } private class AuthException extends AuthenticationException { public AuthException(final String msg) { super(msg); } } } 

随意询问细节。 我希望这对其他人有用:D

对于任何使用grails的人来说,它都要简单得多。 只需将其添加到您的配置:

grails:plugin:springsecurity:ldap:authorities:retrieveDatabaseRoles:true