gpt4 book ai didi

java - Spring Security LDAP 和记住我

转载 作者:IT老高 更新时间:2023-10-28 13:46:55 65 4
gpt4 key购买 nike

我正在使用 Spring Boot 构建一个与 LDAP 集成的应用程序。我能够成功连接到 LDAP 服务器并验证用户身份。现在我需要添加记住我的功能。我试图浏览不同的帖子( this ),但无法找到我的问题的答案。官方 Spring Security document 指出

If you are using an authentication provider which doesn't use a UserDetailsService (for example, the LDAP provider) then it won't work unless you also have a UserDetailsService bean in your application context



这是我的工作代码以及一些添加记住我功能的初步想法:

网络安全配置
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();
}
}

CustomUserDetailsS​​erviceImpl
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) {

}
}

我知道我需要以某种方式实现 UserService,但不确定如何实现。

最佳答案

使用 LDAP 配置 RememberMe 功能有两个问题:

  • 选择正确的 RememberMe 实现( token 与 PersistentTokens)
  • 它的配置使用 Spring 的 Java Configuration

  • 我会一步一步来。

    基于 token 的记住我功能 ( TokenBasedRememberMeServices ) 在身份验证期间以下列方式工作:
  • 用户通过身份验证(反对 AD),我们目前知道用户的 ID 和密码
  • 我们构造值 username + expireTime + password + staticKey 并为其创建 MD5 哈希
  • 我们创建了一个包含用户名 + 过期时间 + 计算出的哈希值的 cookie

  • 当用户想要返回服务并使用“记住我”功能进行身份验证时,我们:
  • 检查 cookie 是否存在且未过期
  • 从 cookie 中填充用户 ID 并调用提供的 UserDetailsS​​ervice,该服务应返回与用户 ID 相关的信息,包括密码
  • 然后我们根据返回的数据计算哈希值,并验证 cookie 中的哈希值是否与我们计算的值匹配
  • 如果匹配,我们返回用户的身份验证对象

  • 哈希检查过程是必需的,以确保没有人可以创建“假的”记住我的 cookie,这会让他们冒充另一个用户。问题是这个过程依赖于从我们的存储库加载密码的可能性——但是这对于 Active Directory 是不可能的——我们无法根据用户名加载纯文本密码。

    这使得基于 token 的实现不适合与 AD 一起使用(除非我们开始创建一些包含密码或其他一些基于用户的 secret 凭据的本地用户存储,我不建议这种方法,因为我不知道其他细节您的应用程序,尽管它可能是一个不错的方法)。

    另一个记住我的实现基于持久 token ( PersistentTokenBasedRememberMeServices ),它的工作方式如下(以稍微简化的方式):
  • 当用户进行身份验证时,我们会生成一个随机 token
  • 我们将 token 以及与其关联的用户 ID 信息一起存储在存储中
  • 我们创建了一个包含 token ID
  • 的 cookie

    当用户想要进行身份验证时,我们:
  • 检查我们是否有带有 token ID 的 cookie 可用
  • 验证数据库中是否存在 token ID
  • 根据数据库中的信息加载用户的数据

  • 如您所见,不再需要密码,尽管我们现在需要一个 token 存储(通常是数据库,我们可以使用内存进行测试)来代替密码验证。

    这让我们进入配置部分。基于持久 token 的记住我的基本配置如下所示:
    @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;
    }

    此实现将使用内存 token 存储,应替换为 JdbcTokenRepositoryImpl用于生产。提供的 UserDetailsService负责为由从记住我的 cookie 加载的用户 ID 标识的用户加载附加数据。最简单的实现如下所示:
    public class BasicRememberMeUserDetailsService implements UserDetailsService {
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return new User(username, "", Collections.<GrantedAuthority>emptyList());
    }
    }

    您也可以提供另一个 UserDetailsService根据您的需要,从您的 AD 或内部数据库加载其他属性或组成员身份的实现。它可能看起来像这样:
    @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;
    }

    这将使您记住我的功能,该功能可与 LDAP 一起使用并在 RememberMeAuthenticationToken 中提供加载的数据将在 SecurityContextHolder.getContext().getAuthentication() 中提供.它还可以重用您现有的逻辑来将 LDAP 数据解析为用户对象 ( CustomUserDetailsServiceImpl )。

    作为一个单独的主题,问题中发布的代码也存在一个问题,您应该替换:
        authManagerBuilder
    .authenticationProvider(activeDirectoryLdapAuthenticationProvider())
    .userDetailsService(userDetailsService())
    ;

    和:
        authManagerBuilder
    .authenticationProvider(activeDirectoryLdapAuthenticationProvider())
    ;

    对 userDetailsS​​ervice 的调用应该仅用于添加基于 DAO 的身份验证(例如针对数据库),并且应该使用用户详细信息服务的真实实现来调用。您当前的配置可能会导致无限循环。

    关于java - Spring Security LDAP 和记住我,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24745528/

    65 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com