Альтернатива 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; } public boolean cookieChanged(){ if (this.cookie == null) return false; return true; } public void clearСookie(){ this.cookie = null; } }

Переделал статью и дополнил в фильтр авторизации 2 последних метода из кода выше. Без последнего был баг. Он заключался с тем, что когда мы выходим из аккаунта и вводим рандомные данные, то аутентификация берётся из последнего сохранённого куки. Поэтому этот метод вызывается для очистки. Код взят с моего проекта. Тут задействован ещё и 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 в таблице пользователи.

Альтернатива Spring Security с использованием JJWT токена и Cookie, HttpServletRequest, HttpServletResponse
role.add(roleService.findById(user.get(0).getRoleId()));

Здесь в коллекцию role пытаемся добавить объекты. Потом идёт проверка вернул ли сервис пользователя, если да то он существует и проверяем его пароль и роль.

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

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

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

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.cookieChanged()) { httpServletResponse.addCookie(filterAutorizationPage.getCookie()); filterAutorizationPage.clearСookie(); } users.clear(); return result; }

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

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

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

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

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

Это мы рассмотрели только класс фильтр авторизации. Нужно ещё рассмотреть класс фильтра аутентификации других страниц. Покажу пример класса фильтра который установлен и работает при запросе главной страницы домена например 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>
11
Начать дискуссию