Введение: почему ваш пароль — это не просто строка
Представьте: вы написали крутое Java-приложение для онлайн-библиотеки. Пользователи регистрируются, сохраняют закладки, пишут рецензии. И вдруг — утечка базы данных. Все пароли в открытом виде. Катастрофа? Ещё какая! В 2024 году утечки данных происходят каждые 39 секунд, и 81% из них связаны со слабой аутентификацией. Если вы пишете на Java и хотите, чтобы ваш проект не попал в сводки киберновостей — эта статья для вас.
Мы разберём безопасную аутентификацию в Java приложениях от А до Я: от хэширования паролей до JWT и защиты от CSRF. Без скучной теории, только рабочий код и реальные практики. Поехали!
1. Хэширование паролей: первая линия обороны
Первое правило безопасности: никогда не храните пароли в открытом виде. Даже если ваша база данных — это священный грааль, шифрование паролей — обязательный минимум. В Java для этого используют алгоритмы вроде bcrypt, scrypt или Argon2. Почему не MD5 или SHA? Потому что они слишком быстрые — злоумышленник подберёт пароль за секунды.
Практика: bcrypt в действии
Добавьте зависимость в pom.xml (Maven):
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>6.2.0</version>
</dependency>
Теперь хэшируем пароль при регистрации:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordService {
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
public String hashPassword(String rawPassword) {
return encoder.encode(rawPassword); // генерирует соль автоматически
}
public boolean checkPassword(String rawPassword, String storedHash) {
return encoder.matches(rawPassword, storedHash);
}
}
Важно: bcrypt сам добавляет случайную соль, так что даже одинаковые пароли будут выглядеть по-разному. Стоимость (cost factor) по умолчанию — 10, но для критичных систем можно поднять до 12-14.
2. JWT (JSON Web Tokens): лёгкая и безопасная сессия
После регистрации пользователь должен как-то подтверждать свою личность. Куки? Можно, но JWT — современный стандарт для REST API. Токен содержит информацию о пользователе и подписан секретным ключом. Никаких сессий на сервере — всё в токене.
Создание JWT в Java
Подключите библиотеку io.jsonwebtoken:jjwt:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
Генерация токена:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class JwtUtil {
private final SecretKey key = Keys.hmacShaKeyFor("секретный_ключ_длиной_не_менее_256_бит".getBytes());
public String generateToken(String username) {
return Jwts.builder()
.subject(username)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 3600000)) // 1 час
.signWith(key)
.compact();
}
public String extractUsername(String token) {
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
}
}
Совет: не храните секретный ключ в коде! Используйте переменные окружения или Vault. И никогда не кладите в JWT конфиденциальные данные (пароли, номера карт) — токен можно декодировать без подписи.
3. Защита от CSRF: когда злоумышленник действует от вашего имени
CSRF (Cross-Site Request Forgery) — это атака, при которой пользователь, будучи авторизованным, случайно выполняет действие на вашем сайте (например, переводит деньги). Java-фреймворки вроде Spring Security имеют встроенную защиту.
Spring Security и CSRF-токен
Если вы используете Spring Security, CSRF-защита включена по умолчанию для любых изменяющих запросов (POST, PUT, DELETE). Но для REST API с JWT её часто отключают — и это нормально, если токен не хранится в куках.
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // отключаем для REST API с JWT
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
Когда CSRF обязателен: если вы используете сессии на основе кук (например, в монолитном веб-приложении). Тогда добавляйте токен в каждую форму:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
4. Хранение токенов: безопасность на стороне клиента
Вы сгенерировали JWT. Куда его сохранить на фронтенде? localStorage — плохая идея: если на сайт проникнет XSS-скрипт, токен украдут. httpOnly куки — лучше, но тогда нужна CSRF-защита.
Лучшая практика: httpOnly куки + Secure + SameSite
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
public void addTokenCookie(HttpServletResponse response, String token) {
Cookie cookie = new Cookie("jwt", token);
cookie.setHttpOnly(true); // недоступен для JavaScript
cookie.setSecure(true); // только по HTTPS
cookie.setPath("/");
cookie.setMaxAge(3600); // 1 час
response.addCookie(cookie);
}
Почему это работает? XSS-атака не сможет прочитать куку, а Secure не даст перехватить её по HTTP. Для дополнительной защиты используйте атрибут SameSite=Strict.
5. Многофакторная аутентификация (MFA): двойной замок
Один пароль — хорошо, два — лучше. Если ваше приложение работает с финансами или персональными данными, добавьте второй фактор: TOTP (Time-based One-Time Password) через Google Authenticator или SMS.
Реализация TOTP в Java
Используйте библиотеку com.warrenstrange:googleauth:
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId