Введение: почему "своя велосипедная" аутентификация — это риск?
Представьте: вы пишете приложение на Kotlin — будь то Android-клиент или бэкенд на Ktor/Spring Boot. Вы быстро накидываете логин-форму, сохраняете пароль в SharedPreferences и радуетесь, что всё работает. А через неделю получаете сообщение: "Ваше приложение взломали, данные утекли". Знакомо? К сожалению, это классика.
Безопасная аутентификация в Kotlin — это не просто "проверить пароль". Это целая экосистема: хеширование, токены, защита от подделки запросов, безопасное хранение секретов. В этой статье мы разберём, как не допустить типичных ошибок и построить надёжную систему входа. Поехали!
1. Хеширование паролей: bcrypt и его друзья
Первое правило безопасности: никогда не храните пароли в открытом виде. Даже если вы используете HTTPS, база данных может быть скомпрометирована. Ваша задача — хранить только хеш.
Почему MD5 и SHA — зло?
Эти алгоритмы быстрые. А быстрые хеши — мечта взломщика: он может перебрать миллиарды комбинаций в секунду. Вместо них используйте bcrypt, scrypt или Argon2. Они намеренно медленные и устойчивы к атакам на GPU.
Пример на Kotlin с использованием библиотеки jbcrypt:
import org.mindrot.jbcrypt.BCrypt
fun hashPassword(password: String): String {
val salt = BCrypt.gensalt(12) // cost factor 12 — оптимально
return BCrypt.hashpw(password, salt)
}
fun checkPassword(password: String, hashed: String): Boolean {
return BCrypt.checkpw(password, hashed)
}
Важно: соль генерируется автоматически и встраивается в хеш. Вам не нужно хранить её отдельно.
2. JWT: лёгкая и безопасная аутентификация
JSON Web Token (JWT) — стандарт для передачи утверждений (claims) между сторонами. В Kotlin вы можете использовать библиотеку java-jwt (Auth0) или jsonwebtoken (jjwt).
Генерация JWT на Kotlin
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.security.Keys
import java.util.Date
fun generateToken(userId: String, secretKey: String): String {
val key = Keys.hmacShaKeyFor(secretKey.toByteArray())
return Jwts.builder()
.setSubject(userId)
.setIssuedAt(Date())
.setExpiration(Date(System.currentTimeMillis() + 3600_000)) // 1 час
.signWith(key, SignatureAlgorithm.HS256)
.compact()
}
Проверка токена
fun validateToken(token: String, secretKey: String): String? {
return try {
val key = Keys.hmacShaKeyFor(secretKey.toByteArray())
val claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.body
claims.subject // возвращаем userId
} catch (e: Exception) {
null // токен невалиден или истёк
}
}
Совет: не храните секретный ключ в коде. Используйте переменные окружения или Vault.
3. Безопасное хранение токенов на Android
Если вы пишете мобильное приложение на Kotlin, токен доступа нельзя хранить в SharedPreferences — это открытый файл, который могут прочитать другие приложения или злоумышленник с root-доступом.
Используйте EncryptedSharedPreferences (из AndroidX Security) или Keystore:
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
fun saveToken(context: Context, token: String) {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPreferences = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
sharedPreferences.edit().putString("auth_token", token).apply()
}
Для веб-приложений используйте HttpOnly cookies — они недоступны для JavaScript и защищены от XSS-атак.
4. OAuth2 и Social Login: делегируем безопасность
Зачем писать свою аутентификацию, если можно доверить её Google, GitHub или VK? OAuth2 — стандарт де-факто для авторизации через сторонние сервисы.
В Kotlin (Spring Boot) подключить OAuth2 просто:
spring.security.oauth2.client.registration.google.client-id=ВАШ_CLIENT_ID
spring.security.oauth2.client.registration.google.client-secret=ВАШ_SECRET
А на стороне кода:
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.oauth2Login()
.and()
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
}
}
Плюсы: не нужно хранить пароли, пользователи доверяют знакомому провайдеру, поддержка MFA (многофакторной аутентификации) из коробки.
5. Защита от распространённых атак
SQL-инъекции
В Kotlin с JPA или Exposed вы защищены, если используете параметризованные запросы. Но если пишете raw SQL — всегда используйте ? вместо конкатенации строк:
// Плохо
val query = "SELECT * FROM users WHERE login = '$login'"
// Хорошо
val prepared = connection.prepareStatement("SELECT * FROM users WHERE login = ?")
prepared.setString(1, login)
CSRF (межсайтовая подделка запроса)
Для веб-приложений включите CSRF-защиту (Spring Security делает это по умолчанию). Для REST API используйте JWT — он защищает от CSRF, так как токен не передаётся автоматически через cookies.
Брутфорс и rate limiting
Ограничьте количество попыток входа с одного IP:
import io.github.bucket4j.Bandwidth
import io.github.bucket4j.Bucket
import java.time.Duration
fun createLoginBucket(): Bucket {
val limit = Bandwidth.simple(10, Duration.ofMinutes(1))
return Bucket.builder().addLimit(limit).build()
}
6. OWASP Top 10 и Kotlin: что проверить в первую очередь
Рекомендую пройтись по чек-листу OWASP для веб-приложений. Вот ключевые пункты для Kotlin-проекта:
- A1: Broken Access Control — проверяйте права на