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

Kate

Administrator
Команда форума
Коротко о главном. Конфигурации не нужны. На каждую страницу создаётся отдельный класс фильтр с методом аутентификации. Если к группе страниц доступ получает пользователь с одной и той же ролью, то используется один и тот же класс фильтр. Логично
Но страница авторизации устроена хитрее. Там аж 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.getName().equals("JWT")) {
String jws = cookie.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 в таблице пользователи.
2954b111d498afae8e73bc6c4a35c33f.png
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.getName().equals("JWT")) {
String jws = cookie.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>


 
Сверху