OAuth 2.0 и JWT авторизация на Kotlin: для начинающих
Современные веб-приложения и API редко обходятся без системы аутентификации и авторизации. Два самых популярных стандарта в этой области — OAuth 2.0 и JWT (JSON Web Token). В этой статье мы разберём, как реализовать авторизацию на Kotlin с использованием этих технологий. Материал рассчитан на начинающих разработчиков, знакомых с основами языка и фреймворка Spring Boot.
Что такое OAuth 2.0 и JWT?
Прежде чем писать код, важно понять теорию.
OAuth 2.0 — это протокол авторизации, который позволяет приложениям получать ограниченный доступ к ресурсам пользователя без передачи логина и пароля. Представьте, что вы входите на сайт через "Войти через Google" — это и есть OAuth 2.0.
JWT — это компактный и самодостаточный формат токена. Он состоит из трёх частей: заголовок (header), полезная нагрузка (payload) и подпись (signature). JWT часто используется вместе с OAuth 2.0 для передачи информации об аутентификации.
Основные потоки OAuth 2.0:
- Authorization Code Flow — для серверных приложений (самый безопасный)
- Client Credentials Flow — для сервер-серверной коммуникации
- Implicit Flow — устарел, не рекомендуется
- Resource Owner Password Flow — только если доверяете клиенту
В этой статье мы сосредоточимся на Client Credentials Flow, так как он проще всего для понимания и часто применяется в микросервисах.
Настройка проекта Spring Boot на Kotlin
Создадим новый проект Spring Boot с поддержкой Kotlin. Добавим необходимые зависимости в build.gradle.kts:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
}
Эти зависимости обеспечивают поддержку Spring Security, OAuth 2.0 Resource Server и библиотеку JJWT для работы с JWT.
Создание JWT токенов на Kotlin
Напишем сервис для генерации и валидации JWT. Это основа нашей авторизации.
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.security.Keys
import org.springframework.stereotype.Service
import java.util.Date
@Service
class JwtService {
private val secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256)
private val expirationMs = 3600000L // 1 час
fun generateToken(clientId: String): String {
val now = Date()
val expiryDate = Date(now.time + expirationMs)
return Jwts.builder()
.setSubject(clientId)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(secretKey)
.compact()
}
fun validateToken(token: String): Boolean {
return try {
Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
true
} catch (e: Exception) {
false
}
}
fun getClientIdFromToken(token: String): String {
val claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.body
return claims.subject
}
}
В этом сервисе мы используем симметричный ключ. В продакшене лучше использовать асимметричное шифрование (RS256) и хранить ключи в безопасном хранилище.
Реализация OAuth 2.0 Client Credentials Flow
Теперь настроим Spring Security для работы с 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.http.SessionCreationPolicy
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.config.annotation.web.invoke
@Configuration
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf { disable() }
sessionManagement {
sessionCreationPolicy = SessionCreationPolicy.STATELESS
}
authorizeHttpRequests {
authorize("/api/public/**", permitAll)
authorize("/api/token", permitAll)
authorize("/api/**", authenticated)
}
oauth2ResourceServer {
jwt { }
}
}
return http.build()
}
}
Обратите внимание: мы используем DSL синтаксис Kotlin для конфигурации. Это делает код более читаемым.
Создадим контроллер для выдачи токенов:
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api")
class AuthController(private val jwtService: JwtService) {
@PostMapping("/token")
fun getToken(@RequestParam clientId: String,
@RequestParam clientSecret: String): ResponseEntity<Map<String, String>> {
// В реальном проекте здесь проверка clientSecret
// Для простоты пропустим
val token = jwtService.generateToken(clientId)
return ResponseEntity.ok(mapOf("access_token" to token, "token_type" to "Bearer"))
}
@GetMapping("/secure")
fun secureEndpoint(@AuthenticationPrincipal jwt: Jwt): String {
val clientId = jwt.subject
return "Hello, $clientId! You have access to this secure endpoint."
}
}
Защита эндпоинтов и работа с токенами
Теперь добавим публичный эндпоинт и защищённый. Пользователь сначала получает токен через /api/token, затем использует его для доступа к /api/secure.
Пример запроса на получение токена:
curl -X POST http://localhost:8080/api/token \
-d "clientId=my-client" \
-d "clientSecret=my-secret"
Ответ будет содержать JWT токен. Затем используем его для доступа к защищённому ресурсу:
curl -H "Authorization: Bearer <ваш_токен>" \
http://localhost:8080/api/secure
Чтобы сделать эндпоинт