Авторизация и аутентификация в Kotlin: JWT, Spring Security и Ktor

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Авторизация и аутентификация в Kotlin: полное руководство

В современной веб-разработке на Kotlin безопасность приложения начинается с правильной реализации аутентификации (проверки подлинности пользователя) и авторизации (проверки прав доступа). В этой статье мы рассмотрим основные подходы, библиотеки и инструменты для реализации этих механизмов на платформах Spring Boot и Ktor.

Основные понятия

Прежде чем перейти к коду, разберемся с терминами:

  • Аутентификация — процесс проверки, что пользователь является тем, за кого себя выдает (обычно через логин/пароль, токен или биометрию).
  • Авторизация — определение прав доступа аутентифицированного пользователя (например, доступ к определенным маршрутам или данным).
  • JWT (JSON Web Token) — компактный и самодостаточный токен для передачи данных между сторонами.
  • BCrypt — алгоритм хеширования паролей с солью, устойчивый к атакам перебором.

Аутентификация с помощью JWT в Spring Boot

Spring Security предоставляет мощную инфраструктуру для аутентификации. Рассмотрим реализацию JWT аутентификации с использованием библиотеки io.jsonwebtoken:jjwt.

// build.gradle.kts
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("io.jsonwebtoken:jjwt-api:0.12.3")
    runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3")
    runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3")
}

Создадим утилитный класс для работы с JWT:

import io.jsonwebtoken.*
import io.jsonwebtoken.security.Keys
import org.springframework.stereotype.Component
import java.security.Key
import java.util.*

@Component
class JwtUtil {
    private val secretKey: Key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
    private val expirationMs: Long = 3600000 // 1 час

    fun generateToken(username: String): String {
        return Jwts.builder()
            .subject(username)
            .issuedAt(Date())
            .expiration(Date(System.currentTimeMillis() + expirationMs))
            .signWith(secretKey)
            .compact()
    }

    fun extractUsername(token: String): String? {
        return try {
            Jwts.parser().verifyWith(secretKey).build()
                .parseSignedClaims(token).payload.subject
        } catch (e: Exception) {
            null
        }
    }

    fun validateToken(token: String): Boolean {
        return try {
            Jwts.parser().verifyWith(secretKey).build()
                .parseSignedClaims(token)
            true
        } catch (e: JwtException) {
            false
        }
    }
}

Фильтр аутентификации для Spring Security

Теперь создадим фильтр, который будет перехватывать запросы и проверять JWT токен:

import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
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

@Component
class JwtAuthFilter(
    private val jwtUtil: JwtUtil,
    private val userDetailsService: org.springframework.security.core.userdetails.UserDetailsService
) : OncePerRequestFilter() {

    override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        chain: FilterChain
    ) {
        val authHeader = request.getHeader("Authorization")
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            chain.doFilter(request, response)
            return
        }

        val token = authHeader.substring(7)
        val username = jwtUtil.extractUsername(token)

        if (username != null && SecurityContextHolder.getContext().authentication == null) {
            val userDetails = userDetailsService.loadUserByUsername(username)
            if (jwtUtil.validateToken(token)) {
                val authToken = UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.authorities
                )
                authToken.details = WebAuthenticationDetailsSource().buildDetails(request)
                SecurityContextHolder.getContext().authentication = authToken
            }
        }
        chain.doFilter(request, response)
    }
}

Аутентификация в Ktor

Фреймворк Ktor имеет встроенную поддержку аутентификации через плагин ktor-server-auth. Рассмотрим пример с JWT:

// build.gradle.kts
dependencies {
    implementation("io.ktor:ktor-server-auth:2.3.7")
    implementation("io.ktor:ktor-server-auth-jwt:2.3.7")
    implementation("com.auth0:java-jwt:4.4.0")
}
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm

fun Application.authModule() {
    // Настройка JWT аутентификации
    install(Authentication) {
        jwt("auth                    

Рекомендаций