Подтвердить что ты не робот

Несколько механизмов аутентификации в одном приложении с помощью 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, и если нет токена сеанса, он должен просто перейти к текущему механизму аутентификации. Как добавить этот второй уровень аутентификации.

4b9b3361

Ответ 1

Я потратил довольно много времени, обворачивая голову вокруг spring -security при использовании чистой конфигурации java. Есть несколько шагов, связанных с тем, чтобы это работало. Это должно быть что-то в этом роде. Основной процесс следующий:

  • Создание настраиваемых фильтров для проверки запросов для конкретной информации о авторизации

  • Каждый фильтр возвращает null (если авторизация этого типа не найдена) или пользовательский AbstractAuthenticationToken

  • Если фильтр возвращает токен, каждый метод аутентификации AuthenticationProvider будет вызван с этим токеном, возвращающим true | false, если он должен попробовать аутентификацию

  • tryAuthentication будет вызываться на AuthenticationProvider, который поддерживает токен. Здесь вы выполняете любые служебные вызовы для аутентификации пользователя. Затем вы можете вывести LoginException или вызвать authentication.setAuthenticated(true) и вернуть токен для успешной аутентификации.

Я использую эту установку некоторое время, поддерживая различные методы аутентификации (подписанный запрос, имя пользователя/пароль, oauth и т.д.), и он работает очень хорошо.

Вы также можете передать AuthenticationSuccessHandler и AuthenticationFailuersHandler в пользовательские фильтры безопасности для предоставления настраиваемых стратегий перенаправления и обработки ошибок.

Также не забудьте настроить макеты ant в конструкторах фильтра, чтобы контролировать, какие шаблоны url применяют фильтры. Например, фильтр запросов ldap, вероятно, нужно будет проверять с помощью любого запроса "/*", тогда как фильтр имени пользователя/пароля можно просто проверить на POST для входа/входа или чего-то подобного.

Пример кода:

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 безопасность

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

Надеюсь, что это поможет!

Ответ 2

Еще один вариант добавления второго поставщика проверки подлинности: просто укажите другой параметр 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);
        }
      }
    }

Ответ 3

Я хочу просто добавить ответ mclema. Возможно, вам придется добавить переопределение для успешной проверки подлинности и продолжить цепочку фильтров, иначе пользователь будет перенаправлен на 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);
}