Продовження першої частини статті про JAAS. Розберемося, чи можна використовувати для JAAS лише анотації, які ми зустрінемо проблеми. Які засоби Servlet API дозволяють зробити код універсальніше, дізнаємося з цієї частини. І підіб'ємо підсумок прочитаного.
Продовження
У першій частині розгляду технології JAAS (див. " JAAS - Введення в технологію (частина 1) ") ми розглянули основний сценарій використання JAAS та Servlet API. Ми побачабо, що контейнер сервлетів Tomcat керував безпекою нашого веб-додатку із застосуванням архітектури JAAS. Маючи знання про "auth-method" і "Security Realm" контейнер Tomcat сам надав нам потрібну реалізацію механізму автентифікації і сам надав нам CallbackHandler, ми лише використовували це все в нашому логін модулі. Єдине, важливо пам'ятати, що браузер зберігає дані логіну та пароля, передані за BASIC аутентифікації. Тому для кожної нової перевірки за допомогою Chrome можна натискати Ctrl+Shift+N для відкриття нового вікна для роботи в режимі інкогніто.Анотації
Конфігурування за допомогою 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.
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")
Ось ми зробабо з вами аутентифікацію за допомогою фільтра. З одного боку — багато коду та ручна обробка. З іншого боку – універсальність та незалежність від сервера.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ