JWT токены в Kotlin: полное руководство с библиотекой jjwt
В современной веб-разработке JWT (JSON Web Tokens) стали стандартом для аутентификации и обмена информацией между клиентом и сервером. В экосистеме Kotlin, особенно при создании бэкенд-приложений на Spring Boot или Ktor, часто возникает необходимость работать с JWT. В этой статье мы разберём библиотеку io.jsonwebtoken (jjwt) — самый популярный инструмент для работы с JWT в Java/Kotlin.
Что такое JWT?
JWT — это компактный, URL-безопасный способ представления утверждений (claims) между двумя сторонами. Токен состоит из трёх частей, разделённых точками:
- Header — заголовок, содержащий тип токена и алгоритм подписи (например, HS256, RS256).
- Payload — полезная нагрузка с утверждениями (claims):
sub(субъект),iat(время выпуска),exp(время истечения) и пользовательские данные. - Signature — подпись, созданная с помощью секретного ключа или пары ключей.
Подключение библиотеки
Для работы с JWT в Kotlin мы будем использовать jjwt (io.jsonwebtoken). Добавьте зависимость в build.gradle.kts:
dependencies {
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6") // для JSON парсинга
}
Генерация JWT токена
Для создания токена необходимо задать секретный ключ, установить утверждения и подписать токен. Пример на Kotlin:
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import java.util.Date
import javax.crypto.SecretKey
fun generateToken(userId: String, secret: String): String {
val key: SecretKey = Keys.hmacShaKeyFor(secret.toByteArray())
val now = Date()
val expiration = Date(now.time + 3600_000) // +1 час
return Jwts.builder()
.subject(userId)
.issuedAt(now)
.expiration(expiration)
.claim("role", "USER")
.signWith(key)
.compact()
}
Проверка и парсинг JWT
При получении токена от клиента необходимо проверить его подпись и срок действия. Используйте Jwts.parser():
import io.jsonwebtoken.JwtException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import javax.crypto.SecretKey
fun validateToken(token: String, secret: String): Boolean {
return try {
val key: SecretKey = Keys.hmacShaKeyFor(secret.toByteArray())
Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
true
} catch (e: JwtException) {
false
}
}
fun extractUserId(token: String, secret: String): String? {
return try {
val key: SecretKey = Keys.hmacShaKeyFor(secret.toByteArray())
val claims = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.payload
claims.subject
} catch (e: Exception) {
null
}
}
Работа с кастомными claims
Вы можете добавлять любые произвольные данные в payload. Например, хранить email или роль пользователя:
fun generateTokenWithCustomClaims(userId: String, email: String, secret: String): String {
val key = Keys.hmacShaKeyFor(secret.toByteArray())
return Jwts.builder()
.subject(userId)
.claim("email", email)
.claim("isAdmin", false)
.issuedAt(Date())
.expiration(Date(System.currentTimeMillis() + 3600_000))
.signWith(key)
.compact()
}
fun extractCustomClaim(token: String, secret: String): String? {
val key = Keys.hmacShaKeyFor(secret.toByteArray())
val claims = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.payload
return claims.get("email", String::class.java)
}
Использование в Spring Boot
В реальных проектах удобно создать сервисный класс для работы с JWT. Пример с инъекцией секрета из конфигурации:
import org.springframework.stereotype.Component
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import java.util.Date
import javax.crypto.SecretKey
@Component
class JwtService(private val jwtSecret: String) {
private val key: SecretKey = Keys.hmacShaKeyFor(jwtSecret.toByteArray())
fun generateToken(userId: String): String {
return Jwts.builder()