Spring in Action Covers Spring 5-1--11 — страница 19 из 63

4.2.2 Хранилище пользователей на основе JDBC

Сведения о пользователях часто хранятся в реляционной базе данных, и хранилище пользователей на основе JDBC кажется подходящим. В следующем списке показано, как настроить Spring Security для проверки подлинности сведений о пользователях, хранящихся в реляционной базе данных с помощью JDBC.

Листинг 4.3 Идентификация через JDBC-базу

@Autowired


DataSource dataSource;



@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.jdbcAuthentication()


   .dataSource(dataSource);


}

Эта реализация configure() вызывает jdbcAuthentication() на данном AuthenticationManagerBuilder. Для этого необходимо установить DataSource, чтобы он знал, как получить доступ к DataSource. DataSource, используемый здесь, обеспечивается магией autowiring.

ПЕРЕОПРЕДЕЛЕНИЕ ПОЛЬЗОВАТЕЛЬСКИХ ЗАПРОСОВ ПО УМОЛЧАНИЮ

Хотя эта минимальная конфигурация будет работать, она делает некоторые предположения о схеме базы данных. Предполагается, что существуют определенные таблицы, в которых будут храниться пользовательские данные. В частности, следующий фрагмент кода из внутренних компонентов Spring Security показывает SQL-запросы, которые будут выполняться при поиске сведений о пользователе:

public static final String DEF_USERS_BY_USERNAME_QUERY =


 "select username,password,enabled " +


 "from users " +


 "where username = ?";



public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =


 "select username,authority " +


 "from authorities " +


 "where username = ?";



public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =


 "select g.id, g.group_name, ga.authority " +


 "from groups g, group_members gm, group_authorities ga " +


 "where gm.username = ? " +


 "and g.id = ga.group_id " +


 "and g.id = gm.group_id";

Первый запрос извлекает имя пользователя, пароль и информацию о том, включены они или нет. Эта информация используется для аутентификации пользователя. Следующий запрос ищет предоставленные полномочия пользователя для целей авторизации, а последний запрос ищет полномочия, предоставленные пользователю как члену группы.

Если вы согласны с определением и заполнением таблиц в базе данных, удовлетворяющих этим запросам, вам больше нечего делать.  Но, скорее всего, ваша база данных не выглядит так, и вам понадобится больше контроля над запросами. В этом случае можно настроить собственные запросы.

Листинг 4.4 Настройка запросов сведений о пользователе

@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.jdbcAuthentication()


   .dataSource(dataSource)


   .usersByUsernameQuery(


     "select username, password, enabled from Users " +


     "where username=?")


   .authoritiesByUsernameQuery(


     "select username, authority from UserAuthorities " +


     "where username=?");


}

В этом случае переопределяются только запросы проверки подлинности и обычной авторизации. Но можно также переопределить запрос полномочий группы, вызвав groupAuthoritiesByUsername () с помощью пользовательского запроса.

При замене SQL-запросов по умолчанию, запросами собственной разработки важно придерживаться базового контракта запросов. Все они принимают имя пользователя в качестве единственного параметра. Запрос проверки подлинности выбирает  username, password и enabled. Запрос привилегий выбирает ноль или более строк, содержащих username и authority. Запрос привилегий группы выбирает ноль или более строк, каждая с идентификатором группы, group_name и authority.

РАБОТА С ЗАКОДИРОВАННЫМИ ПАРОЛЯМИ

Сосредоточившись на запросе проверки подлинности, можно увидеть, что пароли пользователей должны храниться в базе данных.  Единственная проблема с этим заключается в том, что если пароли хранятся в текстовом виде, они могут стать доступны для любопытных глаз хакеров. Но если вы закодируете пароли в базе данных, проверка подлинности завершится ошибкой, так как она не будет соответствовать паролю открытого текста, отправленному пользователем.

Для решения этой проблемы, вам нужно указать кодировщик пароля, обратившись к методу passwordEncoder():

@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.jdbcAuthentication()


   .dataSource(dataSource)


   .usersByUsernameQuery(


     "select username, password, enabled from Users " +


     "where username=?")


   .authoritiesByUsernameQuery(


     "select username, authority from UserAuthorities " +


     "where username=?")


   .passwordEncoder(new StandardPasswordEncoder("53cr3t");


}

Метод passwordEncoder() принимает любую реализацию интерфейса PasswordEncoder Spring Security. Криптографический модуль Spring Security включает в себя несколько таких реализаций:

-BCryptPasswordEncoder—применяется bcrypt строгое шифрование хэширования (Applies bcrypt strong hashing encryption)

-NoOpPasswordEncoder—не применяется шифрование

-Pbkdf2PasswordEncoder—применяется PBKDF2 шифрования

-SCryptPasswordEncoder—применяется scrypt ширование хэширования (Applies scrypt hashing encryption)

-StandardPasswordEncoder—применяется SHA-256 ширование хэширования (Applies SHA-256 hashing encryption)

Предыдущий код использует StandardPasswordEncoder. Но можно выбрать любую из других реализаций или даже предоставить собственную пользовательскую реализацию, если ни одна из готовых реализаций не соответствует вашим потребностям. Интерфейс PasswordEncoder довольно прост:

public interface PasswordEncoder {


 String encode(CharSequence rawPassword);


 boolean matches(CharSequence rawPassword, String encodedPassword);


}

Независимо от того, какой кодировщик паролей вы используете, важно понимать, что пароль в базе данных никогда не декодируется. Вместо этого пароль, который пользователь вводит при входе в систему, кодируется с использованием того же алгоритма, а затем сравнивается с закодированным паролем в базе данных. Это сравнение выполняется в Passwordencoder методе matches().

В конечном счете, вы будете хранить пользовательские данные Taco Cloud в базе данных. Однако вместо того, чтобы использовать jdbcAuthentication(), у меня есть другой вариант аутентификации. Но прежде чем мы к нему перейдем, давайте посмотрим, как можно настроить Spring  Security полагаться на еще один источник данных пользователей: LDAP (облегченный протокол доступа к каталогам).

4.2.3 Хранилище пользователей в LDAP

Для настройки Spring Security для аутентификации на основе LDAP можно использовать метод ldapAuthentication(). Этот метод является LDAP аналогом jdbcAuthentication(). Следующий метод configure() показывает простую конфигурацию для аутентификации LDAP:

@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.ldapAuthentication()


   .userSearchFilter("(uid={0})")


   .groupSearchFilter("member={0}");


}

Методы userSearchFilter() и groupSearchFilter() используются для предоставления фильтров для базовых запросов LDAP, которые используются для поиска пользователей и групп. По умолчанию базовые запросы как для пользователей, так и для групп пусты, что указывает на то, что поиск будет выполняться из корня иерархии LDAP. Но вы можете изменить это, указав базовый запрос:

@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.ldapAuthentication()


   .userSearchBase("ou=people")


   .userSearchFilter("(uid={0})")


   .groupSearchBase("ou=groups")


   .groupSearchFilter("member={0}");


}

Метод userSearchBase() предоставляет базовый запрос для поиска пользователей. Аналогичным образом, метод groupSearchBase() определяет базовый запрос для поиска групп. Вместо поиска в корневом каталоге в этом примере указывается, что пользователей следует искать в организационном разделе людей. Группы следует искать в том месте, где находится организационный раздел групп.

НАСТРОЙКА СРАВНЕНИЯ ПАРОЛЕЙ

Стратегия по умолчанию для аутентификации перед LDAP должна выполнить операцию привязки, аутентифицируя пользователя непосредственно к серверу LDAP. Другой вариант - выполнить операцию сравнения. Для этого необходимо отправить введенный пароль в каталог LDAP и попросить сервер сравнить пароль с атрибутом пароля пользователя. Потому что сравнение делается в LDAP-сервером, пароль остается в тайне.

Если вы предпочитаете аутентификацию путем сравнения паролей, вы можете реализваоть это с помощью метода passwordCompare():

@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.ldapAuthentication()


   .userSearchBase("ou=people")


   .userSearchFilter("(uid={0})")


   .groupSearchBase("ou=groups")


   .groupSearchFilter("member={0}")


   .passwordCompare();


}

По умолчанию пароль, указанный в форме входа, будет сравниваться со значением атрибута userPassword в записи LDAP пользователя. Если пароль хранится в другом атрибуте, можно указать имя атрибута пароля с помощью passwordAttribute():

@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.ldapAuthentication()


   .userSearchBase("ou=people")


   .userSearchFilter("(uid={0})")


   .groupSearchBase("ou=groups")


   .groupSearchFilter("member={0}")


   .passwordCompare()