Java Platform, Enterprise Edition (Java EE) 8
Учебник по Java EE

Назад Вперёд Содержание

Использование программной безопасности в веб-приложениях

Программная безопасность используется в приложениях, когда одной декларативной безопасности недостаточно для реализации модели безопасности приложения.

Здесь рассматриваются следующие темы:

Программная аутентификация пользователей

Вы можете использовать интерфейсы SecurityContext и HttpServletRequest для программной аутентификации пользователей веб-приложения.

SecurityContext

Интерфейс SecurityContext, как указано в спецификации API безопасности Java EE, определяет следующий метод для программного запуска процесса аутентификации:

  • authenticate() позволяет приложению сигнализировать контейнеру, что оно должно начать процесс аутентификации вызывающего субъекта.

Программный запуск означает, что контейнер отвечает, как если бы вызывающий субъект пытался получить доступ к защищённому ресурсу. Это заставляет контейнер вызывать механизм аутентификации, настроенный для приложения. Если настроен механизм аутентификации HttpAuthenticationMechanism, то аргумент AuthenticationParameters имеет смысл и доступны расширенные возможности HttpAuthenticationMechanism. Если нет, поведение и результат будут такими, как если бы были вызваны HttpServletRequest.authenticate().

HttpServletRequest

Интерфейс HttpServletRequest определяет следующие методы, которые позволяют программно аутентифицировать пользователей веб-приложения.

  • authenticate позволяет приложению инициировать аутентификацию вызывающего субъекта контейнером внутри контекста незащищённого запроса. Отображается диалоговое окно входа в систему для ввода имени пользователя и пароля в целях аутентификации.

  • login позволяет приложению получить имя пользователя и пароль как альтернатива указанию аутентификации на основе форм в дескрипторе развёртывания приложения.

  • logout позволяет приложению "забыть" вызывающего субъекта.

В следующем примере кода показано, как использовать методы login и logout:

package test;

import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name="TutorialServlet", urlPatterns={"/TutorialServlet"})
public class TutorialServlet extends HttpServlet {
    @EJB
    private ConverterBean converterBean;

    /**
     * Обработка запросов как для <code>GET</code>,
     *    так и для <code>POST</code> методов HTTP.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request,
            HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {

            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet TutorialServlet</title>");
            out.println("</head>");
            out.println("<body>");
            request.login("TutorialUser", "TutorialUser");
            BigDecimal result =
                converterBean.dollarToYen(new BigDecimal("1.0"));
            out.println("<h1>Servlet TutorialServlet result of dollarToYen= "
                + result + "</h1>");
            out.println("</body>");
            out.println("</html>");
        } catch (Exception e) {
            throw new ServletException(e);
        } finally {
            request.logout();
            out.close();
        }
    }
}

В следующем примере кода показано, как использовать метод authenticate:

package com.example.test;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class TestServlet extends HttpServlet {

    protected void processRequest(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            request.authenticate(response);
            out.println("Authenticate Successful");
        } finally {
            out.close();
        }
    }

Программная проверка вызывающего субъекта

В общем случае контейнер обеспечивает прозрачное для веб-компонента управление безопасностью. Используйте API безопасности, описанные в этом разделе, только в тех редких ситуациях, когда методам веб-компонента требуется доступ к информации из контекста безопасности.

Спецификация API безопасности Java EE определяет следующие методы интерфейса SecurityContext, которые позволяют приложению тестировать различные аспекты вызывающего субъекта:

  • getCallerPrincipal() извлекает Principal, представляющий вызывающего субъекта. Это специфичное для контейнера представление вызывающего принципала, и тип может отличаться от типа вызывающего принципала, первоначально установленного HttpAuthenticationMechanism. Этот метод возвращает null для неаутентифицированного пользователя.

  • getPrincipalsByType() извлекает всех принципалов данного типа. Этот метод может использоваться для извлечения специфичного для данного приложения вызывающего принципала, определённого при аутентификации. Этот метод в первую очередь полезен в том случае, если вызывающий принципал контейнера отличается от вызывающего принципала приложения, и приложению требуется особая информация о поведении, доступная только из субъекта приложения. Этот метод возвращает пустой Set, если вызывающий субъект не прошёл проверку подлинности или если запрошенный тип не найден.

    Если присутствуют и вызывающий принципал контейнера, и вызывающий принципал приложения, значение, возвращаемое getName(), одинаково для обоих принципалов.

  • isCallerInRole() принимает аргумент String, представляющий роль, подлежащую проверке. Спецификация не определяет способ определения роли, но результат должен быть таким же, как если бы был сделан соответствующий вызов, специфичный для контейнера (например, HttpServletRequest.isUserInRole(), EJBContext.isCallerInRole()) и должен соответствовать результату, подразумеваемому спецификациями, предписывающими поведение отображения ролей.

Сервлет 4.0 определяет следующие методы, которые позволяют получить доступ к информации о вызывающем компонент субъекте.

  • getRemoteUser определяет имя пользователя, с которым аутентифицировался клиент. Метод getRemoteUser возвращает имя удалённого пользователя (вызывающего субъекта), связанного контейнером с запросом. Если ни один пользователь не был аутентифицирован, этот метод возвращает null.

  • isUserInRole определяет, находится ли удалённый пользователь в определённой роли безопасности. Если ни один пользователь не был аутентифицирован, этот метод возвращает false. Этот метод ожидает в параметре объект типа String, содержащий название роли из role-name.

    Элемент security-role-ref должен быть объявлен в дескрипторе развёртывания с подэлементом role-name, содержащим имя роли, которое будет передано методу. Использование ссылок на роли безопасности обсуждается в Объявление и связывание ссылок на роли.

  • getUserPrincipal определяет название принципала текущего пользователя и возвращает объект java.security.Principal. Если ни один пользователь не был аутентифицирован, этот метод возвращает null. Вызов метода getName у Principal, возвращённого getUserPrincipal, возвращает имя удалённого пользователя.

Приложение может принимать решения бизнес-логики на основе информации, полученной с помощью этих API.

Программное тестирование доступа к ресурсу

Интерфейс SecurityContext, как указано в спецификации API Java EE Security, определяет следующий метод для программного тестирования доступа к ресурсу:

  • Метод hasAccessToWebResource() определяет, имеет ли вызывающий субъект доступ к указанному веб-ресурсу для указанных методов HTTP, что определяется ограничениями безопасности, настроенными для приложения.

    Параметром ресурса является URLPatternSpec, как определено в спецификации Java Authorization Contract for Containers 1.5 (http://jcp.org/en/jsr/detail?id=115), который идентифицирует веб-ресурс приложения.

    Этот метод может использоваться для проверки доступа к ресурсам только в текущем приложении — его нельзя использовать для проверки доступа к ресурсам в других приложениях.

Например, рассмотрим следующее определение сервлета:

@WebServlet("/protectedServlet")
@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
public class ProtectedServlet extends HttpServlet { ... }

И следующий вызов hasAccessToWebResource():

securityContext.hasAccessToWebResource("/protectedServlet", GET)

Вышеупомянутый вызов hasAccessToWebResource() возвращает true только в том случае, когда вызывающий субъект имеет роль "foo".

Пример кода для программной безопасности

Следующий код демонстрирует использование программной защиты в целях программного входа в систему. Этот сервлет делает следующее.

  1. Отображает информацию о текущем пользователе.

  2. Предлагает пользователю войти в систему.

  3. Снова печатает информацию для демонстрации эффекта метода login.

  4. Осуществляет выход пользователя из системы.

  5. Снова выводит информацию для демонстрации эффекта метода logout.

package enterprise.programmatic_login;

import java.io.*;
import java.net.*;
import javax.annotation.security.DeclareRoles;
import javax.servlet.*;
import javax.servlet.http.*;

@DeclareRoles("javaeeuser")
public class LoginServlet extends HttpServlet {

    /**
     * Обработка запросов как GET, так и POST методов HTTP.
     * @param request servlet request
     * @param response servlet response
     */
    protected void processRequest(HttpServletRequest request,
                 HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            String userName = request.getParameter("txtUserName");
            String password = request.getParameter("txtPassword");

            out.println("Before Login" + "<br><br>");
            out.println("IsUserInRole?.."
                        + request.isUserInRole("javaeeuser")+"<br>");
            out.println("getRemoteUser?.." + request.getRemoteUser()+"<br>");
            out.println("getUserPrincipal?.."
                        + request.getUserPrincipal()+"<br>");
            out.println("getAuthType?.." + request.getAuthType()+"<br><br>");

            try {
                request.login(userName, password);
            } catch(ServletException ex) {
                out.println("Login Failed with a ServletException.."
                    + ex.getMessage());
                return;
            }
            out.println("After Login..."+"<br><br>");
            out.println("IsUserInRole?.."
                        + request.isUserInRole("javaeeuser")+"<br>");
            out.println("getRemoteUser?.." + request.getRemoteUser()+"<br>");
            out.println("getUserPrincipal?.."
                        + request.getUserPrincipal()+"<br>");
            out.println("getAuthType?.." + request.getAuthType()+"<br><br>");

            request.logout();
            out.println("After Logout..."+"<br><br>");
            out.println("IsUserInRole?.."
                        + request.isUserInRole("javaeeuser")+"<br>");
            out.println("getRemoteUser?.." + request.getRemoteUser()+"<br>");
            out.println("getUserPrincipal?.."
                        + request.getUserPrincipal()+"<br>");
            out.println("getAuthType?.." + request.getAuthType()+"<br>");
        } finally {
            out.close();
        }
    }
    ...
}

Объявление и связывание ссылок на роли

Ссылка на роль безопасности — это сопоставление имени роли, которая вызывается из веб-компонента с помощью isUserInRole(String role), и имени роли безопасности, определённой для приложения. Если в дескрипторе развёртывания не объявлено никакого элемента security-role-ref и вызван метод isUserInRole, контейнер по умолчанию проверяет указанное имя роли по списку всех ролей, определённых для веб-приложения. Использование метода по умолчанию вместо использования элемента security-role-ref ограничивает вашу гибкость в изменении имён ролей в приложении без перекомпиляции сервлета, выполняющего вызов.

Элемент security-role-ref используется, когда приложение использует HttpServletRequest.isUserInRole(String role). Значение, переданное методу isUserInRole, является объект типа String, представляющий имя роли пользователя. Значением элемента role-name должен быть объект типа String, используемый в качестве параметра для HttpServletRequest.isUserInRole(String role). role-link должна содержать имя одной из ролей безопасности, определённых в элементах security-role. Контейнер использует отображение security-role-ref на security-role при определении возвращаемого значения вызова.

Например, чтобы отобразить ссылку роли cust на роль bankCustomer, элементы должны выглядеть следующим образом:

<servlet>
...
    <security-role-ref>
        <role-name>cust</role-name>
        <role-link>bankCustomer</role-link>
    </security-role-ref>
...
</servlet>

Если метод сервлета вызывается пользователем с ролью безопасности bankCustomer, isUserInRole("cust") возвращает true.

Элемент role-link в элементе security-role-ref должен соответствовать role-name, определённому в элементе security-role того же дескриптора развёртывания web.xml, как показано здесь:

<security-role>
    <role-name>bankCustomer</role-name>
</security-role>

Ссылка на роль безопасности, включая имя, определённое ссылкой, распространяется на компонент, дескриптор развёртывания которого содержит элемент дескриптора развёртывания security-role-ref.


Назад Вперёд Содержание
Логотип Oracle  Copyright © 2017, Oracle и/или её дочерних компаний. Все права защищены. Версия перевода 1.0.5 (Java EE Tutorial — русскоязычная версия)