LDAPと統合されたSpring Bootを使用してアプリを構築しています。 LDAPサーバーに正常に接続し、ユーザーを認証することができました。今、remember-me機能を追加する必要があります。別の投稿( this )を調べてみましたが、私の問題に対する答えを見つけることができませんでした。公式の春のセキュリティ ドキュメント は、
UserDetailsServiceを使用しない認証プロバイダー(LDAPプロバイダーなど)を使用している場合は、アプリケーションコンテキストにUserDetailsService Beanがない限り、機能しません。
ここに、remember-me機能を追加するためのいくつかの最初の考えを含む私の作業コード:
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) {
}
}
どういうわけかUserServiceを実装する必要があることはわかっていますが、それを実現する方法はわかりません。
LDAPでのRememberMe機能の構成には、2つの問題があります。
私はこれらを一歩ずつやっていきます。
トークンベースの記憶機能(TokenBasedRememberMeServices
)は、認証中に次のように機能します。
ユーザーがサービスに戻って、remember me機能を使用して認証されることを望む場合、次のようにします。
ハッシュチェックプロセスは、誰も「偽の」覚えているcookieを作成できないようにするために必要です。これにより、他のユーザーになりすますことができます。問題は、このプロセスがリポジトリからパスワードをロードする可能性に依存していることです。ただし、これはActive Directoryでは不可能です。ユーザー名に基づいてプレーンテキストのパスワードをロードすることはできません。
これにより、トークンベースの実装がADでの使用に不適切になります(パスワードまたはその他の秘密のユーザーベースの資格情報を含むローカルユーザーストアの作成を開始しない限り、私は他の詳細がわからないため、このアプローチを提案しません。あなたのアプリケーション、それは行くのに良い方法かもしれませんが)。
もう1つは、実装が永続的なトークン(PersistentTokenBasedRememberMeServices
)に基づいていることを思い出してください。
ユーザーが認証を希望する場合:
ご覧のとおり、パスワードは不要になりましたが、パスワード検証の代わりに使用されるトークンストレージ(通常はデータベース、テストにはインメモリを使用できます)が必要になりました。
これで、構成部分に進みます。永続的なトークンベースの基本的な構成は、次のようになっています。
@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
は、remember me cookieからロードされたユーザーIDによって識別されるユーザーの追加データのロードを担当します。最も単純な実装は次のようになります。
public class BasicRememberMeUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, "", Collections.<GrantedAuthority>emptyList());
}
}
必要に応じて、ADまたは内部データベースから追加の属性またはグループメンバーシップをロードする別のUserDetailsService
実装を提供することもできます。次のようになります。
@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で機能し、SecurityContextHolder.getContext().getAuthentication()
で利用できるRememberMeAuthenticationToken
内にロードされたデータを提供する機能を覚えていただけます。また、LDAPデータを解析してユーザーオブジェクト(CustomUserDetailsServiceImpl
)にするための既存のロジックを再利用することもできます。
別の主題として、質問に投稿されたコードにも1つの問題があります。
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService())
;
と:
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
;
UserDetailsServiceの呼び出しは、DAOベースの認証(データベースなど)を追加するためにのみ行う必要があり、ユーザー詳細サービスの実際の実装で呼び出す必要があります。現在の構成では、無限ループが発生する可能性があります。
UserService
への参照が必要なRememberMeService
のインスタンスがないようです。 LDAPを使用しているため、LDAPバージョンのUserService
が必要になります。私はJDBC/JPA実装に精通しているだけですが、org.springframework.security.ldap.userdetails.LdapUserDetailsManager
があなたが探しているもののようです。次に、設定は次のようになります。
@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;
}