OAuth 2.0 и JWT авторизация на Kotlin: полное руководство для начинающих

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

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

Начать курс

Что такое OAuth 2.0 и JWT и зачем они нужны в Kotlin?

В современной веб-разработке безопасность данных и управление доступом — одни из ключевых задач. Если вы пишете серверное приложение на Kotlin, рано или поздно столкнетесь с необходимостью защитить API или организовать вход через сторонние сервисы (Google, GitHub, VK). Для этого существуют два стандарта — OAuth 2.0 (протокол авторизации) и JWT (JSON Web Token — формат токена).

В этой статье мы разберем, как работают OAuth 2.0 и JWT, и реализуем простую авторизацию на Kotlin с использованием популярных библиотек. Материал рассчитан на начинающих, поэтому все сложные термины будут объяснены простым языком.

Основные понятия: OAuth 2.0 и JWT

Что такое OAuth 2.0?

OAuth 2.0 — это протокол, который позволяет одному сервису (например, вашему приложению) получить ограниченный доступ к ресурсам пользователя на другом сервисе (например, к его профилю в Google) без передачи логина и пароля. Представьте, что вы хотите войти на сайт через аккаунт Google — это и есть OAuth 2.0.

Основные участники OAuth 2.0:

  • Resource Owner — пользователь, владелец данных.
  • Client — ваше приложение, которое хочет получить доступ.
  • Authorization Server — сервер, который выдает разрешение (например, Google).
  • Resource Server — сервер, хранящий данные пользователя (например, API Google).

Что такое JWT?

JWT — это компактный и самодостаточный формат токена, который содержит информацию о пользователе (например, ID, роль, срок действия). Токен подписывается цифровой подписью, поэтому его нельзя подделать. JWT состоит из трех частей, разделенных точками:

header.payload.signature

Пример декодированного JWT:

{
  "sub": "1234567890",
  "name": "Иван Петров",
  "iat": 1516239022,
  "exp": 1516242622
}

Главное преимущество JWT — серверу не нужно хранить сессии в базе данных. Вся информация уже внутри токена.

Реализация JWT авторизации на Kotlin с Spring Boot

Рассмотрим практический пример. Мы создадим REST API на Kotlin с использованием Spring Boot и Spring Security. Наше приложение будет выдавать JWT токен при успешной аутентификации.

Шаг 1. Добавление зависимостей

Добавьте в build.gradle.kts следующие зависимости:

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("io.jsonwebtoken:jjwt-api:0.12.5")
    runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5")
    runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.5")
}

Шаг 2. Создание JWT сервиса

Создадим класс JwtService, который будет генерировать и проверять токены:

import io.jsonwebtoken.Claims
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import org.springframework.stereotype.Service
import java.util.*

@Service
class JwtService {
    private val secretKey = "mySecretKeyForJwtToken12345678901234567890" // минимум 32 символа
    private val expirationMs = 3600000L // 1 час

    fun generateToken(username: String): String {
        val claims: Map = mapOf(
            "sub" to username,
            "iat" to Date(),
            "exp" to Date(System.currentTimeMillis() + expirationMs)
        )
        return Jwts.builder()
            .claims(claims)
            .signWith(Keys.hmacShaKeyFor(secretKey.toByteArray()))
            .compact()
    }

    fun extractUsername(token: String): String? {
        return try {
            val claims = parseToken(token)
            claims.subject
        } catch (e: Exception) {
            null
        }
    }

    fun isTokenValid(token: String): Boolean {
        return try {
            parseToken(token)
            true
        } catch (e: Exception) {
            false
        }
    }

    private fun parseToken(token: String): Claims {
        return Jwts.parser()
            .verifyWith(Keys.hmacShaKeyFor(secretKey.toByteArray()))
            .build()
            .parseSignedClaims(token)
            .payload
    }
}

Шаг 3. Контроллер для аутентификации

Создадим простой контроллер, который принимает логин и пароль (пока без базы данных) и возвращает JWT:

import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/auth")
class AuthController(private val jwtService: JwtService) {

    @PostMapping("/login")
    fun login(@RequestBody request: LoginRequest): Map<String, String> {
        // В реальном приложении здесь проверка пользователя из БД
        if (request.username != "admin" || request.password != "password") {
            throw RuntimeException("Неверные учетные данные")
        }
        val token = jwtService.generateToken(request.username)
        return mapOf("token" to token)
    }
}

data class LoginRequest(val username: String, val password: String)

Шаг 4. Фильтр JWT для Spring Security

Теперь настроим Spring Security, чтобы он проверял JWT токен при каждом запросе к защищенным endpoint'ам:

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.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter

@Component
class JwtAuthenticationFilter(
    private val jwtService: JwtService
) : OncePerRequestFilter() {

    override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    ) {
        val authHeader = request.getHeader("Authorization")
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            val token = authHeader.substring(7)
            if (jwtService.isTokenValid(token)) {
                val username = jwtService.extractUsername(token)
                if (username != null) {
                    val authentication = UsernamePasswordAuthenticationToken(
                        username, null, listOf()
                    )
                    SecurityContextHolder.getContext().authentication = authentication
                }
            }
        }
        filterChain.doFilter(request, response)
    }
}

Добавьте конфигурацию Security:

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

Блоги

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