java - conectar - spring security ldap authentication provider
Spring Security LDAP y Recordarme (2)
Hay dos problemas para la configuración de las características de RememberMe con LDAP:
- selección de la implementación correcta de RememberMe (Tokens vs. PersistentTokens)
- su configuración usando Spring''s Java Configuration
Tomaré estos paso por paso.
La función recordarme basada en Token ( TokenBasedRememberMeServices
) funciona de la siguiente manera durante la autenticación:
- el usuario se autentica (a excepción de AD) y actualmente sabemos la identificación y contraseña del usuario
- construimos valor username + expirationTime + password + staticKey y creamos un hash MD5 de él
- creamos una cookie que contiene nombre de usuario + vencimiento + el hash calculado
Cuando el usuario desea volver al servicio y autenticarse mediante la función recordarme, nosotros:
- verificar si la cookie existe y no ha expirado
- rellene el ID de usuario de la cookie y llame al UserDetailsService proporcionado, que se espera que devuelva información relacionada con la identificación del usuario, incluida la contraseña
- luego calculamos el hash de los datos devueltos y verificamos que el hash en la cookie coincida con el valor que calculamos
- si coincide, devolvemos el objeto de Autenticación del usuario
El proceso de comprobación de hash es necesario para garantizar que nadie pueda crear una cookie de recuerdo "falso", lo que les permitiría suplantar a otro usuario. El problema es que este proceso depende de la posibilidad de cargar la contraseña desde nuestro repositorio, pero esto es imposible con Active Directory. No podemos cargar la contraseña de texto plano en base al nombre de usuario.
Esto hace que la implementación basada en Tokens no sea adecuada para su uso con AD (a menos que comencemos a crear una tienda local de usuarios que contenga la contraseña o alguna otra credencial secreta basada en el usuario y no sugiero este enfoque ya que no conozco otros detalles de su aplicación, aunque podría ser una buena forma de hacerlo).
La otra me recuerda que la implementación se basa en tokens persistentes ( PersistentTokenBasedRememberMeServices
) y funciona así (de una manera un poco simplificada):
- cuando el usuario se autentica, generamos un token aleatorio
- almacenamos el token en el almacenamiento junto con información sobre el ID del usuario asociado con él
- creamos una cookie que incluye la ID del token
Cuando el usuario quiere autenticarse, nosotros:
- verificar si tenemos la cookie con la identificación de token disponible
- verificar si la ID del token existe en la base de datos
- cargar los datos del usuario en función de la información en la base de datos
Como puede ver, la contraseña ya no es necesaria, aunque ahora necesitamos un token de almacenamiento (normalmente una base de datos, que podemos usar en la memoria para probar) que se usa en lugar de la verificación de la contraseña.
Y eso nos lleva a la parte de configuración. La configuración básica para remember-token-based remember me se ve así:
@Override
protected void configure(HttpSecurity http) throws Exception {
....
String internalSecretKey = "internalSecretKey";
http.rememberMe().rememberMeServices(rememberMeServices(internalSecretKey)).key(internalSecretKey);
}
@Bean
public RememberMeServices rememberMeServices(String internalSecretKey) {
BasicRememberMeUserDetailsService rememberMeUserDetailsService = new BasicRememberMeUserDetailsService();
InMemoryTokenRepositoryImpl rememberMeTokenRepository = new InMemoryTokenRepositoryImpl();
PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices(staticKey, rememberMeUserDetailsService, rememberMeTokenRepository);
services.setAlwaysRemember(true);
return services;
}
Esta implementación utilizará el almacenamiento de token en memoria que debe reemplazarse con JdbcTokenRepositoryImpl
para producción. El UserDetailsService
proporcionado es responsable de la carga de datos adicionales para el usuario identificado por la ID de usuario cargada desde la cookie recordarme. La implementación más simple puede verse así:
public class BasicRememberMeUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, "", Collections.<GrantedAuthority>emptyList());
}
}
También podría suministrar otra implementación de UserDetailsService
que cargue atributos adicionales o membresías de grupos desde su AD o base de datos interna, según sus necesidades. Podría verse así:
@Bean
public RememberMeServices rememberMeServices(String internalSecretKey) {
LdapContextSource ldapContext = getLdapContext();
String searchBase = "OU=Users,DC=test,DC=company,DC=com";
String searchFilter = "(&(objectClass=user)(sAMAccountName={0}))";
FilterBasedLdapUserSearch search = new FilterBasedLdapUserSearch(searchBase, searchFilter, ldapContext);
search.setSearchSubtree(true);
LdapUserDetailsService rememberMeUserDetailsService = new LdapUserDetailsService(search);
rememberMeUserDetailsService.setUserDetailsMapper(new CustomUserDetailsServiceImpl());
InMemoryTokenRepositoryImpl rememberMeTokenRepository = new InMemoryTokenRepositoryImpl();
PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices(internalSecretKey, rememberMeUserDetailsService, rememberMeTokenRepository);
services.setAlwaysRemember(true);
return services;
}
@Bean
public LdapContextSource getLdapContext() {
LdapContextSource source = new LdapContextSource();
source.setUserDn("user@"+DOMAIN);
source.setPassword("password");
source.setUrl(URL);
return source;
}
Esto hará que recuerdes la funcionalidad que funciona con LDAP y proporciona los datos cargados dentro de RememberMeAuthenticationToken
que estarán disponibles en SecurityContextHolder.getContext().getAuthentication()
. También podrá reutilizar su lógica existente para analizar datos LDAP en un objeto User ( CustomUserDetailsServiceImpl
).
Como tema separado, también hay un problema con el código publicado en la pregunta, debe reemplazar el:
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService())
;
con:
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
;
La llamada a userDetailsService solo debe realizarse para agregar autenticación basada en DAO (por ejemplo, contra la base de datos) y debe invocarse con una implementación real del servicio de detalles del usuario. Su configuración actual puede conducir a bucles infinitos.
Estoy construyendo una aplicación con Spring Boot que tiene integración con LDAP. Pude conectarme con éxito al servidor LDAP y autenticar al usuario. Ahora tengo un requisito para agregar la funcionalidad de recordarme. Traté de revisar diferentes publicaciones ( this ) pero no pude encontrar una respuesta a mi problema. El document oficial de Spring Spring establece que
Si está utilizando un proveedor de autenticación que no utiliza un servicio UserDetailsService (por ejemplo, el proveedor LDAP), entonces no funcionará a menos que también tenga un bean UserDetailsService en su contexto de aplicación.
Aquí está mi código de trabajo con algunos pensamientos iniciales para agregar funcionalidad de recuerdo:
WebSecurityConfig
import com.ui.security.CustomUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.event.LoggerListener;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
String DOMAIN = "ldap-server.com";
String URL = "ldap://ds.ldap-server.com:389";
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/ui/**").authenticated()
.antMatchers("/", "/home", "/UIDL/**", "/ui/**").permitAll()
.anyRequest().authenticated()
;
http
.formLogin()
.loginPage("/login").failureUrl("/login?error=true").permitAll()
.and().logout().permitAll()
;
// Not sure how to implement this
http.rememberMe().rememberMeServices(rememberMeServices()).key("password");
}
@Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService())
;
}
@Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(DOMAIN, URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
return provider;
}
@Bean
public UserDetailsContextMapper userDetailsContextMapper() {
UserDetailsContextMapper contextMapper = new CustomUserDetailsServiceImpl();
return contextMapper;
}
/**
* Impl of remember me service
* @return
*/
@Bean
public RememberMeServices rememberMeServices() {
// TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("password", userService);
// rememberMeServices.setCookieName("cookieName");
// rememberMeServices.setParameter("rememberMe");
return rememberMeServices;
}
@Bean
public LoggerListener loggerListener() {
return new LoggerListener();
}
}
CustomUserDetailsServiceImpl
public class CustomUserDetailsServiceImpl implements UserDetailsContextMapper {
@Autowired
SecurityHelper securityHelper;
Log ___log = LogFactory.getLog(this.getClass());
@Override
public LoggedInUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> grantedAuthorities) {
LoggedInUserDetails userDetails = null;
try {
userDetails = securityHelper.authenticateUser(ctx, username, grantedAuthorities);
} catch (NamingException e) {
e.printStackTrace();
}
return userDetails;
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
Sé que tengo que implementar UserService de alguna manera, pero no estoy seguro de cómo se puede lograr.
Parece que le falta una instancia de UserService
que su servicio RememberMeService
necesita una referencia. Como está utilizando LDAP, necesitaría una versión LDAP de UserService
. Solo estoy familiarizado con las implementaciones JDBC / JPA, pero parece que org.springframework.security.ldap.userdetails.LdapUserDetailsManager
es lo que estás buscando. Entonces tu configuración se vería así:
@Bean
public UserDetailsService getUserDetailsService() {
return new LdapUserDetailsManager(); // TODO give it whatever constructor params it needs
}
@Bean
public RememberMeServices rememberMeServices() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("password", getUserDetailsService());
rememberMeServices.setCookieName("cookieName");
rememberMeServices.setParameter("rememberMe");
return rememberMeServices;
}