несколько механизмов аутентификации в одном приложении с помощью Java config
В настоящее время у меня есть один механизм аутентификации в моем приложении, который должен использовать LDAP для аутентификации и авторизации. Моя конфигурация безопасности выглядит так
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
@Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
@Value("${ldap-${env}.manager.dn}")
private String managerDn;
@Value("${ldap-${env}.manager.pass}")
private String managerPass;
@Value("${ldap-${env}.server.url}")
private String url;
@Value("${ldap.password.attribute:userPassword}")
private String passwordAttr;
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userDnPatterns("uid={0},ou=people").groupSearchBase("ou=groups")
.groupSearchFilter("(member={0})").userSearchBase("ou=people").userSearchFilter("(uid={0})")
.userDetailsContextMapper(new CustomLdapPersonContextMapper())
// .passwordCompare()
// .passwordAttribute(passwordAttr)
// .passwordEncoder(new PlaintextPasswordEncoder())
// .and()
.contextSource().managerDn(managerDn).managerPassword(managerPass).url(url);
}
}
}
существуют ситуации, когда пользователи могут войти с токеном сеанса, который может аутентифицировать с сервера ключей сеанса, а действительный токен возвращает имя пользователя, которое затем может использоваться для загрузки информации об аутентификации из LDAP для этого пользователя. Поэтому мой второй механизм аутентификации должен сначала, если токен сеанса присутствует в заголовках http, он должен выполнить аутентификацию токена, а затем поиск ldap, и если токен сеанса отсутствует, он должен просто попасть в текущий механизм аутентификации. Как я могу добавить этот второй уровень аутентификации.
4 ответов
Я потратил довольно много времени, оборачивая голову вокруг spring-security при использовании чистой конфигурации java. Чтобы это сработало, нужно сделать несколько шагов. Должно быть что-то в этом роде. Основной процесс заключается в следующем:
создание пользовательских фильтров для проверки запросов на конкретную информацию авторизации
каждый фильтр возвращает null (если авторизация этого типа не найдена) или пользовательский AbstractAuthenticationToken
если фильтр возвращает токен, каждый AuthenticationProvider поддерживает(класса) метод будет вызван с этой лексемой возвращая true|false, если он должен попробовать аутентификации
attemptAuthentication будет вызываться на AuthenticationProvider, который поддерживает токен. Здесь вы выполняете любые вызовы службы для аутентификации пользователя. Затем вы можете бросить LoginException или вызвать аутентификацию.setAuthenticated (true) и верните маркер для успешной аутентификации.
Я использую эту настройку некоторое время, поддерживая различные методы аутентификации (подписанный запрос, имя пользователя/пароль, oauth и т. д.), и он работает довольно хорошо.
вы также можете передать AuthenticationSuccessHandler и AuthenticationFailuersHandler в пользовательские фильтры безопасности для предоставления пользовательских стратегий перенаправления и обработки сбоев.
также не забудьте настроить Ant matchers в конструкторах фильтра для управления шаблонами url-адресов фильтры также применяются. Например, фильтр запроса ldap, вероятно, должен быть проверен с любым запросом"/*", тогда как фильтр имени пользователя/пароля может быть просто проверен на POST's to /login или что-то подобное.
Пример Кода:
1) Создайте пользовательские AuthenticationToken для каждого типа аутентификации, который вы хотите поддержать
public class LDAPAuthorizationToken extends AbstractAuthenticationToken {
private String token;
public LDAPAuthorizationToken( String token ) {
super( null );
this.token = token;
}
public Object getCredentials() {
return token;
}
public Object getPrincipal() {
return null;
}
}
public class OTPAuthorizationToken extends UsernamePasswordAuthenticationToken {
private String otp;
public OTPAuthorizationToken( String username, String password, String otp ) {
super( username, password );
this.otp = otp;
}
public String getOTP() {
return otp;
}
}
2) создание пользовательских фильтров безопасности для каждого тип
public class LDAPAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private UserDetailsService userDetailsService;
public LDAPAuthorizationFilter() {
super( "/*" ); // allow any request to contain an authorization header
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
{
if ( request.getHeader( "Authorization" ) == null ) {
return null; // no header found, continue on to other security filters
}
// return a new authentication token to be processed by the authentication provider
return new LDAPAuthorizationToken( request.getHeader( "Authorization" ) );
}
}
public class OTPAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private UserDetailsService userDetailsService;
public OTPAuthorizationFilter() {
super( "/otp_login" );
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
{
if ( request.getParameter( "username" ) == null || request.getParameter( "password" ) == null || request.getParameter( "otp" ) == null ) {
return null;
}
// return a new authentication token to be processed by the authentication provider
return new OTPAuthorizationToken( request.getParameter( "username" ), request.getParameter( "password" ), request.getParameter( "otp" ) );
}
}
3) создание пользовательских AuthenticationProviders
public class LDAPAuthenticationProvider implements AuthenticationProvider {
@Autowired
private MyAuthenticationService sampleService;
@Override
public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
LDAPAuthorizationToken auth = (LDAPAuthorizationToken)authentication;
String username = sampleService.verifyToken( auth.getCredentials() );
if ( username == null ) {
throw new LoginException( "Invalid Token" );
}
auth.setAuthenticated( true );
return auth;
}
@Override
public boolean supports( Class<?> authentication ) {
if ( authentication.isAssignableFrom( LDAPAuthorizationToken.class ) ) {
return true;
}
return false;
}
}
public class OTPAuthenticationProvider implements AuthenticationProvider {
@Autowired
private MyAuthenticationService sampleService;
@Override
public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
OTPAuthorizationToken auth = (OTPAuthorizationToken)authentication;
String error = sampleService.loginWithOTP( auth.getPrincipal(), auth.getCredentials(), auth.getOTP() );
if ( error != null ) {
throw new LoginException( error );
}
auth.setAuthenticated( true );
return auth;
}
@Override
public boolean supports( Class<?> authentication ) {
if ( authentication.isAssignableFrom( OTPAuthorizationToken.class ) ) {
return true;
}
return false;
}
}
4) Настройка spring security
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure( HttpSecurity http ) throws Exception {
// configure filters
http.addFilterBefore( new LDAPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );
http.addFilterBefore( new OTPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );
// configure authentication providers
http.authenticationProvider( new LDAPAuthenticationProvider() );
http.authenticationProvider( new OTPAuthenticationProvider() );
// disable csrf
http.csrf().disable();
// setup security
http.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and().httpBasic();
}
}
надеюсь, что это поможет!
другой, возможность добавить второй поставщик аутентификации: просто укажите другой на AuthenticationManagerBuilder
. Потому что @EnableWebSecurity
аннотация сама аннотируется с помощью EnableGlobalAuthentication
вы можете настроить глобальный экземпляр AuthenticationManagerBuilder
. (См. javadocs для получения более подробной информации.)
например, здесь у нас есть поставщик аутентификации LDAP, а также поставщик аутентификации в памяти (жестко закодированный) (это то, что мы делаем в разработке, чтобы иметь локальных пользователей для тестирования с):
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${user.role}")
private String userRole; // i.e. ROLE_APP_USER
@Value("${include.test.users}")
private boolean includeTestUsers;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**/js/**").permitAll()
.antMatchers("/**/images/**").permitAll()
.antMatchers("/**/favicon.ico").permitAll()
.antMatchers("/**/css/**").permitAll()
.antMatchers("/**/fonts/**").permitAll()
.antMatchers("/**").hasAnyRole(userRole)
.and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();
http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, LdapContextSource contextSource) throws Exception {
auth.ldapAuthentication()
.userSearchBase("OU=Users OU")
.userSearchFilter("sAMAccountName={0}")
.groupSearchBase("OU=Groups OU")
.groupSearchFilter("member={0}")
.contextSource(contextSource);
if (includeTestUsers) {
auth.inMemoryAuthentication().withUser("user").password("u").authorities(userRole);
}
}
}
Я хочу просто добавить к ответу маклемы. Возможно, Вам потребуется добавить переопределение для успешной аутентификации и продолжить цепочку фильтров, иначе пользователь будет перенаправлен на url по умолчанию ("/") вместо исходного (например: /myrest/server/somemethod)
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
SecurityContextHolder.setContext(context);
chain.doFilter(request, response);
}
принятый ответ имеет проблему, что текущий запрос не предоставлен ie. только для следующих запросов сессия устанавливается! Поэтому мне нужно было настроить в пункте 2
public class MyAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
public MyAuthorizationFilter() {
super( "/*" ); // allow any request to contain an authorization header
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
{
if ( request.getHeader( "Authorization" ) == null ) {
return null; // no header found, continue on to other security filters
}
// required to use the token
myNewToken = new MyAuthorizationToken( request.getHeader( "Authorization" ) );
// and set in the current context ==> the current request is as well authorized
SecurityContextHolder.getContext().setAuthentication(myNewToken);
// return a new authentication token to be processed by the authentication provider
return myNewToken;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// try to authenticate the current request
attemptAuthentication((HttpServletRequest) req, (HttpServletResponse) res);
super.doFilter(req, res, chain);
}
}
иначе текущий запрос еще не аутентифицирован, хотя сеанс уже создан!!! (И провайдеры мне не нужны, т. е. добавление фильтра достаточно.)