Опубликован: 18.03.2010 | Доступ: свободный | Студентов: 779 / 61 | Оценка: 4.48 / 4.33 | Длительность: 12:01:00
Лекция 5:

JNDI и приложения

< Лекция 4 || Лекция 5: 1234
Аннотация: В данной лекции основы технологии JNDI, а также использование JNDI для извлечения объектов из службы имен DNS.

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 -каталоге следующим образом:


Рис. 5.1.

В этом примере мы разработаем систему аутентификации/авторизации пользователей на основе LDAP.(Подробнее об аутентификации и авторизации будет рассказано в одном из следующих разделов.) При этом информация о пользователях и их паролях будет храниться в LDAP каталоге. Предположим, что, как в примере, все пользователи в каталоге являются потомками узлом dc=ifmo, dc=ru, а их пароли хранятся в атрибуте userPassword. Пример соответствующего DIT с двумя пользователями представлен на следующем рисунке.


Рис. 5.2.

Центром системы станет сеансовый компонент без состояния (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 - выводится на экран.

< Лекция 4 || Лекция 5: 1234
Анна Малова
Анна Малова
Азербайджан, Баку
Олег Корсак
Олег Корсак
Латвия, Рига