Альтернатива Spring Security с использованием JJWT токена и Cookie, HttpServletRequest, HttpServletResponse

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Коротко о главном. Конфигурации не нужны. На каждую страницу создаётся отдельный класс фильтр с методом аутентификации. Если к группе страниц доступ получает пользователь с одной и той же ролью, то используется один и тот же класс фильтр. Логично

Но страница авторизации устроена хитрее. Там аж 4 метода: авторизация если cookie полностью отсутствуют, аутентификация, обычная авторизация и получение изменённого cookie. Соответственно 1 и 3 методы похожи но на 1 стоит проверка cookie на null. В случае если 1 метод не отработал вызывается 2 и 3 соответственно. Можно было сделать 1 метод, но так он визуально более понятен.

Два последних метода были созданы из за невозможности возврата строки названия html страницы в стиле шаблонизатора Thymeleaf c изменённым куки. Ещё HttpServletResponse применим только внутри контроллера, поэтому в классах фильтрах только проверка Http запроса к серверу. Пример:

package com.hotabmax.filters;

import com.hotabmax.models.User;
import com.hotabmax.services.UserService;
import com.hotabmax.models.Role;
import com.hotabmax.services.RoleService;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.ArrayList;
import java.util.List;

@Service
@Component("FilterAutorizationPage")
public class FilterAutorizationPage {
    Cookie cookie;

    private UserService userService;
    private RoleService roleService;

    @Autowired
    public void setDependencies(
            @Qualifier("UserService") UserService userService,
            @Qualifier("RoleService") RoleService roleService
    ) {
        this.userService = userService;
        this.roleService = roleService;
    }

    public String autorizationIfCookieIsNull(HttpServletRequest httpServletRequest,
         Key key, String AutorityName, int AutorityPassword) {

        Cookie[] cookie = httpServletRequest.getCookies();
        List<User> user = new ArrayList<>();
        List<Role> role = new ArrayList<>();
        String resultPage = "autorization";
        if(cookie == null) {
            user = userService.findByName(AutorityName);
            role.add(roleService.findById(user.get(0).getRoleId()));
            if (user.size() != 0) {
                if (user.get(0).getPassword() == AutorityPassword) {
                    if (role.get(0).getName().equals("logist")){
                        String jws = Jwts.builder().setSubject(AutorityName+" "+AutorityPassword).signWith(key).compact();
                        Cookie cookieAdd = new Cookie("JWT", jws);
                        cookieAdd.setMaxAge(999999);
                        this.cookie = cookieAdd;
                        resultPage = "redirect:/logist";
                    } else if (role.get(0).getName().equals("seller")) {
                        String jws = Jwts.builder().setSubject(AutorityName+" "+AutorityPassword).signWith(key).compact();
                        Cookie cookieAdd = new Cookie("JWT", jws);
                        cookieAdd.setMaxAge(999999);
                        this.cookie = cookieAdd;
                        resultPage = "redirect:/seller";
                    } else {
                        System.out.println("Пользователя не существует");
                        resultPage = "autorizationErr";
                    }
                } else resultPage = "autorizationErr";
            } else resultPage = "autorizationErr";
        }
        if (resultPage.equals("autorization"))
            return autentification(httpServletRequest, key,
                                  AutorityName, AutorityPassword);
        else return resultPage;
    }

    public String autentification(HttpServletRequest httpServletRequest,
                                  Key key, String AutorityName, int AutorityPassword) {
        String JWTname = new String();
        int JWTpassword = 0;
        String[] values;
        Cookie[] cookie = httpServletRequest.getCookies();
        List<User> user = new ArrayList<>();
        List<Role> role = new ArrayList<>();
        String resultPage = "autorization";
        try {
            for(int i = 0; i < cookie.length; i++) {
                if(cookie[i].getName().equals("JWT")) {
                    String jws = cookie[i].getValue();
                    String sources = Jwts.parserBuilder()
                            .setSigningKey(key)
                            .build()
                            .parseClaimsJws(jws)
                            .getBody()
                            .getSubject();
                    values = sources.split("\\s+");
                    JWTname = values[0];
                    JWTpassword = Integer.parseInt(values[1].replaceAll("[^0-9]", ""));

                }
            }
        } catch (JwtException exc) {
            System.out.println("Куки недействителен");
        }

        user = userService.findByName(JWTname);
        role.add(roleService.findById(user.get(0).getRoleId()));
        if (user.size() != 0) {
            if (user.get(0).getPassword() == JWTpassword) {
                if (role.get(0).getName().equals("logist")){
                    resultPage = "redirect:/logist";
                } else if (role.get(0).getName().equals("seller")) {
                    System.out.println("Недостаточно прав для админа");
                    resultPage = "redirect:/seller";
                } else {
                    System.out.println("Пользователя не существует");
                    resultPage = "autorizationErr";
                }
            }
        }

        if (resultPage.equals("autorization"))
            return autorization(key, AutorityName, AutorityPassword);
        else return resultPage;
    }

    public String autorization(Key key, String AutorityName, int AutorityPassword) {
        String resultPage = "autorization";
        List<User> user = new ArrayList<>();
        List<Role> role = new ArrayList<>();
        user = userService.findByName(AutorityName);
        role.add(roleService.findById(user.get(0).getRoleId()));
            if (user.size() != 0) {
                if (user.get(0).getPassword() == AutorityPassword) {
                    if (role.get(0).getName().equals("logist")){
                        String jws = Jwts.builder().setSubject(AutorityName+" "+AutorityPassword).signWith(key).compact();
                        Cookie cookieAdd = new Cookie("JWT", jws);
                        cookieAdd.setMaxAge(999999);
                        this.cookie = cookieAdd;
                        resultPage = "redirect:/logist";
                    } else if (role.get(0).getName().equals("seller")) {
                        String jws = Jwts.builder().setSubject(AutorityName+" "+AutorityPassword).signWith(key).compact();
                        Cookie cookieAdd = new Cookie("JWT", jws);
                        cookieAdd.setMaxAge(999999);
                        this.cookie = cookieAdd;
                        resultPage = "redirect:/seller";
                    } else {
                        System.out.println("Пользователя не существует");
                        resultPage = "autorizationErr";
                    }
                } else resultPage = "autorizationErr";
            } esle resultPage = "autorizationErr";
        return resultPage;
    }

    public Cookie getCookie() {
        return this.cookie;
    }
  
}

Не стал менять код выше для статьи ввиду лени автора. Код взят с моего проекта. Тут задействован ещё и Spring Data

!!! Предполагается что вы знаете уже хоть какую-то ORM. Но напишите, если нужно дополнить статью этим фреймворком !!!

Первый метод autorizationIfCookieIsNull принимает Key, который инициализируется либо банально в контроллере

@Controller
public class LogistController {
  private Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
}

Либо в отдельном классе сервисе ClassOfKey к примеру создаём метод бин, который создаст Key, если он был равен null. При втором вызове он уже не изменится и все контроллеры получат одинаковый Key

@Service
@Component("ClassOfKey")
public class ClassOfKey {
    private Key key;

    public Key setKey() {
        if(this.key == null){
            this.key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        }
        return this.key;
    }
}
@Controller
public class LogistController {
  private Key key;
  
  @Bean
    public void setKeyLogist(){
        key = classOfKey.setKey();
    }
}
@Controller
public class SellerController {
  private Key key;
  
  @Bean
    public void setKeyLogist(){
        key = classOfKey.setKey();
    }
}

Ещё этот метод принимает HttpServletResponse из которого мы достаём куки (40, 78 стр)

HttpServletRequest httpServletRequest

Далее идут логин и пароль. Пароль может быть и строкой. Зависит только от вашей фантазии и метода сервиса Spring Data разумеется. В моём случае это число

String AutorityName, int AutorityPassword

Внутри метода мы вызываем коллекцию из 1 пользователя

user = userService.findByName(AutorityName);

Далее находим роль пользователя. Метод называется getRoleId потому, что таблица пользователи имеет зависимость к таблице роли и ищутся роли по колонке id в таблице пользователи.

role.add(roleService.findById(user.get(0).getRoleId()));

Потом идёт проверка вернул ли сервис пользователя, если да то он существует и проверяем его пароль и роль.

Так же в проекте используется фреймворк JJWT https://github.com/jwtk/jjwt вся документация там. Фреймвор под лицензией Apatch 2.0 поэтому в шапке класса нужно написать комментарий:

Copyright [2021] [jwtk] Licensed under the Apache License, Version 2.0 (the «License»)

В метод setSubject кладём логин и пароль через пробел, а в метод signWith кладём ключ. Получившийся токен представлен в виде строки. Пример:

0LrRgdC40LwgMTIzIn0.a90QRs3CBRxEzfgezWetBvjozb8btfXFahmrsgSe8Jc

Данные до точки это зашифрованные данные, а после ключ. Эта строка добавляется в cookie с заголовком JWT например

Далее устанавливается время жизни токена примерно на месяц

cookieAdd.setMaxAge(999999);

И передаём cookie в глобальную переменную

this.cookie = cookieAdd;

Ну и логично, что в методе контроллера нам нужно дополнительно вызывать метод для извлечения cookie

@PostMapping("/autorization")
    public  String loadAutorization(HttpServletRequest httpServletRequest,
                                       HttpServletResponse httpServletResponse,
                                       @RequestParam("name") String name,
                                       @RequestParam("password") int password) {
        String result;
        Cookie cookieWrite;
        result = filterAutorizationPage.autorizationIfCookieIsNull(httpServletRequest, key, name, password);

        if (filterAutorizationPage.getCookie() != null) {
            httpServletResponse.addCookie(filterAutorizationPage.getCookie());
        }
        users.clear();
        return result;
    }

Если авторизация была неудачной, вернётся страница регистрации autorizationErr.html с текстом возможной ошибки

Третий метод похож на первый поэтому рассмотрим только второй. Стоит так же отметить, что весь процесс извлечения строки происходит в блоке try, потому что если ключ не подходит генерируется ошибка JwtException. В методе находим тело cookie записи по заголовку. Извлечённую строку подставляем в монолитную конструкцию и получаем строку с логином и паролем через проблем. Применяем

values = sources.split("\\s+");

и получаем массив из двух строк. Также преобразуем пароль в Integer

JWTpassword = Integer.parseInt(values[1].replaceAll("[^0-9]", ""));

Честно говоря легче было сделать пароль в виде строки, но что сделано то сделано. Оставлю это здесь может пригодится. Ну и точно также проверяем дынные на действительность.

Это мы рассмотрели только класс фильтр авторизации. Нужно ещё рассмотреть класс фильтра аутентификации других страниц. Покажу пример класса фильтра который установлен и работает при запросе главной страницы домена например ursite.com/

package com.hotabmax.filters;


import com.hotabmax.models.User;
import com.hotabmax.services.UserService;
import com.hotabmax.models.Role;
import com.hotabmax.services.RoleService;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.ArrayList;
import java.util.List;

@Component("FilterDomenPage")
public class FilterDomenPage {

    private UserService userService;
    private RoleService roleService;

    @Autowired
    public void setDependencies(
            @Qualifier("UserService") UserService userService,
            @Qualifier("RoleService") RoleService roleService
    ) {
        this.userService = userService;
        this.roleService = roleService;
    }

    public String autentification(HttpServletRequest httpServletRequest, Key key) {
        Cookie[] cookie = httpServletRequest.getCookies();
        String resultPage = "autorization";
        List<User> user = new ArrayList<>();
        List<Role> role = new ArrayList<>();

        if (cookie != null){
            try {
                for(int i = 0; i < cookie.length; i++) {
                    if(cookie[i].getName().equals("JWT")) {
                        String jws = cookie[i].getValue();
                        String sources = Jwts.parserBuilder()
                                .setSigningKey(key)
                                .build()
                                .parseClaimsJws(jws)
                                .getBody()
                                .getSubject();
                        String[] values = sources.split("\\s+");
                        String name = values[0];
                        int password = Integer.parseInt(values[1].replaceAll("[^0-9]", ""));
                        user = userService.findByName(name);
                        role.add(roleService.findById(user.get(0).getRoleId()));
                        if (user.size() != 0) {
                            if (user.get(0).getPassword() == password) {
                                if (role.get(0).getName().equals("logist")){
                                    resultPage = "redirect:/logist";
                                } else if (role.get(0).getName().equals("seller")) {
                                    System.out.println("Недостаточно прав для админа");
                                    resultPage = "redirect:/seller";
                                } else {
                                    System.out.println("Пользователя не существует");
                                    resultPage = "redirect:/autorizationErr";
                                }
                            }
                        }
                    }
                }
            } catch (JwtException exc) {
                System.out.println("Куки недействителен");
                resultPage = "autorizationErr";
            }
        } else resultPage = "autorization";

        return resultPage;
    }

}

Как можно заметить метод autentification абсолютно похож на одноименный метод в 1 классе фильтре. При проверке данных токена происходит редирект в соответствии с ролью. Этот класс можно переиспользовать на любой странице и любой роли которую вы захотите создать следующим образом

@GetMapping("/")
    public String getHost(HttpServletRequest httpServletRequest) {
        return filterDomenPage.autentification(httpServletRequest, key);
    }

Необходимые зависимости в pom файле

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.2</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId> 
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
Источник: https://habr.com/ru/post/592257/


Интересные статьи

Интересные статьи

По мотивамЧасть 0x00Часть 0x01Часть 0x02Ребятушки, наконец! После довольно длительного перерыва ко мне, в конце концов, пришло вдохновение написать последнюю, финальную и...
В статье показано, как быстро можно создать свой plugin для облегчения работы с Transactional в Spring. Писать будем на Kotlin и Java Читать далее ...
Многие компании в определенный момент приходят к тому, что ряд процессов в бизнесе нужно автоматизировать, чтобы не потерять свое место под солнцем и своих заказчиков. Поэтому все...
28 июля приглашаем на онлайн-лекцию о разработке приложений на Java Spring, Quarkus, Vert.x и GraalVM с деплоем в MicroK8s. В программе лекции: Сергей Кошкинов и Андрей Смирнов...
Периодически мне в разных вариантах задают вопрос, который «в среднем» звучит так: «что лучше: заказать интернет-магазин на бесплатной CMS или купить готовое решение на 1С-Битрикс и сделать магазин на...