Java for Beginner
676 subscribers
560 photos
156 videos
12 files
858 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
3.2 Фильтрация JWT

Создадим фильтр для проверки JWT токенов.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Key;
import java.util.Collections;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final String secretKey = "mysecretkeymysecretkeymysecretkeymysecretkey";

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");

if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}

String token = authHeader.substring(7);
try {
Key key = Keys.hmacShaKeyFor(secretKey.getBytes());
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();

String username = claims.getSubject();
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
e.printStackTrace();
}
filterChain.doFilter(request, response);
}
}


4. Авторизация на Уровне Методов

Spring Security позволяет ограничивать доступ к методам с помощью аннотаций.

Пример с @PreAuthorize:
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

@Service
public class ReportService {

@PreAuthorize("hasRole('ADMIN')")
public void generateAdminReport() {
System.out.println("Отчет для администратора");
}

@PreAuthorize("hasRole('USER')")
public void generateUserReport() {
System.out.println("Отчет для пользователя");
}
}


Для активации аннотаций на уровне методов, необходимо включить их в конфигурации:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
}


#Java #Training #Spring #Security #Security_Authentication #Security_Authority
Проверка подлинности пользователя в Spring Security

Spring Security предлагает различные подходы для аутентификации пользователей. Каждый из них предназначен для конкретных случаев использования, начиная от классической формы логина до современных методов на основе токенов, таких как JWT и OAuth2.

1. Форма логина (Form-Based Authentication)


1.1 Описание
Форма логина — это один из самых популярных методов аутентификации для веб-приложений. Пользователь вводит имя пользователя и пароль на странице входа, которые отправляются на сервер для проверки.

1.2 Настройка
Шаги настройки:
Настройка страницы логина.
Конфигурация Spring
Security для обработки формы.

Пример конфигурации:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated() // Все запросы требуют аутентификации
)
.formLogin(form -> form
.loginPage("/login") // Кастомная страница логина
.permitAll() // Разрешить доступ к странице логина
)
.logout(logout -> logout
.permitAll() // Разрешить выход
);

return http.build();
}
}


Шаблон страницы логина:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form th:action="@{/login}" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" /><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" /><br>
<button type="submit">Login</button>
</form>
</body>
</html>


Нюансы:
По умолчанию Spring Security предоставляет стандартную страницу логина, если не указана кастомная.
Все данные передаются через HTTPS для обеспечения безопасности.


2. HTTP Basic Authentication

2.1 Описание
HTTP Basic Authentication — это простой метод аутентификации, при котором имя пользователя и пароль передаются в заголовке HTTP-запроса.

2.2 Настройка
Пример конфигурации:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.httpBasic(); // Включение HTTP Basic
return http.build();
}


Пример запроса:
GET /api/resource HTTP/1.1
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=


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


3. Token-Based Authentication


3.1 Описание
Вместо хранения сессий на сервере, токены используются для аутентификации. Самый популярный вариант — JWT (JSON Web Token).

3.2 Настройка JWT Authentication
1. Генерация токена:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

import java.security.Key;
import java.util.Date;

@Service
public class JwtService {
private final String secretKey = "mysecretkeymysecretkeymysecretkeymysecretkey"; // 256 бит
private final long expirationMs = 86400000; // 1 день

public String generateToken(String username) {
Key key = Keys.hmacShaKeyFor(secretKey.getBytes());
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationMs))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
}


#Java #Training #Spring #Security #Security_Authority
2. JWT фильтр для валидации токенов:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Key;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final String secretKey = "mysecretkeymysecretkeymysecretkeymysecretkey";

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
Key key = Keys.hmacShaKeyFor(secretKey.getBytes());
String username = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();

if (username != null) {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(username, null, null));
}
}
filterChain.doFilter(request, response);
}
}


3. Регистрация фильтра:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}


Нюансы:
JWT-токены не требуют хранения на сервере.
Важно правильно настроить срок действия токена и защиту от кражи.


#Java #Training #Spring #Security #Security_Authority
4. OAuth2 Authentication

4.1 Описание
OAuth2 — это стандарт аутентификации, который позволяет пользователям аутентифицироваться с помощью сторонних сервисов, таких как Google, Facebook и т.д.

4.2 Настройка
Spring Security поддерживает OAuth2 через spring-boot-starter-oauth2-client.

Добавление зависимости:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>


Пример конфигурации:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.oauth2Login(oauth -> oauth
.loginPage("/oauth2/authorization/google") // Настройка страницы логина
);
return http.build();
}
}


Нюансы:
OAuth2 требует настройки клиентских данных (client ID и secret).
Поддерживается автоматический редирект на стороннюю страницу аутентификации.


5. LDAP Authentication

5.1 Описание
LDAP используется для аутентификации через службы каталогов, такие как Active Directory.

Пример конфигурации:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.formLogin()
.and()
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org");
return http.build();
}


Нюансы:
Требуется настроить LDAP сервер.
Используется в корпоративных приложениях.


Spring Security поддерживает множество способов аутентификации, начиная от традиционных форм логина и заканчивая современными подходами, такими как OAuth2 и JWT. Выбор подхода зависит от требований приложения:
Для веб-приложений подойдет форма логина.
Для REST API — JWT.
Для корпоративных систем — LDAP.



#Java #Training #Spring #Security #Security_Authority
Использование базы данных для хранения пользователей в Spring Security

Хранение пользователей в базе данных является наиболее распространённым способом управления аутентификацией в реальных приложениях. Это позволяет централизовать информацию о пользователях и их ролях, упрощая управление и обеспечивая безопасность.

1. Общая архитектура

Когда Spring Security использует базу данных для аутентификации, основные элементы выглядят следующим образом:
База данных: Хранит таблицы пользователей и ролей.
UserDetailsService: Загрузчик пользователей, который извлекает данные из базы данных.
PasswordEncoder: Обеспечивает хэширование паролей.
AuthenticationManager: Выполняет процесс аутентификации.
Роли и привилегии: Хранятся в базе для управления авторизацией.


2. Шаги по реализации

2.1 Настройка базы данных
Создайте базу данных и добавьте необходимые таблицы для хранения пользователей и их ролей. Рассмотрим пример на основе реляционной базы данных.

Пример SQL-схемы:
-- Таблица пользователей
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
enabled BOOLEAN NOT NULL
);

-- Таблица ролей
CREATE TABLE roles (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);

-- Таблица связи пользователей и ролей
CREATE TABLE user_roles (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);


Пример данных:
INSERT INTO users (username, password, enabled) VALUES 
('admin', '$2a$10$7a6yXl4bKh7QvXuUIhEY2eP2d6P5M6NmrkObWbxODeEM.gSUTZlVu', true), -- пароль: admin
('user', '$2a$10$w8y4/gM3/uF9Rf/5S1OejX.6M4KZt7eEPY2OnB9oRxCSU8Y4.jK8u', true); -- пароль: user

INSERT INTO roles (name) VALUES ('ROLE_ADMIN'), ('ROLE_USER');

INSERT INTO user_roles (user_id, role_id) VALUES
(1, 1), -- admin имеет роль ROLE_ADMIN
(2, 2); -- user имеет роль ROLE_USER
Пароли были закодированы с использованием BCryptPasswordEncoder.


2.2 Настройка приложения
Подключение к базе данных

Настройте подключение к базе данных в файле application.properties (или application.yml):

application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/spring_security_db
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.jpa.hibernate.ddl-auto=update


Зависимости
Добавьте зависимости для работы с базой данных и Spring Security:

Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>


Gradle:
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'mysql:mysql-connector-java'


#Java #Training #Spring #Security #Security_BD
2.3 Реализация UserDetailsService
Spring Security использует интерфейс UserDetailsService для загрузки пользователей из базы данных. Реализуем его в нашем приложении.
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Collections;

@Service
public class CustomUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Пользователь не найден: " + username));

return User.builder()
.username(userEntity.getUsername())
.password(userEntity.getPassword())
.authorities(userEntity.getRoles().stream()
.map(role -> "ROLE_" + role.getName())
.toList())
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(!userEntity.isEnabled())
.build();
}
}


2.4 Создание Репозиториев
Создадим репозитории для работы с таблицами пользователей и ролей.


UserRepository:
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<UserEntity, Long> {
Optional<UserEntity> findByUsername(String username);
}


RoleRepository:
import org.springframework.data.jpa.repository.JpaRepository;

public interface RoleRepository extends JpaRepository<RoleEntity, Long> {
}


2.5 Создание Сущностей
UserEntity:
import jakarta.persistence.*;
import java.util.Set;

@Entity
@Table(name = "users")
public class UserEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true)
private String username;

@Column(nullable = false)
private String password;

@Column(nullable = false)
private boolean enabled;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<RoleEntity> roles;

// Геттеры и сеттеры
}


RoleEntity:
import jakarta.persistence.*;
import java.util.Set;

@Entity
@Table(name = "roles")
public class RoleEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true)
private String name;

@ManyToMany(mappedBy = "roles")
private Set<UserEntity> users;

// Геттеры и сеттеры
}


#Java #Training #Spring #Security #Security_BD
2.6 Настройка Конфигурации Безопасности
Настроим SecurityConfig для использования пользовательской реализации UserDetailsService.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

private final CustomUserDetailsService customUserDetailsService;

public SecurityConfig(CustomUserDetailsService customUserDetailsService) {
this.customUserDetailsService = customUserDetailsService;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin()
.and()
.logout().permitAll();

return http.build();
}

@Bean
public AuthenticationManager authManager(HttpSecurity http, PasswordEncoder passwordEncoder)
throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder)
.and()
.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}


3. Тестирование

Теперь, когда настройка завершена:
Вы можете зарегистрировать новых пользователей, хэшировать их пароли с помощью BCryptPasswordEncoder и сохранять их в базу данных.
При попытке входа Spring
Security будет извлекать пользователя из базы данных через CustomUserDetailsService.

#Java #Training #Spring #Security #Security_BD
Настройка ролей и прав доступа в Spring Security

1. Основные концепции


Authentication (аутентификация): процесс проверки подлинности пользователя, т.е. кто он такой.
Authorization (авторизация): процесс проверки прав пользователя, т.е. что он может делать.
Principal: представляет текущего аутентифицированного пользователя.
GrantedAuthority: представляет разрешение или роль, назначенную пользователю.
Role: определенная категория, которая группирует определенные разрешения.


2. Архитектура и основные компоненты

SecurityContext и SecurityContextHolder: SecurityContext хранит данные о текущей аутентификации, включая Principal и GrantedAuthority. SecurityContextHolder — это контейнер, который обеспечивает доступ к текущему SecurityContext.
AuthenticationManager: основной интерфейс для выполнения аутентификации. Он принимает объект Authentication и возвращает аутентифицированный объект или выбрасывает исключение.
AccessDecisionManager: принимает решение о предоставлении или отклонении доступа к защищенному ресурсу на основе предоставленных прав.
FilterSecurityInterceptor: фильтр, который проверяет доступ к ресурсам на основе настроек безопасности.


3. Настройка ролей и прав доступа

Spring Security поддерживает настройку через Java-код (Java Config) и XML.

3.1 Подключение Spring Security
Добавьте зависимости в pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>


3.2 Создание конфигурационного класса
Конфигурация Spring Security осуществляется с помощью аннотации @EnableWebSecurity и наследования от класса WebSecurityConfigurerAdapter.

Пример базовой конфигурации:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // Только для ADMIN
.antMatchers("/user/**").hasRole("USER") // Только для USER
.antMatchers("/public/**").permitAll() // Доступ для всех
.anyRequest().authenticated() // Все остальные запросы требуют аутентификации
.and()
.formLogin() // Включить форму логина
.and()
.logout().permitAll(); // Разрешить logout для всех
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("adminPass")).roles("ADMIN")
.and()
.withUser("user").password(passwordEncoder().encode("userPass")).roles("USER");
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}


4. Роли и права: тонкая настройка

Роли (ROLE_*) — это абстракция для группировки прав. Spring
Security требует, чтобы роли начинались с префикса ROLE_.

4.1 Настройка прав доступа через аннотации
Spring Security поддерживает аннотации для определения прав доступа на уровне методов.

Включение поддержки аннотаций:

В классе конфигурации добавьте @EnableGlobalMethodSecurity:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Конфигурация как ранее
}


#Java #Training #Spring #Security #Security_Roles
Использование аннотаций в методах:
@Service
public class MyService {

@PreAuthorize("hasRole('ADMIN')")
public void adminOnlyMethod() {
// Только для ADMIN
}

@PreAuthorize("hasAuthority('PERMISSION_VIEW')")
public void specificPermissionMethod() {
// Проверка конкретного разрешения
}

@PostAuthorize("returnObject.owner == authentication.name")
public MyEntity getEntity() {
// Проверка после выполнения метода
}
}


4.2 Определение прав доступа (Authorities)
Вместо простого использования ролей можно настроить более тонкие права:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("adminPass")).authorities("ROLE_ADMIN", "PERMISSION_READ", "PERMISSION_WRITE")
.and()
.withUser("user").password(passwordEncoder().encode("userPass")).authorities("ROLE_USER", "PERMISSION_READ");
}


5. Настройка пользовательской аутентификации и хранения данных

В реальных приложениях данные о пользователях и ролях хранятся в базе данных.

5.1 Создание модели пользователя
@Entity
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String username;
private String password;
private boolean enabled;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles;

// Геттеры и сеттеры
}


5.2 Создание модели роли
@Entity
public class Role {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

@ManyToMany(mappedBy = "roles")
private Set<User> users;

// Геттеры и сеттеры
}


5.3 Создание пользовательского UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));

return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
mapRolesToAuthorities(user.getRoles())
);
}

private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Collection<Role> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}


5.4 Использование кастомного сервиса в конфигурации
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}


6. Защита REST API

Если вы разрабатываете REST API, можно отключить CSRF и использовать статeless-сессию:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/user/**").hasRole("USER")
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic(); // Для простоты базовая аутентификация
}


#Java #Training #Spring #Security #Security_Roles
JWT (JSON Web Token)

JWT (JSON Web Token) — это компактный и безопасный стандарт для передачи информации между сторонами в виде JSON-объекта. Он часто используется для аутентификации и передачи данных в веб-приложениях. JWT токены обладают рядом преимуществ, таких как легкость использования, самодостаточность (вся необходимая информация содержится внутри токена) и поддержка подписей для проверки подлинности.

1. Что такое JWT?

JWT представляет собой строку, состоящую из трех частей, разделенных точками (.):
xxxxx.yyyyy.zzzzz


Эти части соответствуют:
Header (заголовок) — метаинформация о токене.
Payload (полезная нагрузка) — данные, которые содержатся в токене.
Signature (подпись) — защита токена от подделки.


2. Структура JWT

2.1 Header
Заголовок содержит информацию о типе токена и алгоритме подписи.
{
"alg": "HS256",
"typ": "JWT"
}


alg — алгоритм подписи (например, HMAC-SHA256, RS256 и др.).
typ — тип токена (обычно "JWT").
Заголовок кодируется в формате Base64Url.


2.2 Payload
Полезная нагрузка содержит данные, которые передаются в токене. Эти данные называются "claims" (утверждения).

Они делятся на три типа:

Registered claims (зарегистрированные утверждения):
iss (issuer) — кто выдал токен.
sub (subject) — пользователь или субъект токена.
aud (audience) — для кого предназначен токен.
exp (expiration time) — время истечения токена (UNIX-время).
nbf (not before) — токен не должен использоваться до указанного времени.
iat (issued at) — время выдачи токена.
jti (JWT ID) — уникальный идентификатор токена.


Public claims (публичные утверждения): настраиваемые пользователем данные, такие как role, username и т.д.

Private claims (приватные утверждения): данные, специфичные для приложения, которые не входят в публичный стандарт.

{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1701234567
}
Payload также кодируется в формате Base64Url.


2.3 Signature
Подпись обеспечивает целостность токена. Она создается путем использования:

Закодированных в Base64Url Header и Payload.
Секретного ключа.
Алгоритма подписи.


Пример создания подписи (при использовании алгоритма HMAC-SHA256):
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
Результат (подпись) добавляется в конец токена.


3. Виды шифрования и алгоритмы

JWT может быть подписан или зашифрован:

3.1 Подписанные JWT
Подписанные токены защищены от изменения, но их содержимое можно прочитать. Используемые алгоритмы:

HMAC (симметричное шифрование):
Пример: HS256 (HMAC-SHA256).
Использует один общий секретный ключ для создания и проверки подписи.
Подходит для приложений с одним центром проверки.


RSA (асимметричное шифрование):

Пример: RS256 (RSA-SHA256).
Использует пару ключей: приватный для подписания и публичный для проверки.
Применяется в системах, где проверку подписи выполняют разные сервисы.


ECDSA (эллиптическая криптография):
Пример: ES256.
Более производительный, чем RSA, но с аналогичным подходом к подписи.


3.2 Зашифрованные JWT
Иногда требуется скрыть содержимое токена. Для этого используется JSON Web Encryption (JWE). Зашифрованные токены обеспечивают конфиденциальность данных.

Основные алгоритмы шифрования:
AES (например, A256GCM).
RSA (например, RSA-OAEP).
Шифрование выполняется с использованием публичного ключа, а расшифровка — с помощью приватного.


4. Как формируется JWT?

Создается Header.
Создается Payload с данными.
Заголовок и полезная нагрузка кодируются в формате Base64Url.
Создается Signature, используя заголовок, полезную нагрузку и секретный ключ.
Все части объединяются в строку через точки.


Пример токена:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTcwMTIzNDU2N30
.
sX5yQNjQp17l-lF6fBt6CXyP0NItBTppS-ANkSQ6jCk


#Java #Training #Spring #Security #JWT
5. Что необходимо для расшифровки и проверки?

5.1 Для подписи на основе HMAC:
Алгоритм (например, HS256).
Секретный ключ (shared secret).


5.2 Для подписи на основе RSA:

Алгоритм (например, RS256).
Публичный ключ (для проверки подписи).
Приватный ключ (для создания подписи, если вы являетесь сервером).


5.3 Для шифрования:

Алгоритм (например, AES256 или RSA-OAEP).
Публичный ключ (для расшифровки).


6. Преимущества JWT
Самодостаточность: Вся необходимая информация содержится в токене.
Безопасность: Подпись обеспечивает защиту от подделки.
Простота использования: Легко передается между клиентом и сервером.
Масштабируемость: Подходит для распределенных систем (например, микросервисов).


7. Уязвимости и меры безопасности
Использование слабых секретных ключей: Используйте сложные и длинные ключи.
Отсутствие проверки срока действия (exp): Убедитесь, что токен истекает через разумный период.
Применение неподходящих алгоритмов: Используйте надежные алгоритмы (например, RS256, HS256).
Утечка приватного ключа: Приватные ключи должны быть строго защищены.
XSS-атаки: Храните JWT в HttpOnly cookies, а не в localStorage.


8. Пример реализации на Java
Пример генерации и проверки JWT с использованием библиотеки jjwt (Java JWT):

Зависимость в pom.xml:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>


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

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtExample {

private static final String SECRET_KEY = "your-secret-key";

public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 час
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}


Проверка токена:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.Claims;

public class JwtExample {

private static final String SECRET_KEY = "your-secret-key";

public static Claims validateToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}


#Java #Training #Spring #Security #JWT
Внедрение JWT-аутентификации в Spring Security

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

1. Общий процесс JWT-аутентификации

Аутентификация пользователя: Клиент отправляет логин и пароль на сервер.
Генерация токена: После успешной аутентификации сервер создает JWT токен и возвращает его клиенту.
Доступ к защищенным ресурсам: Клиент отправляет токен в заголовке Authorization (Bearer <token>), чтобы получить доступ к ресурсам.
Проверка токена: Сервер проверяет токен при каждом запросе и предоставляет доступ только аутентифицированным пользователям.


2. Необходимые зависимости

Добавьте в pom.xml зависимости:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>


3. Шаги реализации JWT-аутентификации

3.1 Создание конфигурационного класса Spring Security
Создаем класс SecurityConfig для настройки Spring Security:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

private final JwtAuthenticationFilter jwtAuthenticationFilter;

public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/login", "/auth/register").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
}


#Java #Training #Spring #Security #JWT
3.2 Создание модели пользователя
Создаем сущности User и Role для хранения пользователей и ролей в базе данных.
import javax.persistence.*;
import java.util.Set;

@Entity
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String username;
private String password;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles;

// Getters and Setters
}


@Entity
public class Role {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

// Getters and Setters
}


3.3 Создание сервиса для загрузки пользователя
Создаем сервис, который будет находить пользователя в базе данных и предоставлять его данные Spring Security.
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));

return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(user.getRoles().stream().map(Role::getName).toArray(String[]::new))
.build();
}
}


3.4 Создание утилиты для работы с JWT
Создаем класс JwtUtils для генерации и проверки токенов.
import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtUtils {

private final String jwtSecret = "your-secret-key";
private final long jwtExpirationMs = 86400000; // 24 часа

public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS256, jwtSecret)
.compact();
}

public String extractUsername(String token) {
return Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody()
.getSubject();
}

public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}


#Java #Training #Spring #Security #JWT
3.5 Создание фильтра для проверки JWT
Фильтр проверяет JWT токен в каждом запросе.
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtUtils jwtUtils;
private final CustomUserDetailsService userDetailsService;

public JwtAuthenticationFilter(JwtUtils jwtUtils, CustomUserDetailsService userDetailsService) {
this.jwtUtils = jwtUtils;
this.userDetailsService = userDetailsService;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String jwt = authHeader.substring(7);
if (jwtUtils.validateToken(jwt)) {
String username = jwtUtils.extractUsername(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}


3.6 Контроллер для аутентификации
Создаем контроллер для входа и регистрации пользователей.
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

private final AuthenticationManager authenticationManager;
private final JwtUtils jwtUtils;

public AuthController(AuthenticationManager authenticationManager, JwtUtils jwtUtils) {
this.authenticationManager = authenticationManager;
this.jwtUtils = jwtUtils;
}

@PostMapping("/login")
public String login(@RequestBody AuthRequest authRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
return jwtUtils.generateToken(authRequest.getUsername());
}
}


#Java #Training #Spring #Security #JWT
Обработка исключений в Spring Security

Обработка исключений в Spring Security — это важная часть настройки безопасности, позволяющая управлять реакцией приложения на различные виды ошибок безопасности, такие как неудачная аутентификация, попытка доступа без разрешений или некорректные данные токена.

1. Виды исключений в Spring Security

1.1 Исключения аутентификации
Эти исключения возникают, когда пользователь пытается пройти аутентификацию, но предоставленные данные неверны:
BadCredentialsException — неверный логин или пароль.
UsernameNotFoundException — пользователь не найден.
AccountExpiredException — учетная запись пользователя истекла.
DisabledException — учетная запись отключена.
LockedException — учетная запись заблокирована.
CredentialsExpiredException — срок действия учетных данных истек.


1.2 Исключения авторизации
Эти исключения связаны с попытками доступа к ресурсам без соответствующих прав:
AccessDeniedException — у пользователя недостаточно прав для доступа.
InsufficientAuthenticationException — аутентификация отсутствует или недостаточна.


1.3 Исключения токенов (в случае использования JWT)
JwtException — ошибки валидации JWT токена.
ExpiredJwtException — токен истек.
SignatureException — подпись токена неверна.


2. Как Spring Security обрабатывает исключения?

Spring Security предоставляет встроенные обработчики для исключений:
AuthenticationEntryPoint — используется для обработки ошибок аутентификации. Например, если пользователь не аутентифицирован, возвращается HTTP-статус 401 (Unauthorized).
AccessDeniedHandler — обрабатывает ошибки авторизации (HTTP-статус 403, Forbidden).


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

3. Настройка обработки исключений в Spring Security

3.1 Обработка ошибок аутентификации
Создаем собственный AuthenticationEntryPoint:
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"" + authException.getMessage() + "\"}");
}
}
Этот обработчик возвращает 401 Unauthorized с описанием ошибки в формате JSON.


3.2 Обработка ошибок авторизации
Создаем собственный AccessDeniedHandler:
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("{\"error\": \"Forbidden\", \"message\": \"" + accessDeniedException.getMessage() + "\"}");
}
}
Этот обработчик возвращает 403 Forbidden с описанием причины отказа.


#Java #Training #Spring #Security #Security_Exceptions
3.3 Подключение обработчиков в конфигурации Spring Security
Подключаем наши обработчики в конфигурационном классе:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

private final CustomAuthenticationEntryPoint authenticationEntryPoint;
private final CustomAccessDeniedHandler accessDeniedHandler;

public SecurityConfig(CustomAuthenticationEntryPoint authenticationEntryPoint,
CustomAccessDeniedHandler accessDeniedHandler) {
this.authenticationEntryPoint = authenticationEntryPoint;
this.accessDeniedHandler = accessDeniedHandler;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);

return http.build();
}
}


4. Обработка исключений для JWT токенов

При использовании JWT токенов можно добавить проверку токена в собственном фильтре, а также обрабатывать связанные исключения.

Пример обработки ошибок JWT
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
String errorMessage = "Unauthorized access";

Throwable cause = authException.getCause();
if (cause instanceof ExpiredJwtException) {
errorMessage = "JWT token expired";
} else if (cause instanceof SignatureException) {
errorMessage = "JWT signature invalid";
}

response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"" + errorMessage + "\"}");
}
}
Подключите JwtAuthenticationEntryPoint вместо стандартного обработчика ошибок аутентификации.


5. Дополнительная обработка исключений

Spring Security позволяет также настроить:
Обработчики неудачной аутентификации: Используйте AuthenticationFailureHandler.
Обработчики успешной аутентификации: Используйте AuthenticationSuccessHandler.


Пример настройки AuthenticationFailureHandler:
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException {
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"Authentication failed\", \"message\": \"" + exception.getMessage() + "\"}");
}
}


Добавьте его в цепочку фильтров:
http.formLogin()
.failureHandler(customAuthenticationFailureHandler);


#Java #Training #Spring #Security #Security_Exceptions
6. Глобальная обработка исключений

Для обработки всех исключений в приложении (не только Spring
Security), можно использовать аннотацию @ControllerAdvice:
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(AccessDeniedException.class)
public void handleAccessDeniedException(AccessDeniedException ex, HttpServletResponse response) throws IOException {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write("{\"error\": \"Access Denied\", \"message\": \"" + ex.getMessage() + "\"}");
}

@ExceptionHandler(AuthenticationException.class)
public void handleAuthenticationException(AuthenticationException ex, HttpServletResponse response) throws IOException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"error\": \"Authentication Failed\", \"message\": \"" + ex.getMessage() + "\"}");
}
}


7. Тестирование обработки исключений

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

Пример теста с использованием MockMvc:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class SecurityExceptionTest {

@Autowired
private MockMvc mockMvc;

@Test
void accessDeniedShouldReturn403() throws Exception {
mockMvc.perform(get("/protected-resource"))
.andExpect(status().isForbidden());
}
}


#Java #Training #Spring #Security #Security_Exceptions
Настройка кастомных страниц ошибок в Spring Security

Spring Security позволяет заменить стандартные страницы ошибок на пользовательские. Это улучшает пользовательский опыт и делает интерфейс приложения более профессиональным.

1. Общие страницы ошибок Spring Security

По умолчанию Spring Security использует встроенные страницы ошибок:
401 Unauthorized: для неудачной аутентификации.
403 Forbidden: для попыток доступа без соответствующих прав.


Эти страницы достаточно просты и не подходят для продакшн-приложений. Их можно заменить на кастомные.

2. Подходы к настройке кастомных страниц ошибок

Использование обработчиков (handler):

Настройка обработчиков AuthenticationEntryPoint и AccessDeniedHandler для возврата кастомных страниц.

Глобальная обработка исключений:
Настройка глобального обработчика через @ControllerAdvice.

Перенаправление на HTML-страницы:
Использование механизма перенаправления на заранее подготовленные HTML-страницы.

3. Настройка кастомных страниц ошибок в Spring Security

3.1 Замена страницы для ошибки 403 (Forbidden)
Настраиваем кастомный AccessDeniedHandler:
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
// Перенаправление на кастомную страницу
response.sendRedirect("/error/403");
}
}


Добавляем обработчик в конфигурацию Spring Security:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

private final CustomAccessDeniedHandler accessDeniedHandler;

public SecurityConfig(CustomAccessDeniedHandler accessDeniedHandler) {
this.accessDeniedHandler = accessDeniedHandler;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler); // Подключение кастомного обработчика

return http.build();
}
}


Создаем контроллер для обработки ошибок:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ErrorController {

@GetMapping("/error/403")
public String error403() {
return "error/403"; // Возвращаем HTML-страницу
}
}


Добавляем страницу src/main/resources/templates/error/403.html (например, для Thymeleaf):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Access Denied</title>
</head>
<body>
<h1>403 - Access Denied</h1>
<p>Sorry, you don't have permission to access this page.</p>
</body>
</html>


#Java #Training #Spring #Security #Security_Exceptions
3.2 Замена страницы для ошибки 401 (Unauthorized)

Настраиваем кастомный AuthenticationEntryPoint:

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendRedirect("/error/401");
}
}


Добавляем обработчик в конфигурацию Spring Security:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint); // Подключение кастомного обработчика

return http.build();
}


Создаем контроллер для обработки ошибок:
@Controller
public class ErrorController {

@GetMapping("/error/401")
public String error401() {
return "error/401"; // Возвращаем HTML-страницу
}
}


Создаем страницу src/main/resources/templates/error/401.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Unauthorized</title>
</head>
<body>
<h1>401 - Unauthorized</h1>
<p>Please log in to access this page.</p>
</body>
</html>


4. Обработка всех ошибок через глобальный контроллер

Если нужно обрабатывать все исключения централизованно, можно использовать @ControllerAdvice:
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class GlobalErrorController {

@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public String handleAccessDeniedException() {
return "error/403";
}

@ExceptionHandler(AuthenticationException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public String handleAuthenticationException() {
return "error/401";
}
}


#Java #Training #Spring #Security #Security_Exceptions
5. Использование встроенного механизма Spring Boot для ошибок

Spring Boot предоставляет удобный способ настройки кастомных страниц через файл application.properties:
server.error.whitelabel.enabled=false
server.error.path=/error


Затем создаем контроллер для обработки пути /error:
@Controller
public class CustomErrorController {

@GetMapping("/error")
public String handleError() {
return "error/custom";
}
}


Добавляем страницу src/main/resources/templates/error/custom.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Error</title>
</head>
<body>
<h1>An error occurred</h1>
<p>We are sorry, something went wrong.</p>
</body>
</html>


6. Обработка JSON-ответов для API

Для REST API ошибки обычно возвращаются в формате JSON. Пример глобального обработчика для API:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class ApiErrorController {

@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<?> handleAccessDeniedException(AccessDeniedException ex) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Map.of(
"error", "Forbidden",
"message", ex.getMessage()
));
}
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<?> handleAuthenticationException(AuthenticationException ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
"error", "Unauthorized",
"message", ex.getMessage()
));
}
}


#Java #Training #Spring #Security #Security_Exceptions