OAuth 2.0 и JWT авторизация на Kotlin: на практике

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

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

Начать курс

Введение в OAuth 2.0 и JWT на Kotlin

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

В этой статье мы разберем, как на практике реализовать OAuth 2.0 и JWT авторизацию на Kotlin. Вы узнаете, как настроить сервер выдачи токенов, создать защищенные эндпоинты и обработать различные гранты (grant types), такие как Authorization Code и Client Credentials.

Основы: OAuth 2.0 и JWT

Что такое OAuth 2.0?

OAuth 2.0 — это протокол, который позволяет сторонним приложениям получать ограниченный доступ к ресурсам пользователя без передачи логина и пароля. Основные участники:

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

Что такое JWT?

JWT — это компактный, URL-безопасный токен, состоящий из трех частей (header, payload, signature), разделенных точками. Он используется для передачи утверждений (claims) между сторонами. Преимущества JWT:

  • Не требует хранения сессии на сервере (stateless).
  • Содержит всю необходимую информацию (например, ID пользователя, роли).
  • Подписан цифровой подписью, что гарантирует его целостность.

Реализация OAuth 2.0 с JWT на Kotlin (Spring Boot)

Рассмотрим практический пример с использованием Spring Boot и библиотеки Spring Security. Мы создадим простой сервер авторизации и защищенный ресурс.

Шаг 1: Настройка зависимостей

Добавьте в build.gradle.kts необходимые зависимости:

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

Шаг 2: Конфигурация сервера авторизации

Создайте класс конфигурации для OAuth 2.0:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.oauth2.core.AuthorizationGrantType
import org.springframework.security.oauth2.core.ClientAuthenticationMethod
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.SecurityFilterChain
import java.util.UUID

@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Bean
    fun registeredClientRepository(): RegisteredClientRepository {
        val registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("my-client")
            .clientSecret("{noop}my-secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .redirectUri("http://localhost:8080/login/oauth2/code/my-client")
            .scope("read")
            .scope("write")
            .build()
        return InMemoryRegisteredClientRepository(registeredClient)
    }

    @Bean
    fun userDetailsService(): UserDetailsService {
        val user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build()
        return InMemoryUserDetailsManager(user)
    }

    @Bean
    fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
        return http.formLogin { }.build()
    }
}

Шаг 3: Генерация и проверка JWT

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

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

@Component
class JwtUtil {
    private val secretKey: Key = Keys.hmacShaKeyFor("my-secret-key-which-must-be-at-least-256-bits-long".toByteArray())
    private val expirationMs = 3600000L // 1 час

    fun generateToken(username: String, roles: List<String>): String {
        return Jwts.builder()
            .subject(username)
            .claim("roles", roles)
            .issuedAt(Date())
            .expiration(Date(System.currentTimeMillis() + expirationMs))
            .signWith(secretKey)
            .compact()
    }

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

    fun extractUsername(token: String): String {
        return Jwts.parser()
            .verifyWith(Keys.hmacShaKeyFor(secretKey.encoded))
            .build()
            .parseSignedClaims(token)
            .payload
            .subject
    }
}

Шаг 4: Защита эндпоинтов

Настройте фильтр для проверки 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) : 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                

Блоги

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