Что такое 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