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

Использование java-конфигурации для аутентификации n-фактора

В приложении spring mvc, использующем spring security, я хочу использовать AuthenticationProvider для проверки n-number > дополнительных полей за пределами стандартных username и password. Я пытаюсь использовать конфигурацию Java. Как мне настроить его?

4b9b3361

Ответ 1

Самый простой способ использования java-конфигурации для n-факторной аутентификации - начать с рабочего примера однофакторной аутентификации (имя пользователя и пароль), в котором используется java-конфиг. Затем вам нужно сделать лишь несколько незначительных изменений. Предполагая, что у вас есть однофакторное приложение для проверки подлинности с использованием java-конфигурации, шаги просто:

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

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http
        .csrf().disable()
        .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/getpin")
            .usernameParameter("j_username")
            .passwordParameter("j_password")
            .loginProcessingUrl("/j_spring_security_check")
            .failureUrl("/login")
            .permitAll()
            .and()
        .logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login")
            .and()
        .authorizeRequests()
            .antMatchers("/getpin").hasAuthority("get_pin")
            .antMatchers("/securemain/**").hasAuthority("full_access")
            .antMatchers("/j_spring_security_check").permitAll()
            .and()
        .userDetailsService(userDetailsService);
    }
}

Во-вторых, добавьте код, который обновляет роль пользователя до полного доступа при успешном вводе правильного пин-кода в код контроллера, который обрабатывает форму ввода пин-кода POST. Код для ручного назначения полного доступа в контроллере:

Role rl2 = new Role();rl2.setRole("full-access");//Don't save this one because we will manually assign it on login.
Set<Role> rls = new HashSet<Role>();
rls.add(rl2);
CustomUserDetailsService user = new CustomUserDetailsService(appService);
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities(rls));
SecurityContextHolder.getContext().setAuthentication(authentication);
return "redirect:/securemain";  

Вы можете добавить столько слоев, сколько хотите, после /getpin. Вы также можете поддерживать несколько ролей авторизации и сделать их такими же сложными, как вы хотите. Но этот ответ дает самый простой способ запустить его с помощью java config.

Ответ 2

Во-первых, некоторое объяснение об интерфейсах, с которыми вы работаете, и о роли, которую они играют в процессе аутентификации:

  • Authentication - представляет результат аутентификации пользователя. Владеет полномочиями, предоставленными этому пользователю, и любые дополнительные сведения, которые могут потребоваться пользователю. Поскольку для рамки не существует способа узнать, какие детали понадобятся, объект аутентификации имеет метод getDetails, который может возвращать любой объект

  • AuthenticationProvider - объект, который может каким-то образом создать объект Authentication. Чтобы сделать их более многоразовыми, некоторые (или большинство) из AuthenticationProvider воздерживаются от настройки данных пользователя на объекте Authentication, так как каждому приложению могут понадобиться конкретные данные пользователя. Вместо этого они делегируют процесс разрешения деталей пользователя на настраиваемый UserDetailsService

  • UserDetailsService - strategy для получения сведений о пользователе, необходимых в вашем приложении.

Итак, если вы создаете пользовательский AuthenticationProvider, вам может даже не понадобиться его реализовать таким образом, который требует UserDetailsService. Заключение зависит от вас и зависит от того, планируете ли вы повторное использование своей реализации в других проектах.

Что касается проблем компиляции в вашем коде, вы смешиваете два способа предоставления UserDetailsService. В CustomAuthenticationProvider вы аннотировали поле userService с аннотацией @Inject. Это означает, что контейнер (Spring контекст приложения в вашем случае) должен найти подходящую реализацию и ввести ее в это поле во время выполнения используя отражение. Процесс установки этого поля контекстом называется инъекцией зависимостей. В классе SecurityConfig вы пытаетесь обеспечить реализацию самостоятельно, установив поле через метод setUserDetailsService, который не существует в вашем классе.

Чтобы решить эту проблему, вам необходимо решить один из способов предоставления службы UserDetails и:

  • удалите аннотацию @Inject и создайте метод setUserDetailsService или
  • удалите строку, когда вы вызываете несуществующий метод, и объявите о своей реализации UserDetailsService как bean

В зависимости от того, какой из способов выбрать, путь внедрения dependecy может быть лучше, если вы сможете найти способ повторного использования класса SecurityConfig в других проектах. В этом случае вы можете просто импортировать его (используя антагонизм @Import) и объявить о другой реализации UserDetailsSerice как bean в следующем приложении и заставить ее работать.

Обычно классы, подобные SecurityConfig, на самом деле не могут повторно использоваться, поэтому создание сеттера и удаление инъекции зависимостей, вероятно, будет моим первым выбором.

ИЗМЕНИТЬ

Работая, хотя и упрощенная реализация (основанная на этом блоге ), будет выглядеть следующим образом:

public class CustomAuthenticationProvider implements AuthenticationProvider{

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String name = authentication.getName();
        String password = authentication.getCredentials().toString();
        List<GrantedAuthority> grantedAuths = new ArrayList<>();
        if (name.equals("admin") && password.equals("system")) {
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));  
        } 
        if(pincodeEntered(name)){
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_PINCODE_USER"));  
        }
        Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

    private boolean pincodeEntered(String userName){
        // do your check here
        return true;
    }
}

Затем в вашем классе конфигурации измените следующий метод:

@Bean
AuthenticationProvider customAuthenticationProvider() {
        return new CustomAuthenticationProvider();
}

Ответ 3

Первое, что нам нужно сделать, это расширить класс UsernamePasswordAuthenticationFilter, чтобы он мог обрабатывать второе поле ввода.

public class TwoFactorAuthenticationFilter extends UsernamePasswordAuthenticationFilter
{
    private String extraParameter = "extra";
    private String delimiter = ":";
    //getters and setters

    @Override
    protected String obtainUsername(HttpServletRequest request)
    {
        String username = request.getParameter(getUsernameParameter());
        String extraInput = request.getParameter(getExtraParameter());
        String combinedUsername = username + getDelimiter() + extraInput;
        return combinedUsername;
    }

}

getUsername() Этот метод предназначен для извлечения имени пользователя и дополнительного поля из объекта HttpServletRequest, который прошел.

Затем он объединяет эти два значения в одну строку, разделяя их по разделительной строке (двоеточие, по умолчанию).

Затем он возвращает эту объединенную строку. Параметр, из которого считывается "дополнительное" поле ввода, добавляется по умолчанию.

UserDetailsService должен выглядеть следующим образом:

@Override
public UserDetails loadUserByUsername(String input) throws UsernameNotFoundException, DataAccessException
{
    String[] split = input.split(":");
    if(split.length < 2)
    {
        throw new UsernameNotFoundException("Must specify both username and corporate domain");
    }

    String username = split[0];
    String domain = split[1];
    User user = userDao.findByUsernameAndDomain(username, domain);
    if(user == null)
    {
        throw new UsernameNotFoundException("Invalid username or corporate domain");
    }
    return user;
}

Разделите данное имя пользователя на два его компонента: имя пользователя и дополнительное поле. В этом примере дополнительным полем является корпоративный домен пользователей.

Как только у нас есть имя пользователя и домен, мы можем использовать наш DAO для поиска соответствующего пользователя.

Последняя головоломка:

TwoFactorAuthenticationFilter:

    <http use-expressions="true" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
        <intercept-url pattern="/secured" access="isAuthenticated()" />
        <intercept-url pattern="/**" access="permitAll" />
        <custom-filter position="FORM_LOGIN_FILTER" ref="twoFactorAuthenticationFilter" />
        <logout logout-url="/logout" />
    </http>

    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="authenticationProvider" />
    </authentication-manager>

    <beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <beans:property name="passwordEncoder">
            <beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />
        </beans:property>
        <beans:property name="userDetailsService" ref="userService" />
    </beans:bean>

    <beans:bean id="userService" class="com.awnry.springexample.UserDetailsServiceImpl" />

    <beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:property name="loginFormUrl" value="/login" />
    </beans:bean>

    <beans:bean id="twoFactorAuthenticationFilter" class="com.awnry.springexample.TwoFactorAuthenticationFilter">
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="authenticationFailureHandler" ref="failureHandler" />
        <beans:property name="authenticationSuccessHandler" ref="successHandler" />
        <beans:property name="filterProcessesUrl" value="/processLogin" />
        <beans:property name="postOnly" value="true" />
        <beans:property name="extraParameter" value="domain" />
    </beans:bean>

    <beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <beans:property name="defaultTargetUrl" value="/login" />
    </beans:bean>

    <beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <beans:property name="defaultFailureUrl" value="/login?login_error=true" />
    </beans:bean>

В определении twoFactorAuthenticationFilter bean мы устанавливаем свойство extraParameter в "domain", который является именем поля ввода для использования в нашей форме входа.

EDIT:

Посмотрите на конструкторы класса User.

Если вы не знаете, что предоставленный авторитет рассмотрит эту ссылку ниже:

http://docs.spring.io/autorepo/docs/spring-security/3.2.1.RELEASE/apidocs/org/springframework/security/core/GrantedAuthority.html

Ваше кодирование дает другой режим, применимый только для обычного имени пользователя и пароля. Мой код работает для аутентификации n факторов. Попробуйте переключиться на мой код, если проблема не исчезнет.

Ответ 4

Я очень понимаю, что этот пост подвергся 28 исправлениям, поэтому я, возможно, пропустил какой-то контекст. Я также убежден в том, что вы объединили какой-то код из других ответов на свой вопрос и что проблема была несколько "повернута на голову" из "почему не будет действительный пользователь аутентифицироваться?" на "почему каждый пользователь аутентифицируется?".

Текущая проблема.

Однако, как написано, ваш метод CustomAuthenticationProvider.authenticate() всегда будет возвращать объект Authentication, который возвращает auth.isAuthenticated() == true, потому что вы создаете экземпляр с помощью этот метод, который предупреждает вас об этом. Даже если collection, который вы передали, поскольку третий аргумент был пустым, это будет так. На самом деле коллекция всегда содержит GrantedAuthority для "зарегистрированного", потому что pincodeEntered(name) всегда возвращает true. Итак, вам нужно исправить свою логику в этих методах. authenticate() должен возвращать null, если аутентификация не удалась.

Следующие шаги

Вы указали в комментариях, что вам нужна эталонная реализация многофакторной аутентификации. Это проблематично - не обязательно соглашаться на то, что будет представлять собой такую ​​вещь. Например, некоторые утверждают, что многофактор должен включать фактор владения, а не n факторов знания на одной странице входа. Это также не очень подходит для ответа SO, поскольку ему понадобится запись в блоге (или серия) - как бы щедрая щедрость.

В Интернете существуют рабочие примеры многофакторной аутентификации spring, здесь и здесь, например. Последнее, я думаю, вы, должно быть, обнаружили, поскольку вы, кажется, используете какой-то код оттуда.

Выполнение вашей работы CustomAuthenticationProvider может занять несколько часов. Отладка может занять еще больше времени, поскольку в вашем примере есть смесь методов - это не минимально. В частности, предполагается, что класс TwoFactorAuthenticationFilter используется для перехвата ввода по запросу со страницы входа и объединения имени пользователя и вывода. В примере из блога это настроено в XML - вы можете добавить пространство имен security в свой business-config.xml и добавить те beans там, например.

Однако класс SecurityConfig и CustomAuthenticationProvider снова является другим методом.

Затем код проекта ссылается на j_security_check url, но этот URL-адрес не обрабатывается ничем. Я не уверен в намерениях этого или там, где он исходит. Наконец, конфигурация MVC для маршрутизации URL добавляет еще один элемент в микс - тот, с которым я не знаком.

Я играл с вашим примером некоторое время. Слишком много смешанных методов и слишком много сложностей для исправления быстро - возможно, другие могут.

Я сильно предлагаю вам начать с примера в блоге точно, а затем добавить конфигурацию mvc, которую вы хотите превысить.

N.B. Настройка для других, пытающихся заставить пример работать

В настройке проекта было несколько морщин - у него была ненужная и неудовлетворенная зависимость от javax.mail, вам нужно опубликовать зависимости maven с сервером (в сборке project- > properties- > deployment) и вам необходимо загрузить и установить адаптеры для сервера tomcat, если у вас его еще нет.

Вам также необходимо создать таблицы и столбцы в своей базе данных.