Web App с IoC Starter. Базовый маппинг запросов, используя ioc context, ioc web и ioc orm

    image

    Введение


    С момента первого релиза прошло достаточно много времени (ссылка на предыдущую статью). Что изменилось?


    • улучшена стабильность системы в целом;
    • реализована ленивая загрузка компонентов;
    • встроена базовая система слушателей;
    • встроена поддержка аспектно-ориентированного программирования (для средней сложности решения задач, в остальном все же советую использовать — AspectJ библиотеку)
    • новый загрузчик RequestFactory
    • встроена работа с кешем на базе EhCache, Guava
    • встроена работа с потоками (как инициализация посредством аннотации @SimpleTask, так и прямая работа с пулом)

    **Модули


    • модуль работы с базой (легковесный ORM с поддержкой JPA, Transactions, NO-SQL Driver — Orient, Crud methods, repository system и автогенерацией запросов из функции класса-репозитория)
    • модуль работы с веб-мордой (маппинг линков посредством аннотаций, поддержка кастомных producers/consumes, Velocity Template Rendering Page, Basic Security Requests, Sessions, Cookies, SSL) на базе Netty 4.1.30.Final

    Структура фреймворка
    struct


    "Это конечно же все хорошо,"- скажите Вы, -"но по факту работает ли это все?".
    "Да, работает. Прошу под кат".


    Процесс реализации примера


    Для реализации примера я буду использовать Maven 3 и Intelijj Idea 2018.2.


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


    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://maven.apache.org/POM/4.0.0"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <artifactId>example-webapp</artifactId>
        <groupId>org.ioc</groupId>
        <packaging>jar</packaging>
    
        <version>0.0.1</version>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <.source>1.8</source>
                        <target>1.8</target>
                    </configuration>
    
                    <executions>
                        <execution>
                            <id>default-testCompile</id>
                            <phase>test-compile</phase>
                            <goals>
                                <goal>testCompile</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
        <dependencies>
            <dependency>
                <groupId>org.ioc</groupId>
                <artifactId>context-factory</artifactId>
                <version>2.2.4.STABLE</version>
            </dependency>
    
            <dependency>
                <groupId>org.ioc</groupId>
                <artifactId>orm-factory</artifactId>
                <version>2.2.4.STABLE</version>
            </dependency>
    
            <dependency>
                <groupId>org.ioc</groupId>
                <artifactId>web-factory</artifactId>
                <version>2.2.4.STABLE</version>
            </dependency>
        </dependencies>
    
        <repositories>
            <repository>
                <id>context</id>
                <url>https://raw.github.com/GenCloud/ioc_container/context</url>
            </repository>
            <repository>
                <id>cache</id>
                <url>https://raw.github.com/GenCloud/ioc_container/cache</url>
            </repository>
            <repository>
                <id>threading</id>
                <url>https://raw.github.com/GenCloud/ioc_container/threading</url>
            </repository>
            <repository>
                <id>orm</id>
                <url>https://raw.github.com/GenCloud/ioc_container/orm</url>
            </repository>
            <repository>
                <id>web</id>
                <url>https://raw.github.com/GenCloud/ioc_container/web</url>
            </repository>
        </repositories>
    </project>

    **На maven central пока не переехал, увы.


    Структура проекта:
    структура
    Стандартный MVC паттерн, не правда ли?


    Создадим точку входа в приложение:


    package org.examples.webapp;
    
    import org.ioc.annotations.context.ScanPackage;
    import org.ioc.annotations.modules.CacheModule;
    import org.ioc.annotations.modules.DatabaseModule;
    import org.ioc.annotations.modules.ThreadingModule;
    import org.ioc.annotations.modules.WebModule;
    import org.ioc.context.starter.IoCStarter;
    
    @WebModule
    @CacheModule
    @ThreadingModule
    @DatabaseModule
    @ScanPackage(packages = {"org.examples.webapp"})
    public class AppMain {
        public static void main(String[] args) {
            IoCStarter.start(AppMain.class);
        }
    }

    **Пояснения:
    Аннотация @ScanPackages — определяет контексту пакеты для выявления компонентов (в простонародии — "бинов").
    Аннотация @WebModule — служит для подключения и инициализации web фабрики.
    Аннотация @CacheModule — служит для подключения и инициализации фабрики кеша, используется для корректной работы ORM (в будущих версиях аннотация не будет требоваться).
    Аннотация @ThreadingModule — служит для подключения и инициализации фабрики потоков, используется для корректной работы web фабрики (в будущих версиях аннотация не будет требоваться).
    Аннотация @DatabaseModule — служит для подключения и инициализации фабрики ORM.
    Все фабрики имеют дефолтные конфигураторы, которые можно изменить на свои с переопределением функций использующихся настроек фабриками (в каждой аннотации модуля переопределен класс конфигуратор — Class<?> autoConfigurationClass() default WebAutoConfiguration.class), либо же отключить любую конфигурацию посредством аннотации @Exclude в main классе.
    Утилита IoCStarter — главный класс-инициализатор контекста.


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


    Создадим конфигурационный файл для наших модулей. По дефолту все конфиги грузятся из {working_dir}/configs/default_settings.properties — его и создадим по соответствующему пути.


    # Threading
    ioc.threads.poolName=shared
    ioc.threads.availableProcessors=4
    ioc.threads.threadTimeout=0
    ioc.threads.threadAllowCoreTimeOut=true
    ioc.threads.threadPoolPriority=NORMAL
    # Event dispather
    # кол-во дескрипторов (процессоров) для обработки слушателей (асинхронное выполнение)
    ioc.dispatcher.availableDescriptors=4
    # Cache
    # фабрика кеша (EhFactory|GuavaFactory)
    cache.factory=org.ioc.cache.impl.EhFactory
    # Datasource
    # тип базы (локальная-собственная, локальная-серверная или удаленная)
    #LOCAL, LOCAL_SERVER, REMOTE
    datasource.orient.database-type=LOCAL
    # местонахождение базы
    datasource.orient.url=./database
    # имя базы данных (для локальной не обязательно)
    datasource.orient.database=orient
    # пользователь базы
    datasource.orient.username=admin
    # пароль пользователя
    datasource.orient.password=admin
    # конфигурация для маппинга сущностей в базу (create, dropCreate, refresh, none)
    datasource.orient.ddl-auto=dropCreate
    # конфигурация сообщающая менеджеру, показывать сгенерированные запросы или нет
    datasource.orient.showSql=true
    # Web server
    # порт работы веб сервера
    web.server.port=8081
    # нужен ли SSL обработчик
    web.server.ssl-enabled=false
    # in seconds
    # таймаут сессий (дефолтный 7200 сек. = 2 часа)
    web.server.security.session.timeout=300
    # кодировки веб-морды
    web.server.velocity.input.encoding=UTF-8
    web.server.velocity.output.encoding=UTF-8
    # загрузчик веб-морды
    web.server.velocity.resource.loader=file
    # класс загрузчика
    web.server.velocity.resource.loader.class=org.apache.velocity.runtime.resource.loader.FileResourceLoader
    # путь к находящейся веб-морде
    web.server.velocity.resource.loading.path=./public

    Далее, нам нужны сущность пользователя и ее управляющий репозиторий:
    Реализация сущности TblAccount:


    package org.examples.webapp.domain.entity;
    
    import org.ioc.web.security.user.UserDetails;
    
    import javax.persistence.*;
    import java.util.Collections;
    import java.util.List;
    
    @Entity
    public class TblAccount implements UserDetails {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        private long id;
    
        @Column(name = "username")
        private String username;
    
        @Column(name = "password")
        private String password;
    
        @Transient
        private String repeatedPassword;
    
        public String getRepeatedPassword() {
            return repeatedPassword;
        }
    
        public void setRepeatedPassword(String repeatedPassword) {
            this.repeatedPassword = repeatedPassword;
        }
    
        @Override
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public List<String> getRoles() {
            return Collections.singletonList("ROLE_USER");
        }
    }

    **Пояснения:
    Все стандартно как и у всех JPA-поддерживающих фреймворков. Маппим сущность с помощью @.Entity, создаем Primary Key с помощью аннотации @.Id, маппим колонки с помощью аннотации @Column. и унаследуемся от UserDetails для идентификации сущности в Security модуле.


    Реализациия репозитория сущности TblAccountRepository:


    package org.examples.webapp.domain.repository;
    
    import org.examples.webapp.domain.entity.TblAccount;
    import org.ioc.annotations.context.IoCRepository;
    import org.ioc.orm.repositories.CrudRepository;
    
    import javax.transaction.Transactional;
    
    @IoCRepository
    public interface TblAccountRepository extends CrudRepository<TblAccount, Long> {
        @Transactional
        TblAccount findByUsernameEq(String username);
    }

    **Пояснения:
    Аннотация @IoCRepository — служит для определения контекстом, что класс является репозиториеми его нужно обработать "по-другому".
    Поддерживает стандартные CRUD функции:


    • Entity fetch(ID id) — достает сущность типа Entity из базы, либо из кеша по Primary Key;
    • List fetchAll() — достает все сущности типа Entity, предварительно загружая их в кеш;
    • void save(Entity entity) — создает/обновляет сущность типа Entity как в базе так и в кеше;
    • void delete(Entity entity) — удаляет сущность типа Entity как из базы так и из кеша;
    • boolean exists(ID id) — проверяет наличие сущности в базе по Primary Key.
      Все CRUD-запросы происходят в транзакции.

    Поддерживает автогенерацию запросов посредством переопределения функций с ключевыми словами, как в реализации выше (TblAccount findByUsernameEq(String username)) и вызов зарегистрированных запросов (NamedQuery)


    Функция findByUsernameEq(String username) — осуществляет поиск сущности по ее полю username. Сгенерированный запрос:


         select * from tbl_account where username = 'username'

    Далее нам понадобиться, уровень для управления бизнес-логикой.
    Реализации AccountService:


    package org.examples.webapp.service;
    
    import org.examples.webapp.domain.entity.TblAccount;
    import org.examples.webapp.domain.repository.TblAccountRepository;
    import org.examples.webapp.responces.IMessage;
    import org.ioc.annotations.context.IoCComponent;
    import org.ioc.annotations.context.IoCDependency;
    import org.ioc.web.model.http.Request;
    import org.ioc.web.security.configuration.SecurityConfigureAdapter;
    import org.ioc.web.security.encoder.bcrypt.BCryptEncoder;
    import org.ioc.web.security.user.UserDetails;
    import org.ioc.web.security.user.UserDetailsProcessor;
    
    import java.util.Objects;
    
    import static org.examples.webapp.responces.IMessage.Type.ERROR;
    import static org.examples.webapp.responces.IMessage.Type.OK;
    
    @IoCComponent
    public class AccountService implements UserDetailsProcessor {
        @IoCDependency
        private TblAccountRepository tblAccountRepository;
    
        @IoCDependency
        private BCryptEncoder bCryptEncoder;
    
        @IoCDependency
        private SecurityConfigureAdapter securityConfigureAdapter;
    
        @Override
        public UserDetails loadUserByUsername(String username) {
            return tblAccountRepository.findByUsernameEq(username);
        }
    
        public void save(TblAccount tblAccount) {
            tblAccountRepository.save(tblAccount);
        }
    
        public void delete(TblAccount tblAccount) {
            tblAccountRepository.delete(tblAccount);
        }
    
        public IMessage tryCreateUser(String username, String password, String repeatedPassword) {
            if (username == null || username.isEmpty() || password == null || password.isEmpty()
                    || repeatedPassword == null || repeatedPassword.isEmpty()) {
                return new IMessage(ERROR, "Invalid request parameters!");
            }
    
            if (!Objects.equals(password, repeatedPassword)) {
                return new IMessage(ERROR, "Repeated password doesn't match!");
            }
    
            final UserDetails userDetails = loadUserByUsername(username);
            if (userDetails != null) {
                return new IMessage(ERROR, "Account already exists!");
            }
    
            final TblAccount account = new TblAccount();
            account.setUsername(username);
            account.setPassword(bCryptEncoder.encode(password));
    
            save(account);
            return new IMessage(OK, "Successfully created!");
        }
    
        public IMessage tryAuthenticateUser(Request request, String username, String password) {
            if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
                return new IMessage(ERROR, "Invalid request parameters!");
            }
    
            final UserDetails userDetails = loadUserByUsername(username);
            if (userDetails == null) {
                return new IMessage(ERROR, "Account not found!");
            }
    
            if (!bCryptEncoder.match(password, userDetails.getPassword())) {
                return new IMessage(ERROR, "Password does not match!");
            }
    
            securityConfigureAdapter.getContext().authenticate(request, userDetails);
            return new IMessage(OK, "Successfully authenticated");
        }
    
        public IMessage logout(Request request) {
            if (securityConfigureAdapter.getContext().removeAuthInformation(request)) {
                return new IMessage(OK, "/");
            }
    
            return new IMessage(ERROR, "Credentials not found or not authenticated!");
        }
    }

    **Пояснения:
    Аннотация @IoCComponent — служит для инициализации класса как компонента.
    Аннотация @IoCDependency — служит для внедрения зависимостей в инстанс класса.
    Утилита BCryptEncoder — реализация кодека BCrypt для шифрования пароля (пока что единственные кодек).
    Системный инстанс SecurityConfigureAdapter — служит для работы с маппингом запросов и сессиий пользователей.
    Функция UserDetails loadUserByUsername — наследуемая функция UserDetailsProcessor, служит для загрузки пользователя в сессию и выставлении флага аутентификации (в будущем для стандартного маппинга авторизации в Security)
    Функция IMessage tryCreateUser — функция создания пользователя.
    Функция IMessage tryAuthenticateUser — функция аутентификации пользователя.
    Функция IMessage logout — функция очистки сессии от авторизированного пользователя.
    Класс IMessage — класс-утилита для выведении нужной нам информации в браузере (json-ответ).


    package org.examples.webapp.responces;
    
    public class IMessage {
        private final String message;
        private final Type type;
    
        public IMessage(String message) {
            this.message = message;
            type = Type.OK;
        }
    
        public IMessage(Type type, String message) {
            this.message = message;
            this.type = type;
        }
    
        public String getMessage() {
            return message;
        }
    
        public Type getType() {
            return type;
        }
    
        public enum Type {
            OK,
            ERROR
        }
    }

    Теперь понадобиться реализация самой линковки (маппинга запросов):


    package org.examples.webapp.mapping;
    
    import org.examples.webapp.domain.entity.TblAccount;
    import org.examples.webapp.responces.IMessage;
    import org.examples.webapp.service.AccountService;
    import org.ioc.annotations.context.IoCDependency;
    import org.ioc.annotations.web.IoCController;
    import org.ioc.web.annotations.Credentials;
    import org.ioc.web.annotations.MappingMethod;
    import org.ioc.web.annotations.RequestParam;
    import org.ioc.web.annotations.UrlMapping;
    import org.ioc.web.model.ModelAndView;
    import org.ioc.web.model.http.Request;
    
    @IoCController
    @UrlMapping("/")
    public class MainMapping {
        @IoCDependency
        private AccountService accountService;
    
        @UrlMapping
        public ModelAndView index() {
            final ModelAndView modelAndView = new ModelAndView();
            modelAndView.setView("index");
            return modelAndView;
        }
    
        @UrlMapping(value = "signup", method = MappingMethod.POST)
        public IMessage createUser(@RequestParam("username") String username,
                                   @RequestParam("password") String password,
                                   @RequestParam("repeatedPassword") String repeatedPassword) {
            return accountService.tryCreateUser(username, password, repeatedPassword);
        }
    
        @UrlMapping(value = "signin", method = MappingMethod.POST)
        public IMessage auth(Request request,
                             @RequestParam("username") String username,
                             @RequestParam("password") String password) {
            return accountService.tryAuthenticateUser(request, username, password);
        }
    
        @UrlMapping("signout")
        public IMessage signout(Request request) {
            return accountService.logout(request);
        }
    
        @UrlMapping("loginPage")
        public ModelAndView authenticated(@Credentials TblAccount account) {
            final ModelAndView modelAndView = new ModelAndView();
            modelAndView.setView("auth");
    
            modelAndView.addAttribute("account", account);
            return modelAndView;
        }
    }

    **Пояснения:
    Аннотация @IoCController — служит для идентификации класса в контексте, как контроллера (маппера запросов браузера)
    Аннотация @UrlMapping — указывает, что нужно проанализировать функцию/класс на наличие запросов, обрабатываемых хендлерами канала.
    Параметры:


    • value — нужный нам запрос;
    • method — http метод для обработки (GET, POST, PUT, etc.);
    • consumes — http mime type для проверки наличия запроса конкретного типа (опционально);
    • produces — http content-type для отдачи в ответе конкретного типа контента (Content-Type: text/html; charset=utf-8, Content-Type: multipart/form-data; boundary=something, etc. опционально;

    Аннотация @RequestParam — служит для определении имени получаемого параметра из запроса. Поскольку постольку дефолтными средствами рефлексии нельзя получить текущее имя параметра метода, мне было лень подключать лишнюю зависимость javaassist, шаманить с асмом. Поэтому такой себе метод определения имени параметра для внедрения этому параметру значения, полученного из запроса. Существует аналог для GET типа — @PathVariable — тот же самый принцип работы (не совместим с POST).
    Аннотация @Credentials — служит для вставки текущих данных авторизированного пользователя, в противном случаи может быть null, если информации авторизированного пользователя нет в сесии.
    Системный класс Request — текущая информация о поступившем запросе, содержащая в себе коки, хидеры и канал пользователя, который в последствии можно будет отправлять Push Message's… у кого какая фантазия уже на этот счет.
    Класс-утилита ModelAndView — модель страницы с именем ресурса без расширения, и атрибутами для внедрения в ресурс.


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


    package org.examples.webapp.config;
    
    import org.ioc.annotations.configuration.Property;
    import org.ioc.annotations.configuration.PropertyFunction;
    import org.ioc.web.security.configuration.HttpContainer;
    import org.ioc.web.security.configuration.SecurityConfigureProcessor;
    import org.ioc.web.security.encoder.Encoder;
    import org.ioc.web.security.encoder.bcrypt.BCryptEncoder;
    import org.ioc.web.security.filter.CorsFilter;
    import org.ioc.web.security.filter.CsrfFilter;
    
    @Property
    public class SecurityConfig implements SecurityConfigureProcessor {
        @Override
        public void configure(HttpContainer httpContainer) {
            httpContainer.
                    configureRequests().
                    anonymousRequests("/", "/signup", "/signin").
                    resourceRequests("/static/**").
                    authorizeRequests("/loginPage", "ROLE_USER").
                    authorizeRequests("/signout", "ROLE_USER").
                    and().
                    configureSession().
                    expiredPath("/");
        }
    
        @PropertyFunction
        public CsrfFilter csrfFilter() {
            return new CsrfFilter();
        }
    
        @PropertyFunction
        public CorsFilter corsFilter() {
            return new CorsFilter();
        }
    
        @PropertyFunction
        public Encoder encoder() {
            return new BCryptEncoder();
        }
    }

    **Пояснения:
    Аннотация @Property — сообщает контексту, что это конфигурационный файл и его нужно инициализировать.
    Аннотация @PropertyFunction — сообщает анализатору конфигураций, что эта функция возвращает какой-то тип и должен его инициализировать как компонент (бин).
    Интерфейс SecurityConfigureProcessor — утилита служащая для конфигурации маппинга запросов.
    Класс-модель HttpContainer — утилита, хранящая в себе маппинг запросов, указанных пользователем.
    Класс CsrfFilter — фильтр не валидных запросов (реализации механики CSRF).
    Класс CorsFilter — фильтр Cross-Origin Resource Sharing.


    Функция anonymousRequests — принимает в себя неограниченный массив запросов, не требует авторизированных пользователей и проверки ролей (ROLE_ANONYMOUS).
    Функция resourceRequests — принимает в себя неограниченный массив запросов, конкретно служит для определения, по какому пути будет лежать ресурсный файл, не требующей сложной обработки (css, js, images, etc.).
    Функция authorizeRequests — принимает в себя неограниченный массив запросов, требует авторизированного пользователя и конкретную роль, присущую пользователю.
    Функция expiredPath — при очистке истекшей по времени сессии, пользователя перебрасывает по этому маппингу (redirect link).


    Что ж, остались страницы, скрипты и стили сайта (глубоко углубляться не буду).


    Заголовок спойлера

    index.vm — главная страница


    <html>
    <head>
        <meta charset="utf-8"/>
        <title>IoC Test</title>
        <link rel="stylesheet" href="/static/css/bootstrap.min.css">
    
        <link rel="stylesheet" href="/static/css/style.css"/>
        <link rel="stylesheet" href="/static/css/pnotify.custom.min.css"/>
        <link rel="stylesheet" href="/static/css/pnotify.css"/>
        <link rel="stylesheet" href="/static/css/pnotify.buttons.css"/>
    </head>
    <body>
    <div class="container">
        <h1>IoC Starter Test</h1>
        <br>
        <h4>Create user</h4>
        <br>
        <form id="creation">
            <label for="username">Username: </label>
            <input type="text" id="username" name="username" class="color-input-field"/>
            <label for="password">Password: </label>
            <input type="password" id="password" name="password" class="color-input-field"/>
            <label for="repeatedPassword">Repeate: </label>
            <input type="password" id="repeatedPassword" name="repeatedPassword" class="color-input-field"/>
            <button type="button" class="btn btn-success btn-create">Sing up!</button>
        </form>
    
        <h4>Authenticate</h4>
        <br>
        <form id="auth">
            <label for="username">Username: </label>
            <input type="text" id="username" name="username" class="color-input-field"/>
            <label for="password">Password: </label>
            <input type="password" id="password" name="password" class="color-input-field"/>
            <button type="button" class="btn btn-danger btn-auth">Sing in!</button>
        </form>
    </div>
    
    <script type="text/javascript" src="/static/js/jquery.js"></script>
    <script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="/static/js/scripts.js"></script>
    <script type="text/javascript" src="/static/js/pnotify.js"></script>
    <script type="text/javascript" src="/static/js/pnotify.buttons.js"></script>
    
    </body>
    </html>

    auth.vm — для отображения авторизированного пользователя


    <html>
    <head>
        <meta charset="utf-8"/>
        <title>IoC Test</title>
        <link rel="stylesheet" href="/static/css/bootstrap.min.css">
    
        <link rel="stylesheet" href="/static/css/style.css"/>
        <link rel="stylesheet" href="/static/css/pnotify.custom.min.css"/>
        <link rel="stylesheet" href="/static/css/pnotify.css"/>
        <link rel="stylesheet" href="/static/css/pnotify.buttons.css"/>
    </head>
    <body>
    <div class="container">
        <h1>Authorized page</h1>
        <br>
    
        <h4>Test auth data</h4>
        <div id="auth_data">
            #if($!account)
                <h4>Hello @$!account.username, You successfully authenticated!</h4>
                <br>
                <button type="button" class="btn btn-success btn-logout">Logout!</button>
            #end
        </div>
    </div>
    
    <script type="text/javascript" src="/static/js/jquery.js"></script>
    <script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="/static/js/scripts.js"></script>
    <script type="text/javascript" src="/static/js/pnotify.js"></script>
    <script type="text/javascript" src="/static/js/pnotify.buttons.js"></script>
    
    </body>
    </html>

    scripts.js — контроллер, для отправки и получения информации запроса на сервер


    $(function () {
        $(".btn-create").click(function () {
            var cooki = cookie();
            document.cookie = 'CSRF-TOKEN=' + cooki;
    
            $.ajax({
                url: "/signup",
                data: $('#creation').serialize(),
                headers: {'X-CSRF-TOKEN': cooki},
                crossDomain: true,
                xhrFields: {
                    withCredentials: true
                },
                type: "POST"
            }).done(function (data) {
                switch (data.type) {
                    case 'OK':
                        new PNotify({
                            title: 'Success',
                            text: data.message,
                            type: 'success',
                            hide: false
                        });
                        break;
                    case 'ERROR':
                        new PNotify({
                            title: 'Error',
                            text: data.message,
                            type: 'error',
                            hide: false
                        });
                        break;
                }
            });
        });
    
        $(".btn-auth").click(function () {
            var cooki = cookie();
            document.cookie = 'CSRF-TOKEN=' + cooki;
    
            $.ajax({
                url: "/signin",
                data: $('#auth').serialize(),
                headers: {'X-CSRF-TOKEN': cooki},
                crossDomain: true,
                xhrFields: {
                    withCredentials: true
                },
                type: "POST"
    
            }).done(function (data) {
                switch (data.type) {
                    case 'OK':
                        new PNotify({
                            title: 'Success',
                            text: data.message,
                            type: 'success',
                            hide: false
                        });
    
                        setTimeout(function () {
                            window.location = "/loginPage";
                        }, 5000);
                        break;
                    case 'ERROR':
                        new PNotify({
                            title: 'Error',
                            text: data.message,
                            type: 'error',
                            hide: false
                        });
                        break;
                }
            });
        });
    
        $(".btn-logout").click(function () {
            $.ajax({
                url: "/signout",
                crossDomain: true,
                xhrFields: {
                    withCredentials: true
                },
                type: "GET"
            }).done(function (data) {
                switch (data.type) {
                    case 'OK':
                        new PNotify({
                            title: 'Success',
                            text: 'Logouting...',
                            type: 'success',
                            hide: false
                        });
    
                        setTimeout(function () {
                            window.location = data.message;
                        }, 5000);
                        break;
                    case 'ERROR':
                        new PNotify({
                            title: 'Error',
                            text: data.message,
                            type: 'error',
                            hide: false
                        });
                        break;
                }
            });
        });
    });
    
    function cookie(a) {
        return a           // if the placeholder was passed, return
            ? (              // a random number from 0 to 15
                a ^            // unless b is 8,
                Math.random()  // in which case
                * 16           // a random number from
                >> a / 4         // 8 to 11
            ).toString(16) // in hexadecimal
            : (              // or otherwise a concatenated string:
                [1e7] +        // 10000000 +
                -1e3 +         // -1000 +
                -4e3 +         // -4000 +
                -8e3 +         // -80000000 +
                -1e11          // -100000000000,
            ).replace(     // replacing
                /[018]/g,    // zeroes, ones, and eights with
                cookie           // random hex digits
            )
    }

    Компилим, запускаем все что у нас получилось.
    Если все правильно, увидим что-то подобное в конце загрузки:


    Лог

    [21.10.18 22:29:51:990] INFO web.model.mapping.MappingContainer: Mapped method [/], method=[GET], to [public org.ioc.web.model.ModelAndView org.examples.webapp.mapping.MainMapping.index()]
    [21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signup], method=[POST], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.createUser(java.lang.String,java.lang.String,java.lang.String)]
    [21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signin], method=[POST], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.auth(org.ioc.web.model.http.Request,java.lang.String,java.lang.String)]
    [21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signout], method=[GET], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.signout(org.ioc.web.model.http.Request)]
    [21.10.18 22:29:51:995] INFO web.model.mapping.MappingContainer: Mapped method [/loginPage], method=[GET], to [public org.ioc.web.model.ModelAndView org.examples.webapp.mapping.MainMapping.authenticated(org.examples.webapp.domain.entity.TblAccount)]
    [21.10.18 22:29:51:997] INFO ioc.web.factory.HttpInitializerFactory: Http server started on port(s): 8081 (http)


    Результат:
    1) Главная страница
    index
    2) Регистрация
    signup
    3) Аутентфикация
    auth
    4) Страница с результатом авторизации(редирект после правильного ввода логина и пароля)
    result
    5) Очистка информации авторизации из сесии и перенаправления пользователя на стартовую страницу
    image
    6) Попытка не авторизированного пользователя попасть на страницу с информацией аутентификации сессии
    image


    Конец


    Проект развивающийся, приветствуются "контрибьюторы" и оригинальные идеи, поскольку одному делать этот проект тяжеловато.
    Репозиторий проекта.
    Контекст
    ОРМ фабрика
    Веб фабрика
    Примеры
    Текущий пример из статьи
    Так же на репозитории имеются примеры использования всего функционала в модуле 'examples', и как говориться, "Good luck, have fun", всем спасибо за внимание.

    Share post

    Comments 0

    Only users with full accounts can post comments. Log in, please.