JavaRush /Java блог /Random UA /JAAS - Введення в технологію (частина 2)
Viacheslav
3 рівень

JAAS - Введення в технологію (частина 2)

Стаття з групи Random UA
Продовження першої частини статті про JAAS. Розберемося, чи можна використовувати для JAAS лише анотації, які ми зустрінемо проблеми. Які засоби Servlet API дозволяють зробити код універсальніше, дізнаємося з цієї частини. І підіб'ємо підсумок прочитаного.
JAAS - Введення в технологію (частина 2) - 1

Продовження

У першій частині розгляду технології JAAS (див. " JAAS - Введення в технологію (частина 1) ") ми розглянули основний сценарій використання JAAS та Servlet API. Ми побачабо, що контейнер сервлетів Tomcat керував безпекою нашого веб-додатку із застосуванням архітектури JAAS. Маючи знання про "auth-method" і "Security Realm" контейнер Tomcat сам надав нам потрібну реалізацію механізму автентифікації і сам надав нам CallbackHandler, ми лише використовували це все в нашому логін модулі. Єдине, важливо пам'ятати, що браузер зберігає дані логіну та пароля, передані за BASIC аутентифікації. Тому для кожної нової перевірки за допомогою Chrome можна натискати Ctrl+Shift+N для відкриття нового вікна для роботи в режимі інкогніто.
JAAS - Введення в технологію (частина 2) - 2

Анотації

Конфігурування за допомогою XML давно вже виходить із моди. Тому, важливо сказати, що починаючи з Servlet API версії 3.0, у нас з'явилася можливість задати налаштування сервлетів за допомогою анотацій, без використання web.xml файлу деплоймент дескриптора. Давайте подивимося, як зміниться управління безпекою, якщо ми будемо використовувати анотації. І можна реалізувати вище описані підходи з допомогою анотацій. Розібратися з інструкціями нам допоможе знову специфікація Servlet API та її розділ " Annotations and pluggability " . Цей розділ говорить, що оголошення сервлета web.xmlможна замінити анотацією @WebServlet. Відповідно, наш сервлет буде виглядати так:
@WebServlet(name="app", urlPatterns = "/secret")
public class App extends HttpServlet {
Далі звернемося до розділу " 13.3 Programmatic Security " . У ній сказано, що ми можемо оголосити Security Constraint так само через анотації:
@WebServlet(name="app", urlPatterns = "/secret")
@ServletSecurity(httpMethodConstraints = {
        @HttpMethodConstraint(value = "GET", rolesAllowed = "admin")
})
public class App extends HttpServlet {
Тепер у нашому web.xmlзалишився лише один блок — login-config. Проблема в тому, що так вийшло, що немає способу легко і просто його замінити. Через тісний зв'язок налаштувань безпеки веб-додатка та налаштувань безпеки веб-сервера не існує простого та універсального способу це зробити навіть програмно. Це одна з проблем аутентифікації за допомогою JAAS та Servlet API. Говорячи про блок login-config, варто розуміти, що він є декларативним описом Authentication Mechanisms, тобто. механізмів автентифікації. До цього часу немає простого універсального способу замінити його, т.к. обробка web.xmlвідбувається глибоко всередині сервлет контейнерів. Наприклад, у Tomcat можна переглянути вихідний код ContextConfig.java . Тому навіть для контейнера сервлетів Tomcat існує кілька варіантів і всі вони різні. Наприклад, якщо ми використовуємо контейнер сервлетів Embedded Tomcat (тобто піднімаємо веб-сервер з коду), то про такі варіанти можна прочитати тут: Embedded Tomcat with basic authentication via code . Крім того, загальний приклад підняття Embedde Tomcat можна побачити в посібнику PaaS-платформи Heroku: " Create a Java Web Application Using Embedded Tomcat ". Якщо ж Tomcat використовується не в Embedded режимі, то для Tomcat можна використовувати найпоширеніший підхід - слухачі подій. У Tomcat це " The LifeCycle Listener Component ". При цьому важливо розуміти, що сервлет контейнерів (у нашому випадку Tomcat) можуть бути свої завантажувачі класів і просто так взяти і використовувати свої класи не вийде. Для Tomcat необхідно розбиратися з " Class Loader HOW-TO ". В іншому сервлеті контейнері, який називається Undertow, таке можна реалізувати за допомогою " Servlet Extensions ". Як видно, хтось передбачив гнучкіші механізми, а хтось ні. Як бачите, єдиного варіанта немає. Усі вони дуже різні. Чи є можливість зробити щось універсальне маючи тільки Servlet API і JAAS? В інтернеті можна знайти пропозицію використовувати Servlet Filter для виконання аутентифікації без блоку login-config. Давайте наостанок розглянемо цей варіант. Це дозволить нам ще повторити, як працює JAAS.
JAAS - Введення в технологію (частина 2) - 3

Auth Filter

Отже, наша мета - позбавитися повністю web.xmlфайлу. Якщо ми його позбудемося, то, на жаль, ми зможемо більше використовувати Security Constraint, т.к. їх обробка може відбуватися набагато раніше, ніж буде застосовано сервлет фільтри. Ця та плата, яку доведеться платити за "універсальність" використання фільтрів. Тобто. нам доведеться прибрати анотацію @ServletSecurity, а всі перевірки, які ми раніше описували в security constraint, доведеться виконувати програмним чином. Як бачите, цей варіант накладає на нас також багато неприємних обмежень. Насамперед, приберемо інструкцію @ServletSecurityз ресурсу і блок login-configз файлу web.xml. Тепер нам самим доведеться реалізувати Basic аутентифікацію. Тепер додамо наш фільтр:
@WebFilter("/*")
public class JaasFilter implements javax.servlet.Filter {
Поки що здається просто. Напишемо метод ініціалізації:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
	String jaas_conf = filterConfig.getServletContext().getRealPath("/WEB-INF/jaas.config");
	System.getProperties().setProperty("java.security.auth.login.config",jaas_conf);
}
Як бачимо, ми змушені використовувати базові засоби JAAS для пошуку jaas config файлу. Це має великий недолік — якщо додатків буде кілька, то один додаток може зламати автентифікацію іншому. Про те, як можна взагалі задати Jaas Config файл докладно описано в документації JAAS: " Appendix A: JAAS Settings in the java.security Security Properties File ". Далі опишемо сам метод фільтрації. Почнемо з отримання HTTP реквесту:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
	HttpServletRequest req = (HttpServletRequest) request;
	// Если в реквесте уже есть Principal - ничего не делаем
	if (req.getUserPrincipal() != null ) {
		chain.doFilter(request, response);
	}
Тут все просто. Якщо Principal доступний, то автентифікація пройдена. Далі потрібно описати дії фільтра, коли користувач не пройшов аутентифікацію, тобто. його ще не розпізнали. Раніше ми описували метод базової аутентифікації, BASIC. Т.к. ми тепер самі пишемо фільтр, то доведеться розібратися, а як взагалі працюємо BASIC аутентифікація. Можна скористатися " MDN web docs: HTTP авторизація ". А також " How Does HTTP Authentication Work? ". З опису роботи Basic аутентифікації зрозуміло, якщо сервер хоче провести BASIC аутентифікацію, а користувач не надав дані, то сервер відправляє особливий заголовок "WWW-Authenticate" і код помилки 401. Створимо для цього внутрішній метод:
private void requestNewAuthInResponse(ServletResponse response) throws IOException {
	HttpServletResponse resp = (HttpServletResponse) response;
	String value = "Basic realm=\"JaasLogin\"";
	resp.setHeader("WWW-Authenticate", value);
	resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
Тепер давайте задіємо цей метод і додамо в метод doFilterнаступний блок коду:
// Получаем security Header. А если его нет - запрашиваем
String secHeader = req.getHeader("authorization");
if (secHeader == null) {
	requestNewAuthInResponse(response);
}
Тепер давайте додамо гілку для if, яка буде виконуватися, коли у нас заголовок все ж таки переданий:
// Проверяем аутентификацию
else {
	String authorization = secHeader.replace("Basic ", "");
	Base64.Decoder decoder = java.util.Base64.getDecoder();
	authorization = new String(decoder.decode(authorization));
	String[] loginData = authorization.split(":");
	try {
		if (loginData.length == 2) {
			req.login(loginData[0], loginData[1]);
			chain.doFilter(request, response);
		} else {
			requestNewAuthInResponse(response);
		}
	} catch (ServletException e) {
		requestNewAuthInResponse(response);
	}
}
У цей код можна додати авторизацію, наприклад: req.isUserInRole("admin") Ось ми зробабо з вами аутентифікацію за допомогою фільтра. З одного боку — багато коду та ручна обробка. З іншого боку – універсальність та незалежність від сервера.
JAAS - Введення в технологію (частина 2) - 4

Висновок

Ось ми й дійшли кінця цього огляду. Сподіваюся, тепер стане трохи зрозумілішим, що таке JAAS, що таке Subject і що таке Principals. Слова Security Realm і Login Config більше не викликатимуть запитань. Крім того, ми тепер знаємо, як застосовувати JAAS та Servlet API разом. Крім того, ми дізналися про вузькі місця, де нас не врятують інструкції в Servlet API. Звичайно, це далеко не все. Наприклад, автентифікація Java може бути не тільки BASIC. Про інші типи можна переглянути у специфікації Servlet API у розділі " 13.6 Authentication ". Важко знайти в інтернеті якусь детальну інформацію щодо JAAS. Але я можу порадити цей матеріал: Сподіваюся, інформація з цього огляду буде корисною і зрозумілою. #Viacheslav
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ