JavaRush /Java Blog /Random-TL /JAAS - Panimula sa Teknolohiya (Bahagi 1)

JAAS - Panimula sa Teknolohiya (Bahagi 1)

Nai-publish sa grupo
Ang seguridad sa pag-access ay ipinatupad sa Java sa loob ng mahabang panahon at ang arkitektura para sa pagbibigay ng seguridad na ito ay tinatawag na JAAS - Java Authentication and Authorization Service. Susubukan ng pagsusuring ito na lutasin ang misteryo kung ano ang pagpapatunay, awtorisasyon at kung ano ang kinalaman ng JAAS dito. Paano kaibigan ng JAAS ang Servlet API, at kung saan sila nagkakaproblema sa kanilang relasyon.
JAAS — Введение в технологию (часть 1) - 1

Panimula

Sa pagsusuring ito, nais kong talakayin ang isang paksa tulad ng seguridad sa web application. Ang Java ay may ilang mga teknolohiya na nagbibigay ng seguridad: Ngunit ang pag-uusap natin ngayon ay tungkol sa isa pang teknolohiya, na tinatawag na "Java Authentication and Authorization Service (JAAS)". Siya ang naglalarawan ng mga mahahalagang bagay tulad ng pagpapatunay at awtorisasyon. Tingnan natin ito nang mas detalyado.
JAAS — Введение в технологию (часть 1) - 2

JAAS

Ang JAAS ay isang extension sa Java SE at inilalarawan sa Java Authentication and Authorization Service (JAAS) Reference Guide . Gaya ng ipinahihiwatig ng pangalan ng teknolohiya, inilalarawan ng JAAS kung paano dapat gawin ang pagpapatunay at awtorisasyon:
  • " Authentication ": Isinalin mula sa Greek, "authentikos" ay nangangahulugang "totoo, tunay". Ibig sabihin, ang authentication ay isang pagsubok sa pagiging tunay. Na kung sino ang pinapatotohanan ay tunay na sinasabi nila.

  • " Awtorisasyon ": isinalin mula sa Ingles ay nangangahulugang "pahintulot". Ibig sabihin, ang awtorisasyon ay ang kontrol sa pag-access na ginagawa pagkatapos ng matagumpay na pagpapatunay.

То есть JAAS — это про определние того, кто запрашивает доступ к ресурсу, и про вынесение решения, а может ли он этот доступ получить. Небольшая аналогия из жизни: вы едете по дороге и Вас останавливает инспектор. Просьба предоставить documentы — аутентификация. Можете ли Вы водить автомобиль по documentам — авторизация. Или например в магазине Вы хотите купить алкоголь. Сначала, у Вас просят паспорт — аутентификация. Далее на основе вашего возраста решается, есть ли у Вас право покупать алкоголь. Это авторизация. В веб-applicationsх, вход под пользователем (ввод логина и пароля) является аутентификацией. А определение того, Howие Вам можно открывать pages — авторизацией. В этом нам и помогает "The Java Authentication and Authorization Service (JAAS)". Рассматривая JAAS важно понимать несколько ключевых понятий, которые описывает JAAS: Subject, Principals, Credentials. Subject — это субъект аутентификации. То есть это носитель or обладатель прав. В documentации Subject определён How источник (source) requestа (request) на выполнение некоторого действия. Субъект or источник необходимо How-то описать и для этого используется Principal, который на русском тоже иногда называют принципалом. То есть каждый Principal является представлением Subject с определённой точки зрения. Whatбы стало понятнее, приведём пример: Определённый человек является Subject. А в качестве Principal'ов могут выступать:
  • его водительское удостоверение, How представление человека в качестве участника дорожного движения
  • его паспорт, How представление человека в качестве гражданина своей страны
  • его заграничный паспорт, How представление человека в качестве участника международных отношений
  • его читательский билет в библиотеке, How представление человека в качестве прикреплённого к библиотеке читателя
Кроме того, Subject имеет набор "Credential", что в переводе с английского означает "удостоверение". Это то, чем Subject подтверждает, что он это он. Например, в качестве Credential может выступать пароль пользователя. Или любой an object, которым пользователь может подтвердить, что он это действительно он. Давайте теперь посмотрим, How JAAS используется в веб-applicationsх.
JAAS — Введение в технологию (часть 1) - 3

Веб-приложение

Итак, нам понадобится веб-приложение. В его создании нам поможет система автоматической сборки проектов Gradle. Благодаря использования Gradle мы сможем при помощи выполнения небольших команд собирать Java проект в нужном нам формате, создавать автоматически нужную структуру каталогов и многое другое. Подробнее про Gradle можно прочитать в кратком обзоре: "Краткое знакомство с Gradle" or в официальной documentации "Gradle Getting Started". Нам нужно выполнить инициализацию проекта (Initialization), а для этого в Gradle есть специальный плагин: "Gradle Init Plugin" (Init - сокращение от Initialization, легко запомнить). Whatбы воспользоваться этим плагином, выполним в командной строке команду:
gradle init --type java-application
После успешного выполнения у нас появится Java проект. Откроем теперь на редактирование билд скрипт нашего проекта. Билд скрипт — это файл с названием build.gradle, который описывает нюансы сборки (билда) applications. Отсюда и название такое, билд скрипт. Можно сказать, что это скрипт сборки проекта. Gradle — это такой универсальный инструмент, базовые возможности которого расширяются благодаря pluginм. Поэтому, в первую очередь обратим внимание на блок "plugins" (плагины):
plugins {
    id 'java'
    id 'application'
}
По умолчанию Gradle, в соответствии с указанным нами "--type java-application", выставил набор некоторых базовых плагинов (core plugins), то есть тех плагинов, которые входят в поставку самого Gradle. Если перейти на сайте gradle.org в раздел "Docs" (то есть documentация), то слева в списке тем в разделе "Reference" мы видим раздел "Core Plugins", т.е. раздел с описанием этих самых базовых плагинов. Давайте выберем именно те плагины, которые нам нужны, а не те, которые нам сгенерировал Gradle. Согласно documentации, "Gradle Java Plugin" обеспечивает базовые операции с Java codeом, такие How компиляция исходного codeа. Так же, согласно documentации, "Gradle application plugin" обеспечивает нас средствами для работы с "executable JVM application", т.е. с java приложением, которые можно запустить How самостоятельное приложение (например, консольное приложение or приложение с собственным UI). Получается, что плагин "application" нам не нужен, т.к. нам не нужно самостоятельное приложение, нам нужно веб-приложение. Удалим его. А так же настройку "mainClassName", которая известна только этому плагину. Далее, в том же разделе "Packaging and distribution", где была приведена link на documentацию по Application Plugin, есть link на Gradle War Plugin. Gradle War Plugin, How сказано в documentации, предоставляет поддержку создания Java веб-приложений в формате war. В формате WAR означает, что instead of JAR архива будет создан WAR архив. Кажется, это то, что нам нужно. Кроме того, How сказано в documentации, "The War plugin extends the Java plugin". То есть мы можем заменить плагин java на плагин war. Следовательно, наш блок плагинов в итоге будет иметь следующий вид:
plugins {
    id 'war'
}
Так же в documentации к "Gradle War Plugin" сказано, что плагин использует дополнительный "Project Layout". Layout с английского переводится How расположение. То есть war plugin по умолчанию рассчитывает на существование некоторого расположение файлов, которые он будет использовать для своих задач. Использовать для хранения файлов веб-applications он будет следующий каталог: src/main/webapp Поведения plugin описано так:
JAAS — Введение в технологию (часть 1) - 4
То есть плагин будет учитывать файлы из этого расположения при сборке WAR архива нашего веб-applications. Кроме того, в documentации Gradle War Plugin'а сказано, что данный каталог будет "root of the archive". И уже в нём мы можем создать каталог WEB-INF и добавить туда файл web.xml. What это за файл такой? web.xml — это "Deployment Descriptor" or "описатель развёртывания". Это такой файл, который описывает, How нужно настроить наше веб-приложение для работы. В этом файле указывается, Howие requestы будет обрабатывать наше приложение, настройки безопасности и многое другое. По своей сути он чем-то похож на manifest файл из JAR file (см. "Working with Manifest Files: The Basics"). Manifest файл рассказывает, How работать с Java Application (т.е. с JAR архивом), а web.xml рассказывает, How работать с Java Web Application (т.е. с WAR архивом). Само понятие "Deployment Descriptor" возникло не само по себе, а описано в documentе "Servlet API Specification". Любое Java веб-приложение зависит от этого "Servlet API". Важно понимать, что это API — то есть это описание некоторого контракта взаимодействия. Веб-applications — это не самостоятельные applications. Они запускаются на веб-serverе, который обеспечивает сетевое взаимодействие с пользователями. То есть веб-server это некоторый "контейнер" для веб-приложений. Это логично, т.к. мы хотим писать логику веб-applications, т.е. Howие странички увидит пользователь и How они должны реагировать на действия пользователя. И мы не хотим писать code того, How будет отправляться сообщение пользователю, How будут передаваться byteы информации и другие низкоуровневые и очень требовательные к качеству реализации вещи. Кроме того, получается, что веб-applications все разные, а передача данных одинакова. То есть миллиону программистов пришлось бы писать для одной и той же цели code снова и снова. Поэтому за часть взаимодействия с пользователем и за обмен данными отвечает веб-server, а за формирование этих данных отвечает веб-приложение и разработчик. А чтобы связать эти две части, т.е. веб-server и веб-приложение, нужен контракт их взаимодействия, т.е. по Howим правилам они это будут делать. Whatбы How-то описать контракт, How должно выглядеть взаимодействие между веб-приложением и веб-serverом и придуман Servlet API. Интересно, что даже если вы используете фрэймворки вроде Spring, то "под капотом" всё равно работает Servlet API. То есть вы используете Spring, а Spring за Вас работает с Servlet API. Получается, что наш проект веб-applications должен зависеть (depends on) от Servlet API. В этом случае Servlet API будет зависимостью (dependency). Как мы знаем, Gradle в том числе позволяет декларативным образом описывать зависимости проекта. А то, Howим образом можно управлять зависимостями, описывают плагины. Например, Java Gradle Plugin вводит способ управления зависимостями "testImplementation", который говорит, что такая зависимость нужна только для тестов. А вот Gradle War Plugin добавляет способ управления зависимостями "providedCompile", который говорит, что такая зависимость не будет включена в WAR архив нашего веб-applications. Почему мы не включаем Servlet API в наш WAR архив? Потому что Servlet API будет предоставлен нашему веб-приложению самим веб-serverом. Если веб-server предоставляет Servlet API, тогда такой server называют контейнер сервлетов. Поэтому предоставить нам Servlet API — это обязанность веб-serverа, а наша обязанность предоставить ServletAPI только на момент компиляции codeа. Поэтому и providedCompile. Таким образом, блок зависимостей (dependencies) будет иметь следующий вид:
dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    testImplementation 'junit:junit:4.12'
}
Итак, вернёмся к web.xml файлу. По умолчанию, Gradle не создаёт ниHowой Deployment Descriptor, поэтому нам нужно сделать это самостоятельно. Создадим каталог src/main/webapp/WEB-INF, а в нём создадим XML файл с названием web.xml. Теперь давайте откроем саму спецификацию "Java Servlet Specification" и главу "CHAPTER 14 Deployment Descriptor". Как сказано в "14.3 Deployment Descriptor", XML document Deployment Descriptor'а описан схемой http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd. XML схема описывает, из Howих элементов может состоять document, в Howом порядке они должны идти. Какие обязательные, а Howие нет. В общем, описывает структуру documentа и позволяет проверить, правильно ли XML document составлен. Теперь воспользуемся примером из главы "14.5 Examples", но схему нужно указать для версии 3.1, т.е.
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd
Наш пустой web.xml будет выглядеть следующим образом:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <display-name>JAAS Example</display-name>
</web-app>
Давайте теперь опишем сервлет, который мы будем защищать при помощи JAAS. Ранее нам Gradle сгенерировал класс App. Давайте превратим его в сервлет. Как сказано в специфкиации в "CHAPTER 2 The Servlet Interface", чтобы "For most purposes, Developers will extend HttpServlet to implement their servlets", то есть чтобы сделать класс сервлетом необходимо унаследовать этот класс от HttpServlet:
public class App extends HttpServlet {
	public String getGreeting() {
        return "Secret!";
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().print(getGreeting());
    }
}
Как мы и говорor, Servlet API — это контракт между serverом и нашим веб-приложением. Это контракт позволяет описать, что когда пользователь обратиться к serverу, server сформирует request от пользователя в виде an object HttpServletRequest и передаст его в сервлет. А так же предоставит сервлету an object HttpServletResponse, чтобы сервлет смог записать в него ответ для пользователя. Когда сервлет отработает, server сможет на основе HttpServletResponse предоставить пользователю ответ. То есть сервлет напрямую не общается с пользователем, а только с serverом. Whatбы server знал, что у нас есть сервлет и для Howих requestов его нужно задействовать, нужно serverу об этом рассказать в деплоймент дескрипторе:
<servlet>
	<servlet-name>app</servlet-name>
	<servlet-class>jaas.App</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>app</servlet-name>
	<url-pattern>/secret</url-pattern>
</servlet-mapping>
В данном случае все requestы на /secret будут addressованы нашему одному сервлету по имени app, которое соответствует классу jaas.App. Как мы ранее говорor, веб-приложение может быть развёрнуто только на веб-serverе. Веб-server может быть установлен отдельно (standalone). Но для целей данного обзора подойдёт альтернативный вариант — запуск на встроенном (embedded) serverе. Это значит, что server будет создан и запущен программно (за нас это сделает плагин), а вместе с этим на нём будет развёрнуто наше веб-приложение. Система сборки Gradle позволяет для этих целей использовать плагин "Gradle Gretty Plugin":
plugins {
    id 'war'
    id 'org.gretty' version '2.2.0'
}
Кроме того, плагин Gretty имеет хорошую documentацию. Начнём с того, что плагин Gretty позволяет переключаться между разными веб-serverами. Подробнее это описано в documentации: "Switching between servlet containers". Переключимся на Tomcat, т.к. он является одним из самых популярных в использовании, а так же имеет хорошую documentацию и множество примеров и разобранных проблем:
gretty {
    // Переключаемся с дефолтного Jetty на Tomcat
    servletContainer = 'tomcat8'
    // Укажем Context Path, он же Context Root
    contextPath = '/jaas'
}
Теперь мы можем выполнить "gradle appRun" и тогда наше веб-приложение будет доступно по addressу http://localhost:8080/jaas/secret
JAAS — Введение в технологию (часть 1) - 5
Важно проверить, что контейнер сервлетов выбран Tomcat (см. #1) и проверить, по Howом addressу доступно наше веб-приложение (см. #2).
JAAS — Введение в технологию (часть 1) - 6

Аутентификация

Настройки аутентификации зачастую состоят из двух частей: настроек на стороне serverа и настроек на стороне веб-applications, которое на этом serverе работает. Настройки безопасности веб-applications не могут не взаимодействовать с настройками безопасности веб-serverа хотя бы по той причине, что веб-приложение не может не взаимодействовать с веб-serverом. Мы с Вами не зря переключorсь на Tomcat, т.к. Tomcat имеет хорошо описанную архитектуру (см. "Apache Tomcat 8 Architecture"). Из описания этой архитектуры видно, что Tomcat How веб-server представляет веб-приложение How некоторый контекст, который и называют "Tomcat Context". Этот контекст позволяет каждому веб-приложению иметь свои настройки, изолированные от других веб-приложений. Кроме того, веб-приложение может влиять на настройки этого контекста. Гибко и удобно. Для более глубокого понимания рекомендуется к прочтению статья "Understanding Tomcat Context Containers" и раздел documentации Tomcat "The Context Container". Как выше было сказано, наше веб-приложение может влиять на Tomcat Context нашего applications при помощи file /META-INF/context.xml. И одной из очень важных настроек, на которую мы можем повлиять, является Security Realms. Security Realms — это некоторая "область безопасности". Область, для которой указаны определенные настройки безопасности. Соответственно, используя Security Realm мы применяем настройки безопасности, определённые для этого Realm. Security Realms управляются контейнером, т.е. веб-serverом, а не нашим веб-приложением. Мы можем только рассказать serverу, Howую из областей безопасности нужно распространить на наше приложение. Документация Tomcat в разделе "The Realm Component" описывает Realm How набор данных о пользователях и их ролях для выполнения аутентификации. Tomcat предоставляет набор различных реализаций Security Realm'ов, одним из которых является "Jaas Realm". Разобравшись немного с терминологией, давайте опишем Tomcat Context в файле /META-INF/context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Realm className="org.apache.catalina.realm.JAASRealm"
           appName="JaasLogin"
           userClassNames="jaas.login.UserPrincipal"
           roleClassNames="jaas.login.RolePrincipal"
           configFile="jaas.config" />
</Context>
appName — Name приложение (application name). Tomcat попробует сопоставить это Name с именами, указанными в configFile. configFile — это "login configuration file". Его пример можно увидеть в documentации JAAS: "Appendix B: Example Login Configurations". Кроме того, важно, что данный файл будет искаться сначала в ресурсах. Поэтому, наше веб приложение может само предоставить этот файл. Атрибуты userClassNames и roleClassNames содержат указание на классы, представляющие собой принципал пользователя. JAAS разделяет понятие "пользователь" и "роль" How два разных java.security.Principal. Давайте опишем указанные выше классы. Создадим простейшую реализацию для принципала пользователя:
public class UserPrincipal implements Principal {
    private String name;
    public UserPrincipal(String name) {
        this.name = name;
    }
    @Override
    public String getName() {
        return name;
    }
}
Точно такую же реализацию повторим и для RolePrincipal. Как Вы могли увидеть по интерфейсу, главное для Principal — хранить и возвращать некоторое Name (or ID), представляющие Principal. Теперь, у нас есть Security Realm, есть классы принципалов. Осталось заполнить файл из атрибута "configFile", он же login configuration file. Его описание можно найти в documentации к Tomcat: "The Realm Component".
JAAS — Введение в технологию (часть 1) - 7
То есть мы можем поместить настройку JAAS Login Config в ресурсы своего веб-applications и благодаря Tomcat Context'у мы сможем его использовать. Данный файл должен быть доступен How ресурс для ClassLoader'а, поэтому его путь должен быть такой: \src\main\resources\jaas.config Зададим содержимое данного file:
JaasLogin {
    jaas.login.JaasLoginModule required debug=true;
};
Стоит обратить внимание, что здесь и в context.xml использовано одинаковое Name. Таким образом Security Realm сопоставляется с LoginModule. Итак, Tomcat Context сообщил, Howие классы представляют принципалы, а так же Howой LoginModule использовать. Нам осталось только реализовать этот LoginModule. LoginModule — это, пожалуй, одна из самых интересных вещей в JAAS. В разработке LoginModule нам поможет официальная documentация: "Java Authentication and Authorization Service (JAAS): LoginModule Developer's Guide". Давайте реализуем логин модуль. Создадим класс, который реализует интерфейс LoginModule:
public class JaasLoginModule implements LoginModule {
}
Сначала опишем метод инициализации LoginModule:
private CallbackHandler handler;
private Subject subject;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, <String, ?> sharedState, Map<String, ?> options) {
	handler = callbackHandler;
	this.subject = subject;
}
Данный метод сохранит Subject, который мы далее аутентифицируем и заполним информацией о принципалах. А так же сохраним для дальнейшего использования CallbackHandler, который нам передают. При помощи CallbackHandler мы сможем requestить различную информацию о субъекте аутентификации чуть позже. Подробнее про CallbackHandler можно прочитать в соответствующем разделе documentации: "JAAS Reference Guide : CallbackHandler". Далее выполняется метод login для аутентификации Subject. Это является первой фазой аутентификации:
@Override
public boolean login() throws LoginException {
	// Добавляем колбэки
	Callback[] callbacks = new Callback[2];
	callbacks[0] = new NameCallback("login");
	callbacks[1] = new PasswordCallback("password", true);
	// При помощи колбэков получаем через CallbackHandler логин и пароль
	try {
		handler.handle(callbacks);
		String name = ((NameCallback) callbacks[0]).getName();
		String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword());
		// Далее выполняем валидацию.
		// Тут просто для примера проверяем определённые значения
		if (name != null && name.equals("user123") && password != null && password.equals("pass123")) {
			// Сохраняем информацию, которая будет использована в методе commit
			// Не "пачкаем" Subject, т.к. не факт, что commit выполнится
			// Для примера проставим группы вручную, "хардcodeно".
			login = name;
			userGroups = new ArrayList<String>();
			userGroups.add("admin");
			return true;
		} else {
			throw new LoginException("Authentication failed");
		}
	} catch (IOException | UnsupportedCallbackException e) {
		throw new LoginException(e.getMessage());
	}
}
Важно, что в login мы не должны изменять an object Subject. Такие изменения должны происходить только в методе подтверждения commit. Далее мы должны описать метод подтверждения успешной аутентификации:
@Override
public boolean commit() throws LoginException {
	userPrincipal = new UserPrincipal(login);
	subject.getPrincipals().add(userPrincipal);
	if (userGroups != null && userGroups.size() > 0) {
		for (String groupName : userGroups) {
			rolePrincipal = new RolePrincipal(groupName);
			subject.getPrincipals().add(rolePrincipal);
		}
	}
	return true;
}
Может показаться странным разделение метода login и commit. Но дело в том, что login модули могут быть объединены. И для успешной аутентификации может быть необходимо, чтобы отработало успешно несколько логин модулей. И только если отработают все нужные модули — тогда сохранять изменения. Это является второй фазой аутентификации. Завершим методами abort и logout:
@Override
public boolean abort() throws LoginException {
	return false;
}
@Override
public boolean logout() throws LoginException {
	subject.getPrincipals().remove(userPrincipal);
	subject.getPrincipals().remove(rolePrincipal);
	return true;
}
Метод abort вызывается тогда, когда завершилась неудачей первая фаза аутентификации. Метод logout вызывается при выходе из системы. Реализовав свой Login Module и настроив Security Realm, Теперь нам надо указать в web.xml тот факт, что мы хотим использовать определённый Login Config:
<login-config>
  <auth-method>BASIC</auth-method>
  <realm-name>JaasLogin</realm-name>
</login-config>
Мы указали Name нашего Security Realm и указали Authentication Method — BASIC. Это один из видов аутентификации, описанных в Servlet API в разделе "13.6 Authentication". Остался п
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION