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


   .passwordEncoder(new BCryptPasswordEncoder())


   .passwordAttribute("passcode");


}

В этом примере указывается, что атрибут passcode должен сравниваться с заданным паролем. Кроме того, вы также указываете кодировщик паролей. Приятно, что фактический пароль хранится в секрете на сервере при сравнении паролей на стороне сервера. Но попытка ввода пароля все равно передается по проводам на сервер LDAP и может быть перехвачена хакером. Чтобы предотвратить это, можно указать стратегию шифрования, вызвав метод passwordEncoder().

В предыдущем примере пароли шифруются с помощью функции хэширования паролей bcrypt. Это предполагает, что пароли также зашифрованы с помощью bcrypt на сервере LDAP.

ОБРАЩЕНИЕ К УДАЛЕННОМУ СЕРВЕРУ LDAP

Единственное, я до сих пор так и не указал местоположение, где находится LDAP-сервер и на котором нужные данные фактически находяться. Вы успешно настроили Spring для аутентификации на сервере LDAP, но где этот сервер?

По умолчанию аутентификация LDAP Spring Security предполагает, что сервер LDAP прослушивает порт 33389 на localhost. Но если ваш сервер LDAP находится на другой машине, вы можете использовать метод contextSource() для настройки его местоположения:

@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.ldapAuthentication()


   .userSearchBase("ou=people")


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


   .groupSearchBase("ou=groups")


   .groupSearchFilter("member={0}")


   .passwordCompare()


   .passwordEncoder(new BCryptPasswordEncoder())


   .passwordAttribute("passcode")


   .contextSource()


   .url("ldap://tacocloud.com:389/dc=tacocloud,dc=com");


}

Метод contextSource() возвращает ContextSourceBuilder, который, помимо прочего, предлагает метод url(), позволяющий указать расположение сервера LDAP.

НАСТРОЙКА ВСТРОЕННОГО СЕРВЕРА LDAP

Если у вас нет сервера LDAP, ожидающего аутентификации, Spring Security может предоставить вам встроенный сервер LDAP. Вместо установки URL-адреса удаленного сервера LDAP можно указать корневой суффикс для встроенного сервера с помощью метода root():

@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.ldapAuthentication()


   .userSearchBase("ou=people")


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


   .groupSearchBase("ou=groups")


   .groupSearchFilter("member={0}")


   .passwordCompare()


   .passwordEncoder(new BCryptPasswordEncoder())


   .passwordAttribute("passcode")


   .contextSource()


   .root("dc=tacocloud,dc=com");


}

Когда сервер LDAP запустится, он попытается загрузить данные из любых LDIF-файлов, которые он может найти в пути к классам. LDIF (формат обмена данными LDAP) - это стандартный способ представления данных LDAP в текстовом файле. Каждая запись состоит из одной или нескольких строк, каждая из которых содержит имя и значение. Записи отделяются друг от друга пустыми строками.

Если вы предпочитаете, чтобы Spring не рылась в вашем classpath в поисках любых LDIF-файлов, которые он может найти, вы можете более четко указать, какой LDIF-файл загружается, вызвав метод ldif():

@Override


protected void configure(AuthenticationManagerBuilder auth) throws Exception {


 auth.ldapAuthentication()


   .userSearchBase("ou=people")


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


   .groupSearchBase("ou=groups")


   .groupSearchFilter("member={0}")


   .passwordCompare()


   .passwordEncoder(new BCryptPasswordEncoder())


   .passwordAttribute("passcode")


   .contextSource()


   .root("dc=tacocloud,dc=com")


   .ldif("classpath:users.ldif");


}

Здесь вы специально просите сервер LDAP загрузить его содержимое о пользователях. ldif-файл в корне classpath приложения. Если вам интересно, вот файл LDIF, который вы можете использовать для загрузки встроенного сервера LDAP с пользовательскими данными:

dn: ou=groups,dc=tacocloud,dc=com


objectclass: top


objectclass: organizationalUnit


ou: groups


dn: ou=people,dc=tacocloud,dc=com


objectclass: top


objectclass: organizationalUnit


ou: people


dn: uid=buzz,ou=people,dc=tacocloud,dc=com


objectclass: top


objectclass: person


objectclass: organizationalPerson


objectclass: inetOrgPerson


cn: Buzz Lightyear


sn: Lightyear


uid: buzz


userPassword: password


dn: cn=tacocloud,ou=groups,dc=tacocloud,dc=com


objectclass: top


objectclass: groupOfNames


cn: tacocloud


member: uid=buzz,ou=people,dc=tacocloud,dc=com

Spring Security’s built-in хранилища пользователей удобны и покрывают некоторые общие случаи использования. Но приложение Taco Cloud нуждается в чем-то особенном. Если готовые пользовательские хранилища не соответствуют вашим потребностям, вам потребуется создать и настроить собственную службу сведений о пользователе.

4.2.4 Собственная служба сведений о пользователе

В последней главе вы остановились на использовании Spring Data JPA в качестве опции сохранения для всех данных taco, ingredient и order. Таким образом, было бы целесообразно сохранить пользовательские данные таким же образом. Если вы сделаете это, данные будут в конечном счете находиться в реляционной базе данных, поэтому вы можете использовать аутентификацию на основе JDBC.  Но было бы еще лучше использовать хранилище данных Spring, используемое для хранения пользователей.

Но сначала о главном. Давайте создадим объект домена и интерфейс репозитория, который представляет и сохраняет информацию о пользователе.

ОПРЕДЕЛЕНИЕ ДОМЕНА ПОЛЬЗОВАТЕЛЯ И СОХРАНЯЕМОСТИ(PERSISTENCE)

Когда клиенты Taco Cloud регистрируются в приложении, им необходимо предоставить не только имя пользователя и пароль. Они также дадут вам свое полное имя, адрес и номер телефона. Эта информация может быть использована для различных целей, включая предварительное заполнение формы заказа (не говоря уже о потенциальных маркетинговых возможностях).

Чтобы получить всю эту информацию, создайте класс User, как показано ниже.

Листинг 4.5 Описание сущности user

package tacos;



import java.util.Arrays;


import java.util.Collection;


import javax.persistence.Entity;


import javax.persistence.GeneratedValue;


import javax.persistence.GenerationType;


import javax.persistence.Id;


import org.springframework.security.core.GrantedAuthority;


import org.springframework.security.core.authority.


SimpleGrantedAuthority;


import org.springframework.security.core.userdetails.UserDetails;


import lombok.AccessLevel;


import lombok.Data;


import lombok.NoArgsConstructor;


import lombok.RequiredArgsConstructor;



@Entity


@Data


@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)


@RequiredArgsConstructor


public class User implements UserDetails {


 private static final long serialVersionUID = 1L;



 @Id


 @GeneratedValue(strategy=GenerationType.AUTO)


 private Long id;



 private final String username;


 private final String password;


 private final String fullname;


 private final String street;


 private final String city;


 private final String state;


 private final String zip;


 private final String phoneNumber;



 @Override


 public Collection getAuthorities() {


   return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));


 }



 @Override


 public boolean isAccountNonExpired() {


   return true;


 }



 @Override


 public boolean isAccountNonLocked() {


   return true;


 }



 @Override


 public boolean isCredentialsNonExpired() {


   return true;


 }


 @Override


 public boolean isEnabled() {


   return true;


 }


}

Вы, несомненно, заметили, что класс User немного более связанный, чем любая другая сущность, определенная в главе 3.  В дополнение к определению нескольких свойств, пользователь также реализует интерфейс UserDetails из Spring Security.

Implementations UserDetails предоставит некоторую важную информацию о пользователе от framework, например, какие полномочия предоставлены пользователю и включена ли учетная запись пользователя.

Метод getAuthorities() должен возвращать коллекцию полномочий, предоставленных пользователю. Различные методы is___Expired () возвращают логическое значение, указывающее, включена или нет учетная запись пользователя.

Для сущности User метод getAuthorities () просто возвращает коллекцию, указывающую, что всем пользователям будут предоставлены полномочия ROLE_USER. И, по крайней мере, на данный момент, Taco Cloud не нужно отключать пользователей, поэтому все методы is___Expired() возвращают true, чтобы указать, что пользователи активны.

Определив сущность User, вы можете определить интерфейс репозитория:

package tacos.data;



import org.springframework.data.repository.CrudRepository;


import tacos.User;



public interface UserRepository extends CrudRepository {


 User findByUsername(String username);


}

Помимо операций CRUD, предоставляемых расширением CrudRepository, UserRepository определяет метод findByUsername(), который будет использоваться в сервисе сведений о пользователе для поиска пользователя по его имени пользователя.