← Назад к вопросам
Как сверяются данные после введения пароля и логина
2.0 Middle🔥 141 комментариев
#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как проверяются пароль и логин в приложении
Процесс аутентификации (логин и проверка пароля) реализуется в Spring Security и состоит из нескольких этапов. Я описываю как это работает в современном приложении.
Общая схема
1. Пользователь вводит логин/пароль → отправляет POST запрос
2. Spring Security перехватывает запрос
3. Загружает пользователя из БД по логину
4. Сравнивает пароли (хешированный из БД с введенным)
5. Если совпадают → создает сессию/токен
6. Если не совпадают → возвращает 401 Unauthorized
1. Контроллер логина
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider tokenProvider;
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
try {
// Пользователь отправляет логин и пароль
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
// Если authenticate() успешна → пароли совпадают
SecurityContextHolder.getContext().setAuthentication(authentication);
// Генерируем JWT токен
String token = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new AuthResponse(token));
} catch (BadCredentialsException e) {
// Пароль неправильный
return ResponseEntity.status(401)
.body(new AuthResponse("Invalid username or password"));
} catch (UsernameNotFoundException e) {
// Логин не найден
return ResponseEntity.status(401)
.body(new AuthResponse("User not found"));
}
}
}
@Data
public class LoginRequest {
private String username;
private String password;
}
@Data
public class AuthResponse {
private String token;
}
2. AuthenticationManager — основной класс проверки
AuthenticationManager — это интерфейс который обрабатывает аутентификацию:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
// 1. Создаем AuthenticationManager бин
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
// 2. Конфигурируем как загружаются пользователи
@Bean
public UserDetailsService userDetailsService() {
return username -> userService.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(
"User not found: " + username
));
}
// 3. Конфигурируем как проверяются пароли
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3. UserDetailsService — загрузка пользователя
@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// Шаг 1: Загружаем пользователя из БД по логину
User user = userRepository.findByUsername(username)
.orElseThrow(() -> {
logger.warn("Login attempt with non-existent username: {}", username);
return new UsernameNotFoundException(
"User not found: " + username
);
});
logger.debug("User found: {}", username);
// Шаг 2: Преобразуем в UserDetails
return new CustomUserDetails(
user.getId(),
user.getUsername(),
user.getPassword(), // Хешированный пароль из БД!
user.getEmail(),
user.getRoles()
);
}
}
4. CustomUserDetails — сущность пользователя
@Data
@Builder
public class CustomUserDetails implements UserDetails {
private Long id;
private String username;
private String password; // Хешированный пароль!
private String email;
private List<Role> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// Преобразуем роли в authorities
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
.collect(Collectors.toList());
}
@Override
public boolean isAccountNonExpired() {
return true; // или проверка из БД
}
@Override
public boolean isAccountNonLocked() {
return true; // или проверка из БД
}
@Override
public boolean isCredentialsNonExpired() {
return true; // или проверка из БД
}
@Override
public boolean isEnabled() {
return true; // или проверка из БД
}
}
5. Как работает сравнение пароля
ВАЖНО: Пароль НИКОГДА не хранится в открытом виде!
// При регистрации пользователя — хешируем пароль
@Service
@RequiredArgsConstructor
public class UserRegistrationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public void registerUser(RegisterRequest request) {
// Хешируем пароль перед сохранением
String hashedPassword = passwordEncoder.encode(request.getPassword());
User user = User.builder()
.username(request.getUsername())
.password(hashedPassword) // Сохраняем хеш, не пароль!
.email(request.getEmail())
.build();
userRepository.save(user);
}
}
// При логине — сравниваем пароли
public class PasswordVerification {
private final PasswordEncoder passwordEncoder;
public boolean verifyPassword(String rawPassword, String hashedPassword) {
// BCrypt может сравнить:
// rawPassword (то что ввел пользователь) с
// hashedPassword (то что в БД)
return passwordEncoder.matches(rawPassword, hashedPassword);
}
}
6. Этап за этапом: что происходит при login
// ШАГИ аутентификации:
Shag 1: Пользователь отправляет логин/пароль
POST /api/v1/auth/login
{
"username": "john_doe",
"password": "mySecurePassword123"
}
Шаг 2: AuthenticationManager получает UsernamePasswordAuthenticationToken
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
"john_doe", // principal (то что идентифицирует)
"mySecurePassword123" // credentials (пароль для проверки)
)
)
Шаг 3: AuthenticationManager выбирает подходящий AuthenticationProvider
(для UsernamePassword — выбирает DaoAuthenticationProvider)
Шаг 4: DaoAuthenticationProvider выполняет:
a) Вызывает UserDetailsService.loadUserByUsername("john_doe")
b) Загружает User из БД
c) Достает хешированный пароль из БД: "$2a$10$L9..."
Шаг 5: Сравнивает пароли
passwordEncoder.matches(
"mySecurePassword123", // То что ввел пользователь
"$2a$10$L9..." // Хеш из БД
)
↓
BCrypt проверяет совпадение (это очень сложный процесс)
Шаг 6: Результат
Если совпадают → возвращает Authentication объект
Если нет → выбрасывает BadCredentialsException
Шаг 7: Если успешно
SecurityContextHolder.setAuthentication(authentication);
Теперь пользователь аутентифицирован и может делать запросы
7. Хеширование пароля: BCrypt
// BCrypt — алгоритм ONE-WAY хеширования
// Нельзя "распаковать" пароль из хеша!
public class BCryptExample {
public static void main(String[] args) {
PasswordEncoder encoder = new BCryptPasswordEncoder();
// Хешируем пароль
String rawPassword = "myPassword123";
String hashedPassword = encoder.encode(rawPassword);
// Каждый раз разный хеш! (используется random salt)
System.out.println(encoder.encode(rawPassword));
// $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86AGR0Iez3y
System.out.println(encoder.encode(rawPassword));
// $2a$10$V5c1cIqM0SeswhvBdZ8f2OJST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
// Разные хеши, хотя пароль одинаковый!
// Проверяем пароль
boolean matches1 = encoder.matches(rawPassword,
"$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86AGR0Iez3y");
System.out.println(matches1); // true
boolean matches2 = encoder.matches(rawPassword,
"$2a$10$V5c1cIqM0SeswhvBdZ8f2OJST9/PgBkqquzi.Ss7KIUgO2t0jWMUW");
System.out.println(matches2); // true
// Неправильный пароль
boolean matches3 = encoder.matches("wrongPassword",
"$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86AGR0Iez3y");
System.out.println(matches3); // false
}
}
8. Безопасность: что НЕ НУЖНО делать
// ❌ НИКОГДА ТАК НЕ ДЕЛАЙ!
public boolean verifyPasswordWrong(String inputPassword, String storedPassword) {
// Простое сравнение строк
return inputPassword.equals(storedPassword);
// Проблемы:
// 1. Пароли видны в памяти
// 2. Легко подбрать brute-force
// 3. Нет salt
}
// ❌ Простое хеширование
public String hashWrong(String password) {
return Integer.toHexString(password.hashCode());
// Проблемы: легко разломать
}
// ❌ SHA-256 без salt
public String hashWrong2(String password) throws NoSuchAlgorithmException {
return DigestUtils.sha256Hex(password);
// Проблемы: радужные таблицы работают
}
// ✅ ПРАВИЛЬНО: BCrypt с автоматическим salt
public String hashCorrect(String password) {
return new BCryptPasswordEncoder().encode(password);
// Плюсы:
// 1. Автоматический random salt
// 2. Устойчив к брут-форсу (медленный)
// 3. Адаптивный (можно увеличить сложность со временем)
}
9. JWT токены (альтернатива session)
@Service
@RequiredArgsConstructor
public class JwtTokenProvider {
@Value("${app.jwtSecret}")
private String jwtSecret;
@Value("${app.jwtExpirationMs:86400000}") // 24 часов по умолчанию
private int jwtExpirationMs;
// После успешной аутентификации
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
return Jwts.builder()
.setSubject(userPrincipal.getId().toString())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
// При следующих запросах проверяем токен
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
Итоговая схема
Пользователь Сервер
│ │
│ 1. POST /login │
│ {username, password} │
├───────────────────────────────────>│
│ │ 2. AuthenticationManager
│ │ 3. loadUserByUsername() из БД
│ │ 4. passwordEncoder.matches()
│ │ (BCrypt сравнение)
│ │
│ 5. 200 OK {JWT token} │
│<───────────────────────────────────┤
│ │
│ 6. Следующий запрос с токеном │
│ Header: Authorization: Bearer JWT │
├───────────────────────────────────>│
│ │ 7. Проверяем токен
│ │ 8. Если валидный → обрабатываем
│ 9. 200 OK {data} │
│<───────────────────────────────────┤
Best Practices
- ВСЕГДА используй PasswordEncoder (BCrypt, Argon2)
- НИКОГДА не логируй пароли
- Используй HTTPS для передачи пароля
- HTTPS only cookies для сессий
- CSRF protection при использовании cookies
- Rate limiting для защиты от брут-форса
- Двухфакторная аутентификация для повышенной безопасности
Этот процесс обеспечивает безопасное хранение и проверку пароля.