当前位置: 首页 > 知识库问答 >
问题:

Spring SecurityLDAP和记住我

沈博延
2023-03-14

我正在和Spring Boot一起开发一个集成了LDAP的应用程序。我能够成功地连接到LDAP服务器并验证用户。现在,我需要添加“记住我”功能。我试图通过不同的职位(这),但无法找到我的问题的答案。官方的Spring安全文件指出

如果您正在使用不使用UserDetailsService的身份验证提供程序(例如,LDAP提供程序),那么它将不起作用,除非您的应用程序上下文中也有UserDetailsService bean

下面是我的工作代码,其中包含一些添加“记住我”功能的初步想法:

Web安全配置

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) {

    }
}

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

共有2个答案

裴学
2023-03-14

听起来你是失踪的一个实例<鳕鱼

@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;
}
谯英彦
2023-03-14

有两个问题需要Remem的配置

  • 选择正确的Memberme实现(令牌与PersistentToken)
  • 它的配置使用Spring的Java配置

我会一步一步来。

基于令牌的“记住我”功能(< code > tokenbasedrememberservices )在身份验证期间以以下方式工作:

  • 用户获得身份验证(agaisnt AD),我们目前知道用户的ID和密码
  • 我们构造值用户名过期时间密码静态密钥并创建它的MD5哈希
  • 我们创建一个cookie,其中包含用户名过期计算的哈希

当用户希望返回服务并使用“记住我”功能进行身份验证时,我们:

    < li >检查cookie是否存在并且没有过期 < li >从cookie中填充用户ID,并调用提供的UserDetailsService,该服务将返回与用户ID相关的信息,包括密码 < li >然后,我们根据返回的数据计算哈希,并验证cookie中的哈希与我们计算的值是否匹配 < li >如果匹配,我们返回用户的验证对象

哈希检查过程是必需的,以确保没有人能够创建“假的”remember me cookie,这将使他们能够冒充另一个用户。问题是,这个过程依赖于从我们的存储库加载密码的可能性——但这对于Active Directory是不可能的——我们不能基于用户名加载明文密码。

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

另一个记住我实现是基于持久令牌(PersistentTokenBasedRememberMeServices),它的工作原理是这样的(以一种简化的方式):

    < li >当用户进行身份验证时,我们会生成一个随机令牌 < li >我们将令牌和与之相关的用户ID信息一起存储在存储中 < li >我们创建一个包含令牌ID的cookie

当用户想要认证我们时:

    <李>检查我们是否有饼干

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

这就是我们的配置部分。基于持久令牌的“记住我”的基本配置如下所示:

@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;
 }

该实现将使用内存中的令牌存储器中

public class BasicRememberMeUserDetailsService implements UserDetailsService {
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         return new User(username, "", Collections.<GrantedAuthority>emptyList());
     }
}

您还可以提供另一个< code>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一起工作,并在< code > RememberMeAuthenticationToken 中提供加载的数据,该数据将在< code > security context holder . get context()中提供。getAuthentication()。它还能够重用您现有的逻辑来将LDAP数据解析为用户对象(< code > CustomUserDetailsServiceImpl )。

作为一个单独的主题,问题中发布的代码也存在一个问题,您应该替换:

    authManagerBuilder
            .authenticationProvider(activeDirectoryLdapAuthenticationProvider())
            .userDetailsService(userDetailsService())
    ;

:

    authManagerBuilder
            .authenticationProvider(activeDirectoryLdapAuthenticationProvider())
    ;

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

 类似资料:
  • 8.2 节实现的登录系统自成一体且功能完整,不过大多数网站还会提供一种功能——用户关闭浏览器后仍能记住用户的会话。本节,我们首先实现自动记住用户会话的功能,只有用户明确退出后会话才会失效。8.4.5 节实现另一种常用方式:提供一个“记住我”复选框,让用户选择是否记住会话。这两种方式都很专业,GitHub 和 Bitbucket 等网站使用第一种,Facebook 和 Twitter 等网站使用第二

  • 问题内容: 在AngularJS单页应用程序中使用ng-submit时,如何让浏览器要求用户记住密码。 我的表格: 有任何想法吗? 更新 我刚刚添加了一项操作,以使浏览器识别该表单并诱使它记住密码。(这显然不起作用。)如果不采取任何措施,该表格就可以正常工作。在防止动作的执行。只有做任何事情。 问题答案: 问题是动态生成的登录表单。将表单放入index.html后,它可以按预期工作。我猜这是一个安

  • 问题内容: 我正在寻找一种更好的方法来解决我的问题。我在登录表单上记得我的功能。用户单击“记住我”框,“我的API”向我发送令牌。 我的问题是,当用户返回我的网站时,存储此令牌并再次对其进行身份验证的最佳方法是什么? 我以为 创建一个Cookie并在其中存储令牌。 创建本地存储。 请给我任何可能对我有帮助的建议。 问题答案: 我将document.cookie与这样的工厂代码一起使用: 创建一个c

  • 问题内容: 我有一个登录页面,我想添加“记住我”功能;因此,如果用户注销并再次打开页面,则会加载其用户名和密码。为此,当用户登录(并选中“记住我”)时,我保存了以下cookie: 问题是稍后(在同一会话中)我阅读了cookie,然后看到maxAge = -1; 即使我将其设置为3600 …为什么呢?另一个问题:如果我使用userCookie.setSecure(true)将cookie设置为安全,

  • 问题内容: 为此,我想将硬币兑换功能转换为记忆功能 ,因此我决定使用字典,以便字典中的键将是硬币,而值将是包含所有可更改“钥匙”的硬币的列表。硬币。 我所做的是: 我想得到一些建议,或者也许有另一种方法可以做到这一点。 谢谢。 编辑 备注版本: 问题答案: 当您可以只使用通用的预先编写的装饰器时,不必编写专门的记忆装饰器。例如,直接来自 PythonDecoratorLibrary 的以下 代码