Здравствуйте, подскажите пожалуйста где можно достать материалы по курсу Кросс-платформенные и многозвенные технологии, о которых говориться, к примеру, в Лекции 2. Пример "Служба мгновенных сообщений" |
JNDI и приложения
JNDI
Общие сведение о JNDI
Java Naming and Directory Interface (JNDI) - это API для доступа к службам имен и каталогов. Прежде чем погружаться в JNDI, поясним, о каких службах идет речь. Службой имен, в самом широком смысле, называют систему, управляющую отображением множества имен во множество объектов. Зная имя объекта в системе, клиент может получить доступ к этому объекту или ассоциировать с этим именем другой объект. Примером является DNS, служба доменных имен. В ее ведении находится соответствие между понятными человеку доменными именами (например, www.ifmo.ru) и понятными компьютеру сетевыми IP -адресами (например, 184.35.65.10). Посылая DNS доменное имя, клиент получает соответствующий ему IP -адрес.
В службе каталогов поименованные объекты сгруппированы в древовидную структуру. Кроме того объекты каталога имеют атрибуты. Наиболее близким и понятным примером такой службы является файловая система. Объекты файловой системы - файлы - собраны в каталоги и идентифицируются путями, например, C:\windows\notepad.exe. У файлов есть атрибуты: скрытый, архивный, только для чтения и другие. Передавая файловой системе путь, можно получить содержимое соответствующего файла, записать в него какие-то данные, изменить его атрибуты.
JNDI предназначен для единообразного доступа к разнообразным службам имен и каталогов, включая упомянутые выше DNS и файловую систему, а также LDAP,о котором еще пойдет речь. Разные службы каталогов интегрируются с JNDI через интерфейс поставщика услуг (Service Provider Interface, SPI).
Первая редакция спецификация JNDI была выпущена корпорацией Sun Microsystems 10 марта 1997 г. В 2006 г. вышла спецификация JNDI версии 1.2. JNDI состоит из следующих пяти пакетов:
- javax.naming - содержит основные классы и интерфейсы, необходимые для взаимодействия со службами имен. В частности, интерфейс Context для поиска объектов, привязки объекта к имени, создания и удаления контекстов.
- javax.naming.directory - расширяет пакет javax.naming средствами взаимодействия со службами каталогов. Определяет интерфейс DirContext, позволяющий работать с атрибутами объектов каталога.
- javax.naming.event - определяет классы и интерфейсы событий, происходящих в каталоге, а также средства перехвата этих событий.
- javax.naming.ldap - предоставляет средства для работы со специфическими возможностями LDAP v3, не покрываемыми более общим пакетом javax.naming.directory. Однако эти возможности редко используются, и в большинстве случаев достаточно использовать пакет javax.naming.directory.
- javax.naming.spi определяет способ интеграции новых систем имен или каталогов с JNDI,чтобы клиенты могли пользоваться этими службами из Java -программ средствами JNDI.
В этой главе мы рассмотрим использование JNDI для извлечения объектов из службы имен, а в следующей главе пойдет речь о доступе к LDAP.
Пример: использование JNDI для доступа к DataSource
Часто использование JNDI в программе ограничивается всего несколькими строками и играет вспомогательную, второстепенную роль. Типичный пример - подключение к источнику данных ( data source ). Следующий код, включенный в EJB,позволяет подключиться к источнику данных, привязанному к имени "java :/DefaultDS". Этот источник данных существует в JBoss по умолчанию. Разумеется, можно настроить собственный источник данных и извлечь его из службы имен совершенно аналогично.
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/DefaultDS");
Работа с JNDI всегда начинается с создания объекта InitialContext (или InitialDirContext в случае работы со службой каталогов). Конструктор этого объекта может принимать параметры, определяющие, к какой службе и каким образом подключаться. В данном случае параметры в конструктор не передаются, поэтому происходит подключение к службе имен сервера JBoss, в котором запущен данный EJB. Затем вызов метода lookup () извлекает из службы имен объект, соответствующий имени "java:/DefaultDS".
Спецификация EJB 3.0 еще более упростила задачу получения объектов из службы имен. Теперь для получения того же источника данных достаточно завести член класса EJB со специальной аннотацией, а об извлечении объекта из службы имен и присвоении его члену класса позаботится EJB -контейнер.
@Resource(mappedName="java:/DefaultDS")
private DataSource ds;
В следующем разделе будет приведен более содержательный пример использования JNDI для доступа к LDAP каталогу, хранящему информацию о пользователях.
Общие сведения о LDAP
Lightweight Directory Access Protocol (LDAP) - это сетевой протокол для доступа к каталогам (впрочем, иногда LDAP называют не только протокол, но и сам каталог). LDAP был разработан как замена более старому и тяжеловесному протоколу DAP,определенному в стандарте X.500 и построенному на стеке протоколов Open Systems Interconnection (OSI). В глобальной службе каталогов X.500 может храниться информация о подразделениях компаний, людях, компьютерах, документах и т.д. Зная имя объекта, можно запросить в каталоге информацию о нем (сервис белых страниц, white-pages service),а можно просто просматривать каталог в поисках чего-то интересного (сервис желтых страниц, yellow-pages service).
Информация, содержащаяся в каталоге, составляет базу данных, называемую directory information base (DIB).Элементы DIB образуют дерево, называемое directory information tree (DIT).Каждый элемент имеет имя и набор типизированных атрибутов с их значениями. Схема каталога определяет обязательные и опциональные атрибуты для каждого класса объектов каталога.
Пространство имен каталога X.500 является иерархическим - как файловая система. Каждый объект каталога однозначно идентифицируется уникальным именем, называемым distinguished name (DN). DN - это конкатенация значений некоторого атрибута всех объектов, находящихся на пути от корня дерева к искомому объекту (аналогия в файловой системе - путь к файлу). Указанный атрибут называется relative distinguished name (RDN). Отличие X.500 каталога от файловой системы в том, что имена объектов, DN, записываются справа налево, например, cn=Alexey Vladykin, dc=ifmo, dc=ru. Корень дерева здесь расположен справа, а лист - слева. В файловой системе наоборот: корень - слева, лист -справа, например, c:\windows\notepad.exe.
Пользователи каталога X.500 могут, с учетом прав доступа, читать и изменять объекты и их атрибуты в DIB.
Недостатком стандарта X.500 и определяемого в нем протокола доступа к каталогам DAP является то, что они основаны на стеке протоколов OSI,в то время как стандартом де-факто для Интернета стал более простой стек TCP/IP.В связи с этим потребовалась разработка службы каталогов и протокола доступа к ней, работающих на стеке протоколов TCP/IP.Решением проблемы стал Lightweight DAP,или просто LDAP,разработанный в 1992 г в Мичиганском университете. LDAP сохранил основные концепции X.500,такие как DIT и DN. Текущей версией протокола является LDAP v3 (RFC 2251).
Одним из самых известных и широко распространенных LDAP серверов является OpenLDAP (http://www. openldap. org/), доступный бесплатно вместе с исходными кодами. Во время работы над этой главой автор использовал сборку OpenLDAP под Windows,взятую по адресу http ://lucas.bergmans.us/hacks/openldap/.
Пример: система авторизации пользователей на основе LDAP
LDAP часто используется для хранения информации о людях, в частности, о пользователях корпоративных сетей. Информация о человеке может храниться в LDAP -объекте класса person или расширяющих его organizationalPerson и inetOrgPerson. Например, автор данного текста может быть описан в LDAP -каталоге следующим образом:
В этом примере мы разработаем систему аутентификации/авторизации пользователей на основе LDAP.(Подробнее об аутентификации и авторизации будет рассказано в одном из следующих разделов.) При этом информация о пользователях и их паролях будет храниться в LDAP каталоге. Предположим, что, как в примере, все пользователи в каталоге являются потомками узлом dc=ifmo, dc=ru, а их пароли хранятся в атрибуте userPassword. Пример соответствующего DIT с двумя пользователями представлен на следующем рисунке.
Центром системы станет сеансовый компонент без состояния (stateless session bean),инкапсулирующий всю работу с каталогом и имеющий следующий удаленный интерфейс с единственным методом authenticate ():
package ru.ifmo.javaee.ldapauth; import javax.ejb.Remote; @Remote public interface LdapAuth { public boolean authenticate(String name, String password); }
Различные приложения могут обращаться к этому EJB для проверки того, что введенные пользователем имя и пароль являются верными.
Рассмотрим реализацию этого EJB.
package ru.ifmo.javaee.ldapauth; import java.util.Hashtable; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.ejb.Stateless; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; @Stateless public class LdapAuthBean implements LdapAuth { @Resource(name="providerUrl") private String providerUrl; @Resource(name="securityAuthentication") private String securityAuthentication; @Resource(name="securityPrincipal") private String securityPrincipal; @Resource(name="securityCredentials") private String securityCredentials; @Resource(name="baseContext") private String baseContext; private InitialDirContext ctx; @PostConstruct public void init() { Hashtable<String, String> args = new Hashtable<String, String>(); args.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); args.put(Context.PROVIDER_URL, providerUrl); args.put(Context.SECURITY_AUTHENTICATION, securityAuthentication); args.put(Context.SECURITY_PRINCIPAL, securityPrincipal); args.put(Context.SECURITY_CREDENTIALS, securityCredentials); try { ctx = new InitialDirContext(args); } catch (NamingException e) { e.printStackTrace(); } } public boolean authenticate(String name, String password) { SearchControls controls = new SearchControls(); controls.setSearchScope(SearchControls.SUBTREE_SCOPE); try { NamingEnumeration results = ctx.search( baseContext, "(&(cn={0})(userPassword={1}))", new String[]{ name, password }, controls); return results.hasMore(); } catch (NamingException e) { e.printStackTrace(); } return false; } }
Интерес для нас представляют методы init() и authenticate(). В методе init(), автоматически вызываемом после создания экземпляра компонента, осуществляется соединение с ZDAP -каталогом. Для этого хеш-таблица args заполняется параметрами (так мы сообщаем JNDI,к какой службе подключаться), а затем создается начальный контект ctx, представляющий собой корень каталога.
Метод authenticate () производит в каталоге поиск пользователя с заданным именем и паролем, используя возможности JNDI по поиску. В случае успеха возвращает true, иначе - false.
Перед развертыванием EJB требуется создать еще несколько файлов. Во-первых, ejb-jar.xml, задающий все параметры соединения с ZDAP -каталогом, используемые LdapAuthBean:
<ejb-jar version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar 3 0.xsd" > <enterprise-beans> <session> <ejb-name>LdapAuthBean</ejb-name> <env-entry> <env-entry-name>providerUrl</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>ldap://localhost:389</env-entry-value> </env-entry> <env-entry> <env-entry-name>securityAuthentication</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>simple</env-entry-value> </env-entry> <env-entry> <env-entry-name>securityPrincipal</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>cn=Administrator,dc=ifmo,dc=ru</env- entry-value> </env-entry> <env-entry> <env-entry-name>securityCredentials</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>secret</env-entry-value> </env-entry> <env-entry> <env-entry-name>baseContext</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>dc=ifmo,dc=ru</env-entry-value> </env-entry> </session> </enterprise-beans> </ejb-jar>
Во-вторых, jboss.xml, описывающий развертывание EJB для контейнера JBoss:
<?xml version="1.0" encoding="UTF-8"?> <jboss> <enterprise-beans> <session> <ejb-name>LdapAuthBean</ejb-name> <jndi-name>LdapAuth/Remote</jndi-name> </session> </enterprise-beans> </jboss>
Теперь EJB готов к развертыванию и тестированию. Для его тестирования можно использовать такую программу:
package ru.ifmo.javaee.ldapauth; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class LdapAuthClient { public static void main(String[] args) { Hashtable<String, String> env = new Hashtable<String, String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); env.put(Context.PROVIDER_URL, "localhost:1099"); try { InitialContext ctx = new InitialContext(env); LdapAuth auth = (LdapAuth) ctx.lookup("LdapAuth/Remote"); System.out.println(auth.authenticate(args[0], args[1])); ctx.close(); } catch (NamingException e) { e.printStackTrace(); } } }
Программа подключается к EJB (обратите внимание, для этого используется JNDI) и вызывает его метод authenticate (), передавая полученные с командной строки имя пользователя и пароль. Результат - true или false - выводится на экран.