JAVA EE: Разработка web-приложения. JAAS. Session.

. Генерация токена

Для своего примера я взял одну из

спецификации JWT. Токен генерируется следующим образом:

. Реализация фильтра

Фильтр — это объект класса, реализующего интерфейс

javax.servlet.Filter

. Менеджер аутентификации.


Менеджер аутентификации — это объект класса, реализующего интерфейс

org.springframework.security.authentication.AuthenticationManager

с единственным методом

authenticate()

. Данному методу нужно передать частично заполненный объект, реализующий интерфейс

org.springframework.security.core.Authentication

(контекстом безопасности приложения).


Задача менеджера аутентификации — в случае успешной аутентификации заполнить полностью объект

Authentication

и вернуть его. При заполнении нужно установить пользователя (

principal

), его права (

authorities

), выполнить

setAuthenticated(true)

. В случае неудачи менеджер аутентификации должен выбросить исключение

AuthenticationException

Приведём пример реализации интерфейса org.springframework.security.core.Authentication:

Технологии

Для решения используем фреймворк Spring Boot и Spring Web, для него требуется:

  1. Java 8 ;

  2. Apache Maven

Авторизация и валидация будет выполнена силами Spring Security и JsonWebToken (JWT).Для уменьшения кода использую Lombok.

. Как всё это собрать вместе

Во-первых, нужно установить фильтр. Сделать это можно 2-мя способами

Первый способ — определить фильтр в файле web.xml нашего приложения

При таком способе в конструкторе фильтра нужно сразу задать менеджер аутентификации, так как экземпляр фильтра не будет доступен в контексте приложения Spring. Если необходимо иметь фильтр или менеджер аутентификации в качестве бинов Spring, нужно воспользоваться вторым способом.

Второй способ — установка фильтра в конфигурации Spring Security.

Для примера покажем конфигурацию с использованием Java Config

Создание приложения

Переходим к практике. Создаем Spring Boot приложение и реализуем простое REST API для получения данных пользователя и списка пользователей.

1 Создание Web-проекта

Создаем Maven-проект SpringBootSecurityRest. При инициализации, если вы это делаете через Intellij IDEA, добавьте Spring Boot DevTools, Lombok и Spring Web, иначе добавьте зависимости отдельно в pom-файле.

2 Конфигурация pom-xml

После развертывания проекта pom-файл должен выглядеть следующим образом:

  1. Должен быть указан parent-сегмент с подключенным spring-boot-starter-parent;

  2. И установлены зависимости spring-boot-starter-web, spring-boot-devtools и Lombok.

3 Создание ресурса REST

Разделим все классы на слои, создадим в папке com.springbootsecurityrest четыре новые папки:

Spring Security

Простенькое REST API написано и пока оно открыто для всех. Двигаемся дальше, теперь его необходимо защитить, а доступ открыть только авторизованным пользователям. Для этого воспользуемся Spring Security и JWT.

Spring Security это Java/JavaEE framework, предоставляющий механизмы построения систем аутентификации и авторизации, а также другие возможности обеспечения безопасности для корпоративных приложений, созданных с помощью Spring Framework.

JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанный на формате JSON. Как правило, используется для передачи данных для аутентификации в клиент-серверных приложениях. Токены создаются сервером, подписываются секретным ключом и передаются клиенту, который в дальнейшем использует данный токен для подтверждения своей личности.

1 Подключаем зависимости

Добавляем новые зависимости в pom-файл.

2 Генерация и хранения токена

Начнем с генерации и хранения токена, для этого создадим папку security и в ней создаем класс JwtTokenRepository с имплементацией интерфейса CsrfTokenRepository (из пакета org.springframework.security.web.csrf).

Интерфейс указывает на необходимость реализовать три метода:

  1. Генерация токена в методе generateToken;

  2. Сохранения токена – saveToken;

  3. Получение токена – loadToken.

Генерируем токен силами Jwt, пример реализации метода.

3 Создание нового фильтра для SpringSecurity

Создаем новый класс JwtCsrfFilter, который является реализацией абстрактного класса OncePerRequestFilter (пакет org.springframework.web.filter). Класс будет выполнять валидацию токена и инициировать создание нового. Если обрабатываемый запрос относится к авторизации (путь /auth/login), то логика не выполняется и запрос отправляется далее для выполнения базовой авторизации.

6 Обработка ошибок

Что бы видеть ошибки авторизации или валидации токена, необходимо подготовить обработчик ошибок. Для этого создаем новый класс GlobalExceptionHandler в корне com.springbootsecurityrest, который является расширением класса ResponseEntityExceptionHandler с реализацией метода handleAuthenticationException.

Метод будет устанавливать статус ответа 401 (UNAUTHORIZED) и возвращать сообщение в формате ErrorInfo.

7 Настройка конфигурационного файла Spring Security.

Все данные подготовили и теперь необходимо настроить конфигурационный файл. В папке com.springbootsecurityrest создаем файл SpringSecurityConfig, который является реализацией абстрактного класса WebSecurityConfigurerAdapter пакета org.springframework.security.config.annotation.web.configuration. Помечаем класс двумя аннотациями: Configuration и EnableWebSecurity.

Jaas. настройка политики безопасности в дескриптере развертывания

Теперь начинается самое интересное. Для начала нам необходимо задать в настройках параметры механизма авторизации.

Java authentication and authorization service (jaas) reference guide

To authorize access to resources, applications first need to authenticate the source of the request. The JAAS framework defines the term subject to represent the source of a request. A subject may be any entity, such as a person or a service. Once the subject is authenticated, a javax.security.auth.Subject is populated with associated identities, or Principals. A Subject may have many Principals. For example, a person may have a name Principal (“John Doe”) and a SSN Principal (“123-45-6789”), which distinguish it from other subjects.

A Subject may also own security-related attributes, which are referred to as credentials; see the section Credentials. Sensitive credentials that require special protection, such as private cryptographic keys, are stored within a private credential Set. Credentials intended to be shared, such as public key certificates, are stored within a public credential Set. Different permissions (described below) are required to access and modify the different credential Sets.

Subjects are created using these constructors:

    public Subject();

    public Subject(boolean readOnly, Set principals,
                   Set pubCredentials, Set privCredentials);

The first constructor creates a Subject with empty (non-null) Sets of Principals and credentials. The second constructor creates a Subject with the specified Sets of Principals and credentials. It also has a boolean argument which can be used to make the Subject read-only. In a read-only Subject, the Principal and credential Sets are immutable.

An application writer does not have to instantiate a Subject. If the application instantiates a LoginContext and does not pass a Subject to the LoginContext constructor, the LoginContext instantiates a new empty Subject. See the LoginContext section.

If a Subject was not instantiated to be in a read-only state, it can be set read-only by calling the following method:

    public void setReadOnly();

A javax.security.auth.AuthPermission with target “setReadOnly” is required to invoke this method. Once in a read-only state, any attempt to add or remove Principals or credentials will result in an IllegalStateException being thrown. The following method may be called to test a Subject‘s read-only state:

    public boolean isReadOnly();

To retrieve the Principals associated with a Subject, two methods are available:

    public Set getPrincipals();
    public Set getPrincipals(Class c);

The first method returns all Principals contained in the Subject, while the second method only returns those Principals that are an instance of the specified Class c, or an instance of a subclass of Class c. An empty set will be returned if the Subject does not have any associated Principals.

To retrieve the public credentials associated with a Subject, these methods are available:

    public Set getPublicCredentials();
    public Set getPublicCredentials(Class c);

The behavior of these methods is similar to that for the getPrincipals methods, except in this case the public credentials are being obtained.

To access private credentials associated with a Subject, the following methods are available:

    public Set getPrivateCredentials();
    public Set getPrivateCredentials(Class c);

The behavior of these methods is similar to that for the getPrincipals and getPublicCredentials methods.

To modify or operate upon a Subject‘s PrincipalSet, public credential Set, or private credential Set, callers use the methods defined in the java.util.Set class. The following example demonstrates this:

    Subject subject;
    Principal principal;
    Object credential;

    . . .

    // add a Principal and credential to the Subject
    subject.getPrincipals().add(principal);
    subject.getPublicCredentials().add(credential);

Note: An AuthPermission with target “modifyPrincipals”, “modifyPublicCredentials”, or “modifyPrivateCredentials” is required to modify the respective Sets. Also note that only the sets returned via the getPrincipals(), getPublicCredentials(), and getPrivateCredentials() methods with no arguments are backed by the Subject‘s respective internal sets. Therefore any modification to the returned set affects the internal sets as well. The sets returned via the getPrincipals(Class c), getPublicCredentials(Class c), and getPrivateCredentials(Class c) methods are not backed by the Subject‘s respective internal sets. A new set is created and returned for each such method invocation. Modifications to these sets will not affect the Subject‘s internal sets.

In order to iterate through a Set of private credentials, you need a javax.security.auth.PrivateCredentialPermission to access each credential. See the PrivateCredentialPermission API documentation for further information.

A Subject may be associated with an AccessControlContext (see the doAs and doAsPrivileged method descriptions below). The following method returns the Subject associated with the specified AccessControlContext, or null if no Subject is associated with the specified AccessControlContext.

    public static Subject getSubject(final AccessControlContext acc);

An AuthPermission with target “getSubject” is required to call Subject.getSubject.

The Subject class also includes the following methods inherited from java.lang.Object.

    public boolean equals(Object o);
    public String toString();
    public int hashCode();

Пользователи, группы, роли

Для тех кто уже работал с системы распределенного доступа, вполне понятна схема разделения пользователей по группам, а группы (или пользователей) по ролям. Вкратце объясню не посвященным.

Пользователь является уникальной личностью для сервера.  По желанию можно кластер пользователей объединить в группы, которые можно назвать как набор авторизованных пользователей. Для того, чтобы указать, какие пользователи и/или группы имеют доступ к защищенным ресурсам, необходимо создать роли.

В официальной документации от oracle есть хороший рисунок иллюстрирующий приведенное выше описание:
Пользователи, группы, роли

Применение jaas в web-приложениях на glassfish v2

На этот раз хочется написать про применение JAAS (Java Authentification and Authorization Service) для веб-приложений. Для начала рассмотрим простой контроль доступа к веб-ресурсам и авторизацию. Я попытаюсь раскрыть основную идею, а также дам подсказку по способу развёртывания (позже из текста станет понятно в чём проблема).

Прежде чем переходить к практике, следует осветить несколько основных понятий и общую схему контроля доступа к веб-ресурсам.

Когда веб-контейнер «тречит» сессии всех подключающихся к нему машин, то с каждой такой сессией могут быть ассоциированы роли, а также user principal. Каждая роль связана с возможностью или невозможностью доступа к некоторому набору веб-ресурсов, а также осуществления некоторых действий (privileged action).

Изначально сессия пользователя не связана ни с каким user principal’ом и ни с одной ролью. Это означает, что если на доступ к некоторому набору веб-ресурсов наложено ограничениче (constraint), то он не будет иметь доступ к этим ресурсам.

Для того, чтобы пользовательская сессия стала ассоциироваться с каким-то набором ролей, пользователь должен пройти авторизацию.

Авторизация осущестляется с помощью JAAS. Во время авторизации пользователь посылает свои креденциалы (credentials, в простом случае это могут быть логин: пароль или пользовательский сертификат). На сервере с процессом контроля связаны две сущности, а именно Realm и Login Module. Login Module осуществляет проверку связи пользователя с каким-то набором групп пользователей (не путать с ролями). Кроме того, авторизация может быть пройдена успешно, однако, пользователь может быть не связан ни с одной из групп.

Веб-приложение может определять соотношение между группами и ролями. Приложение задаёт это соответствие с помощью sun-web.xml.

Login Module и Realm не являются частью веб-приложения, а являются разделяемыми ресурсами сервера приложений, так что они должны быть в classpath сервера и должны быть соответствующим образом зарегистрировны в сервере (в login.conf и domain.xml). Приложение выбирает realm по имени через web.xml.

Приложение определяет набор ограничений на доступ к своим ресурсам через web.xml и указывает: какие веб-реурсы (по шаблону URL (url pattern)) могут быть доступны для каких видов запросов (get, post, head, etc) и для каких ролей. Кроме того, приложение определяет способ авторизации. Например, можно указать страницу логина или задать использование стандартной HTTP-авторизции (браузер показывает окно авторизации).

За выполнением ограничений средит веб-контейнер, так что приложению не требуется выполнять дополнительные проверки. Поскольку ограничения накладываются на шаблон URL, то не имеет значения что подпадает под этот шаблон, будь то сервлет, JSP или же даже ничего (404). Даже если ничего (404), а ограничение требует наличия у пользователя некоторой роли, то ему придётся пройти авторизацию для того чтобы увидеть, что там ничего нет.

Каждый realm может быть ассоциирован с каким-нибудь Login Module. Для этого, в свойствах (properties) для него указывает специальная пара «jaas-context» -> «login-module-name». Иногда Realm и Login Module работают только в паре. Например, если мы захотим написать свой Login Module и Realm, то вполне возможно захочется чтобы наш Realm мог работать только с нашим же Login Module и ни с каким другим.

Для примера создадим простую пару для авторизации по паролю.
Начнём с написания Realm’а:

public class TestRealm extends AppservRealm {

    @Override
    public String getAuthType() {
        return "magic";
    }

    @Override
    public Enumeration getGroupNames( String string ) throws InvalidOperationException, NoSuchUserException {
        return Collections.enumeration( Arrays.asList( "users", "guests" ) );
    }

    @Override
    protected void init( Properties props ) throws BadRealmException, NoSuchRealmException {
        super.init( props );

        System.err.println( "Realm:: Hello!!" );

        if( props.containsKey( JAAS_CONTEXT_PARAM ) )
            setProperty( JAAS_CONTEXT_PARAM, props.getProperty( JAAS_CONTEXT_PARAM ) );
    }

    @Override
    public AuthenticationHandler getAuthenticationHandler() {
        return null;
    }

}

Метод getGroupNames возвращает enum с возможными группами. Метод init вызывается при инициализации (по факту — при старте домена). В нём мы можем выполнить требуемую инициализацию (например, достать необходимые паретры из props, а затем куда-то сохранить их для последующего использования). Очень важно не забыть передать свойство JAAS_CONTEXT_PARAM («jaas-context»), иначе реальм не будет работать.

Далее, опишем наш Login Module. Будем использовать авторизацию по паролю.

public class TestLoginModule extends AppservPasswordLoginModule {

    @Override
    protected void authenticateUser() throws LoginException {
        if( _username == null || _password == null )
            throw new LoginException( "Username of password is null" );

        if( "user".equals( _username ) && "user-pass".equals( _password ) )
            commitUserAuthentication( new String[]{ "users" } );
        else if ( "guest".equals( _username ) && "guest".equals( _password ) )
            commitUserAuthentication( new String[]{ "guests" } );
        else
            throw new LoginException( "bad login/password" );
    }

}

В этом примере мы просто «забили» варианты паролей. Вместо этого можно было бы «спросить» у БД или у LDAP-сервера, а может, даже, и то, и другое.

После сборки получим простой jar с двумя классами (для успешной сборки в classpath надо иметь javaee.jar, appserv-rt.jar и appserv-ext.jar и директории glassfish-v2/lib).

После сборки, наш jar можно положить, например, в директорию lib домена.

Далее, Realm и Login Module следует зарегистрировать в домене.
Для начала, надо позаботиться о том, чтобы наш .jar был в classpath сервера. Вероятно, существует неколько способов это сделать надлежащим образом, однако, в документации по теме описывается один конкретный, вот его-то мы и будем использовать.

В файле domain.xml среди всего прочего описываются параметры jvm. Там можно найти тэг java-config. У него есть аттрибут classpath-suffix. Обычно он пуст. Вот туда-то нам и надо «вписаться».
Например:

...
<java-config classpath-suffix="/home/cy6ergn0m/.domains/domain1/lib/MyTestRealm.jar"  ...
....

После этого, надо добавить наш Login Module. Это делается в текстовом файле login.conf в директории домена (domain_dir/conf/login.conf). Можно просто дописать в конец, например, так:


testRealmLM {
        cy6ergn0m.auth.TestLoginModule required;
};

Когда метод логина описан, можно зарегистрировать наш именованный Realm. Его можно добавить в domain.xml или через web admin console. Мы сделаем это через domain.xml.

Среди тэгов верхнего уровня можно найти тэг security-service. Обычно в нём уже есть несколько тэгов auth-realm. Мы добавим туда же и свой. Однако, важно, чтобы наш тэг был после auth-realm’ов, которые уже есть, т.к. схема domain.xml требует, чтобы auth-realm’ы были в начале.

<auth-realm classname="cy6ergn0m.auth.TestRealm" name="testRealm">
          <property name="jaas-context" value="testRealmLM"/>
          <property name="auth-type" value="magic"/>
</auth-realm>

Среди параметров (properties) мы можем передать и другие. Эти свойства потом попадают в метод init нашего Realm’а. Сюда мы можем положить, например, адреспорт сервера или ещё какие-то настройки.

Следует также заметить, что можно создать несколько реалмов, но с разными параметрами. Login Module в свою очередь всегда может узнать, который из реалмов его использует с помощью protected-поля _currentRealm. Также, таким способов он может получить у реалма какие-то специфичные настройки (хост, порт, и т.п.).

Теперь, следует перезапутить домен. При старте домена в логе мы сможем увидеть нашу запись: «Realm:: Hello!!». Если этого не произошло, то скорее всего вы что-то сделали неверно.

Итак, наш модуль авторизации готов к употреблению. Попробуем воспользоваться им.

Для этого напишем крошечное web приложение, которое будет использовать наш модуль.

Создадим несколько web-страниц, для начала сделаем три: index.html, secret.html и for-guests.html.
Сделаем ссылки с index.html на остальные две.

После этого, создадим ограничения на доступ к нашим «страшно секретным» страницам. Для этого придётся редактировать файлы web.xml и sun-web.xml.

Наш модуль авторизации умеет авторизовать пользователей двух групп: «user» и «guest. Давайте создадим две роли соответствующие этим группам.

Откроем файл sun-web.xml и добавим эти соотношения. Для этого, добавим в него две секции <security-role-mapping>:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd">
<sun-web-app error-url="">
  <context-root>/TestRealmClient</context-root>
  <security-role-mapping>
    <role-name>user</role-name>
    <group-name>users</group-name>
  </security-role-mapping>
  <security-role-mapping>
    <role-name>guest</role-name>
    <group-name>guests</group-name>
  </security-role-mapping>
  <class-loader delegate="true"/>
  <jsp-config>
    <property name="keepgenerated" value="true">
      <description>Keep a copy of the generated servlet class' java code.</description>
    </property>
  </jsp-config>
</sun-web-app>

Теперь опишем те роли, которые хотим использовать в web.xml:

    <security-role>
        <description/>
        <role-name>user</role-name>
    </security-role>
    <security-role>
        <description/>
        <role-name>guest</role-name>
    </security-role>

Теперь можно создавать ограничение (constraint). Для этого исправим web.xml и добавим в него ограничения:

    <security-constraint>
        <display-name>Constraint1</display-name>
        <web-resource-collection>
            <web-resource-name>secrets</web-resource-name>
            <description/>
            <url-pattern>/secret*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>
    <security-constraint>
        <display-name>Constraint2</display-name>
        <web-resource-collection>
            <web-resource-name>guests</web-resource-name>
            <description/>
            <url-pattern>/for-guests*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>guest</role-name>
            <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>

Таким образом мы определили, что доступ к страницам /secret* будет только для пользователей с ролью user, а адреса /for-guests* для пользователей с ролями user и guest. Это означает, что пользователь с ролью user может посещать все страницы, пользователь с ролью guest только index.html и for-guests.html, а „никто“ — только index.html.

Теперь мы должны указать realm и способ авторизации. Для начала выберем простой способ (HTTP-авторизация) — BASIC. Для этого в web.xml укажем:

    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>testRealm</realm-name>
    </login-config>

Таким образом, в конце у нас получится следующий web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 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/web-app_2_5.xsd">
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    <security-constraint>
        <display-name>Constraint1</display-name>
        <web-resource-collection>
            <web-resource-name>secrets</web-resource-name>
            <description/>
            <url-pattern>/secret*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>
    <security-constraint>
        <display-name>Constraint2</display-name>
        <web-resource-collection>
            <web-resource-name>guests</web-resource-name>
            <description/>
            <url-pattern>/for-guests*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>guest</role-name>
            <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>testRealm</realm-name>
    </login-config>
    <security-role>
        <description/>
        <role-name>user</role-name>
    </security-role>
    <security-role>
        <description/>
        <role-name>guest</role-name>
    </security-role>
</web-app>

В редакторе web.xml в netbeans это будет выглядеть следующим образом:

web.xml in netbeans

Готово. Теперь приложение можно запустить и проверить. При попытке перейти по одной из ссылок возникнет окно авторизации.

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

Достичь этого мы можем двумя способами. Расмотрим оба.

Для начала мы должны изменить способ авторизации в web.xml. Ранее вы указывать метод авторизции BASIC. Теперь мы выберем логин-форму.

    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>testRealm</realm-name>
        <form-login-config>
            <form-login-page>/login.jsp</form-login-page>
            <form-error-page>/login.jsp?fail</form-error-page>
        </form-login-config>
    </login-config>

Теперь создадим форму логина login.jsp согласно первому способу, более простому:

<%@page contentType="text/html" pageEncoding="windows-1251"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>Hello World!</h1>

        <h2>Web container's login method</h2>
        <form action="j_security_check" method="post">
            <input type="text" name="j_username" />
            <input type="password" name="j_password" />
            <input type="submit" />
        </form>
    </body>
</html>

Готово. Теперь, когда неавторизованный пользователь будет пытаться пройти на „защищённые“ страницы, веб-контейнер будет „редиректить“ его на нашу форму логина. После авторизции пользователь получит необходимые роли и сможет посещать „защищённые“ страницы.

В некоторых случаях нам нужем больший контроль за процессом авторизации и какой-то неконтролируемый нами j_security_check нас не устраивает. В таком случае, мы всегда можем воспользоваться вторым способом. Мы можем вручную выполнить логин. Поскольку Glassfish использует нестандартный логин-модуль (не случайно мы реализовывали AppservPasswordLoginModule вместо стандартного JAAS интерфейса), то мы не можем воспользоваться обычным путём авторизации через LoginContext, а должны использовать нестандартное API сервера (точнее можем, но это сложнее и всё равно непереносимо).

Мы создадим LoginServlet, который будет использовать ProgrammaticLogin из API Glassfish’а [7]. Следует обратить внимание, что для того, чтобы класс бы виден, необходимо добавить файлы javaee.jar, appserv-rt.jar и appserv-ext.jar в classpath при сборке (и в вашей IDE, что возможно одно и то же).

public class LoginServlet extends HttpServlet {


    @Override
    protected void doGet( HttpServletRequest request, HttpServletResponse response )
            throws ServletException, IOException {
        response.sendError( HttpServletResponse.SC_FORBIDDEN );
    }

    @Override
    protected void doPost( HttpServletRequest request, HttpServletResponse response )
            throws ServletException, IOException {
        ProgrammaticLogin pl = new ProgrammaticLogin();
        try {
            Boolean rc = pl.login( request.getParameter( "name"), request.getParameter( "pass"), "testRealm", request, response, true );
            if( rc != null && rc.booleanValue() ) {
                response.sendRedirect( "index.jsp" );
                return;
            }
        } catch( Exception ex ) {
            Logger.getLogger( LoginServlet.class.getName() ).log( Level.SEVERE, null, ex );
        }

        response.sendRedirect( "login.jsp?fail" );
    }

    @Override
    public String getServletInfo() {
        return "Login servlet";
    }

}

А страница login.jsp изменим соответственно так, чтобы параметры формы направляли в LoginSerlvet

<%@page contentType="text/html" pageEncoding="windows-1251"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>Hello World!</h1>

        <h2>Web container's login method</h2>
        <form action="login" method="post">
            <input type="text" name="name" />
            <input type="password" name="pass" />
            <input type="submit" />
        </form>
    </body>
</html>

Таким образом, получим сервлет логина, который можем использовать любым удобным для нас способом, например, мы можем ещё сделать „каптчу“, или проверять логин аяксом и многое другое.

В заключение хочется осветить ещё один момент. Дело в том, что регистрация Realm и Login Module не слишком простая, то хотелось бы иметь возможность автоматически регистрировать их в произвольном домене.

Для реализации задумки можно сделать свою собственную „таску“ (Custom Task) для Ant. Её можно написать на Java и выполнить все необходимые действия.

По служебному долгу пришлось написать подобную вещь, но, по понятным причинам не могу привести тут этот код. Однако, дам несколько намёков о её содержании. К файлу login.conf можно легко дописать пару строчек через FileWriter, созданный с парметром append=true. С манипуляциями над domain.xml сложнее, однако, это ведь XML, к тому же обозримого размера… стало быть, можно с помощью DocumentBuilder’а прочеть весь XML в DOM-дерево, потом внести необходимые поправки в дерево, добавить classpath-suffix и auth-realm, а потом серилизовать DOM-дерево в domain.xml обратно. У меня получилось уложиться в 200 строк вместе с проверками входных параметов. Такую таску можно включить в ant-скрипты создания всего домена (если домен создаётся ant’ом).

Несмотря на то, что тестировал я всё в glassfish v2, однако, судя по разного рода источникам, можно надеяться, что для v3 это также актуально, хотя возможно потребует корректировок.

На этом непростая статья подошла к концу. Конечно, многое осталось за кадром (privileged actions, user principals), но я надеюсь, что мне удалось пролить свет на эту непростую нишу веб-разработки.

Успехов.

CG.

Список литературы ака ссылки:

1. Authentication Using Custom Realms in Sun Java System Application Server
developers.sun.com/appserver/reference/techart/as8_authentication

2. Using JAAS with Tomcat
www.kopz.org/public/documents/tomcat/jaasintomcat.html

3. JAAS Tomcat Login Module
www.owasp.org/index.php/JAAS_Tomcat_Login_Module

4. JavaTM Authentication and Authorization Service (JAAS). LoginModule Developer’s Guide.
java.sun.com/javase/6/docs/technotes/guides/security/jaas/JAASLMDevGuide.html

5. Sun GlassFish Enterprise Server v3 Application Development Guide
Chapter 5 Securing Applications
docs.sun.com/app/docs/doc/820-7695/beabg?l=ru&q=glassfish JAAS&a=view

6. Ветка на форуме про веб-логин через j_security_check
www.sql.ru/Forum/actualthread.aspx?tid=624508

7. Javadoc Class ProgrammaticLogin
glassfish.dev.java.net/nonav/docs/v3/api

8. Sun GlassFish Enterprise Server v3 Application Development Guide
Programmatic Login
docs.sun.com/app/docs/doc/820-7695/beacm?l=ru&a=view

9. Implement JAAS based Authentication and Authorization for ADF Faces applications on OC4J 10.1.3
technology.amis.nl/blog/1426/implement-jaas-based-authentication-and-authorization-for-adf-faces-applications-on-oc4j-1013

10. Securing a Web Application on Glassfish using JAAS — Part 1 and 2
www.developinjava.com/features/47-enterprise-java/105-securing-a-web-application-on-glassfish-using-jaas.html
www.developinjava.com/features/47-enterprise-java/106-securing-a-web-application-on-glassfish-using-jaas-pt-2.html

11. Glassfish javadoc: Class AppservRealm
glassfish.java.net/nonav/javaee5/api/com/sun/appserv/security/AppservRealm.html

Создание защищенного контента

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

Создание страниц авторизации

Выполните следующие действия:

Способ второй: digest authentication

Digest authentication

Способ первый: basic authentication

Basic Authentication

Способ пятый: certificate authentication

. Сертификаты бывают двух типов:

Ниже приведено несколько шагов, как создать Self signed сертификат с помощью утилиты keytool.

generate client and server keyskeytool -genkey -keystore keystore_client -alias clientKeykeytool -genkey -keystore keystore_server -alias serverKey

generate client and server certificateskeytool -export -alias clientKey -rfc -keystore keystore_client > client.certkeytool -export -alias serverKey -rfc -keystore keystore_server > server.cert

import certificates to corresponding truststoreskeytool -import -alias clientCert -file client.cert -keystore truststore_serverkeytool -import -alias serverCert -file server.cert -keystore truststore_client

Теперь полученные сертификаты надо добавить в конфигурацию нашего сервера. В данном, случае используется Tomcat

Способ четвертый: digital signature (public/private key pair)

Идея этого подхода заключается в использовании

. Суть состоит в том что любой может обратится к рест сервису и получить беспорядочный набор символов, а точнее шифрованный ответ от сервера и только владелец приватного ключа сможет его расшифровать.


И так по порядку.

Способ шестой: oauth2 authorization

Ну и на закуску я оставил самый сложны для понимания и реализации способ. Зато он очень гибкий и хорошо подходит для больших порталов. Опять же не буду заниматься копипастом, чтобы почитать, что такое OAuth и как он работает идем

Spring security предоставляет нам класс OAuthTemplate, который значительно облегчает нам жизнь.


Все идеи для реализации своей OAuth имплементации я почерпнул из этой

Заключение

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

Перед выбором security для вашего проекта четко решите, что вам надо, взвесьте все за и против. Не стоит усложнять систему, если этого не требует проект.

Надеюсь, Ваши приложения будут безопасными и надёжными.

Похожее:  Tutorial to create a login system using HTML, PHP, and MySQL / Хабр

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *