Spring Security LDAP和记住我 [英] Spring Security LDAP and Remember Me

查看:126
本文介绍了Spring Security LDAP和记住我的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm building an app with Spring Boot that has integration with LDAP. I was able to connect successfully to LDAP server and authenticate user. Now I have a requirement to add remember-me functionality. I tried to look through different posts (this) but was not able to find an answer to my problem. Official Spring Security document states that

如果您使用的身份验证提供程序不使用 UserDetailsS​​ervice(例如LDAP提供程序),则它将无法正常工作 除非您的应用程序中还有一个UserDetailsS​​ervice bean 上下文

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

这是我的工作代码,带有一些初步想法,可以添加记住我"功能:

Here the my working code with some initial thoughts to add remember-me functionality:

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();
    }
}

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,但不确定如何实现.

I know that I need to implement UserService somehow, but not sure how that can be achieved.

推荐答案

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

There are two issues to configuration of the RememberMe features with LDAP:

  • 选择正确的RememberMe实现(令牌与PersistentTokens)
  • 使用Spring的Java配置对其进行配置

我将逐步进行这些操作.

I'll take these step by step.

基于令牌的记住我"功能(TokenBasedRememberMeServices)在身份验证期间以以下方式工作:

The Token-based remember me feature (TokenBasedRememberMeServices) works in the following way during authentication:

  • 用户通过身份验证(再次获得AD),我们目前知道用户的ID和密码
  • 我们构造值用户名+到期时间+密码+ staticKey并为其创建一个MD5哈希值
  • 我们创建一个包含用户名+到期时间+计算出的哈希值的Cookie

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

When user wants to come back to the service and be authenticated using the remember me functionality we:

  • 检查cookie是否存在且未过期
  • 从cookie中填充用户ID,然后调用提供的UserDetailsS​​ervice,该服务应返回与用户ID相关的信息,其中包括密码
  • 然后我们根据返回的数据计算哈希值,并验证Cookie中的哈希值是否与我们计算出的值匹配
  • 如果匹配,则返回用户的Authentication对象
  • check whether the cookie exists and isn't expired
  • populate the user ID from the cookie and call the provided UserDetailsService which is expected to return information related to the user's ID, including the password
  • we then calculate the hash from the returned data and verify that the hash in the cookie matches with the value we calculated
  • if it matches we return the user's Authentication object

需要进行哈希检查过程,以确保没有人可以创建伪造的"记住我的cookie,这将使他们冒充其他用户.问题在于,此过程依赖于从我们的存储库加载密码的可能性-但这对于Active Directory是不可能的-我们无法基于用户名加载明文密码.

The hash checking process is required in order to make sure that nobody can create a "fake" remember me cookie, which would let them impersonate another user. The problem is that this process relies on possibility of loading password from our repository - but this is impossible with Active Directory - we cannot load plaintext password based on username.

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

This makes the Token-based implementation unsuitable for usage with AD (unless we start creating some local user store which contains the password or some other secret user-based credential and I'm not suggesting this approach as I don't know other details of your application, although it might be a good way to go).

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

The other remember me implementation is based on persistent tokens (PersistentTokenBasedRememberMeServices) and it works like this (in a bit simplified way):

  • 当用户进行身份验证时,我们会生成一个随机令牌
  • 我们将令牌与有关与其关联的用户ID的信息一起存储在存储器中
  • 我们创建一个包含令牌ID的cookie

当用户想要进行身份验证时,我们:

When user wants to authenticate we:

  • 检查我们是否具有带有令牌ID的cookie
  • 验证令牌ID是否存在于数据库中
  • 根据数据库中的信息加载用户数据

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

As you can see, the password is no longer required, although we now need a token storage (typically database, we can use in-memory for testing) which is used instead of the password verification.

然后进入配置部分.基于持久令牌的基本配置记住我,像这样:

And that gets us to the configuration part. The basic configuration for persistent-token-based remember me looks like this:

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

此实现将使用内存令牌存储,应将其替换为JdbcTokenRepositoryImpl进行生产.提供的UserDetailsService负责为用户加载其他数据,该数据由从记住我" cookie加载的用户ID标识.最简单的实现如下所示:

This implementation will use in-memory token storage which should be replaced with JdbcTokenRepositoryImpl for production. The provided UserDetailsService is responsible for loading of additional data for the user identified by the user ID loaded from the remember me cookie. The simpliest implementation can look like this:

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

您还可以提供另一个UserDetailsService实现,根据需要从AD或内部数据库加载其他属性或组成员身份.看起来可能像这样:

You could also supply another UserDetailsService implementation which loads additional attributes or group memberships from your AD or internal database, depending on your needs. It could look like this:

@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).

This will get you remember me functionality which works with LDAP and provides the loaded data inside RememberMeAuthenticationToken which will be available in the SecurityContextHolder.getContext().getAuthentication(). It will also be able to re-use your existing logic for parsing of LDAP data into an User object (CustomUserDetailsServiceImpl).

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

As a separate subject, there's also one problem with the code posted in the question, you should replace the:

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

具有:

    authManagerBuilder
            .authenticationProvider(activeDirectoryLdapAuthenticationProvider())
    ;

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

The call to userDetailsService should only be made in order to add DAO-based authentication (e.g. against database) and should be called with a real implementation of the user details service. Your current configuration can lead to infinite loops.

这篇关于Spring Security LDAP和记住我的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆