Java for Beginner
715 subscribers
646 photos
173 videos
12 files
1.01K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Задачи и жизненный цикл в Gradle


Task API: Task, DefaultTask, @TaskAction

Задачи (tasks) — это основная единица работы в Gradle, представляющая такие действия, как компиляция, тестирование или упаковка. Task API предоставляет инструменты для создания и настройки задач.


Основные компоненты

Task:
Интерфейс org.gradle.api.Task, определяющий базовую функциональность задачи (например, выполнение, зависимости).
Все задачи в Gradle реализуют этот интерфейс.


DefaultTask:
Класс org.gradle.api.DefaultTask, стандартная реализация интерфейса Task.
Используется для создания пользовательских задач.


Пример (Groovy DSL):
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

class CustomTask extends DefaultTask {
@TaskAction
void executeTask() {
println 'Executing custom task'
}
}

tasks.register('customTask', CustomTask)


@TaskAction:
Аннотация, указывающая метод, который выполняется при запуске задачи.

Пример (Kotlin DSL):
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

open class CustomTask : DefaultTask() {
@TaskAction
fun executeTask() {
println("Executing custom task")
}
}

tasks.register<CustomTask>("customTask")


В памяти: Каждая задача представлена как объект в JVM, содержащий метаданные (имя, зависимости, действия). Gradle загружает все задачи в память во время фазы конфигурации, что увеличивает потребление памяти пропорционально их количеству. Плагины, такие как java, добавляют множество задач (например, compileJava, test), увеличивая overhead.


Gradle Lifecycle

Жизненный цикл Gradle состоит из трех фаз: Initialization, Configuration и Execution. Каждая фаза выполняет определенные функции и влияет на производительность и память.

Initialization Phase:
Gradle загружает settings.gradle для определения структуры проекта (корневое имя, подмодули).
Создает объекты Project для корневого проекта и подпроектов.
Устанавливает начальные настройки, такие как репозитории и плагины.

В памяти: Минимальная фаза по потреблению ресурсов, так как загружается только settings.gradle и связанные плагины. Объем памяти зависит от количества модулей (обычно 50-100 МБ).


Configuration Phase:

Gradle парсит все файлы build.gradle, создавая модель проекта и граф задач (Directed Acyclic Graph, DAG).
Все скрипты конфигурации выполняются, даже для задач, которые не будут запущены.
Пример: Определение зависимостей, задач и плагинов.

В памяти: Самая ресурсоемкая фаза, так как Gradle загружает и компилирует все скрипты, плагины и зависимости. Для крупных проектов может потребоваться 500-1000 МБ памяти.

Оптимизация: Используйте флаг --configure-on-demand для конфигурации только необходимых модулей:
./gradlew build --configure-on-demand


Execution Phase:
Gradle выполняет задачи, указанные в командной строке (например, ./gradlew build), в порядке, определенном DAG.
Инкрементальность пропускает задачи, чьи входные/выходные данные не изменились (см. ниже).

В памяти: Зависит от сложности задач. Например, compileJava загружает исходные файлы и зависимости, а test — тестовые классы и фреймворки. Параллельное выполнение (--parallel) увеличивает пиковое потребление памяти.


Нюансы:

Конфигурация выполняется всегда, что замедляет сборку, особенно для крупных проектов.
Gradle Daemon сохраняет JVM между сборками, ускоряя повторные запуски, но увеличивая базовое потребление памяти (200-300 МБ).
Используйте --no-daemon для одноразовых сборок:

./gradlew build --no-daemon


#Java #middle #Gradle #Task #Lifecycle
👍2
Task Graph, зависимости между задачами

Gradle строит Directed Acyclic Graph (DAG) для задач, где узлы — задачи, а ребра — зависимости. Это определяет порядок выполнения.


Зависимости между задачами

dependsOn:
Указывает, что задача зависит от выполнения других задач.

Пример:
task compileJava {
doLast { println 'Compiling Java' }
}
task test(dependsOn: compileJava) {
doLast { println 'Running tests' }
}

Gradle выполнит compileJava перед test.


mustRunAfter:
Определяет порядок выполнения без строгой зависимости.

Пример:

task taskA {
doLast { println 'Task A' }
}
task taskB {
mustRunAfter 'taskA'
doLast { println 'Task B' }
}

Если обе задачи выполняются, taskB будет после taskA, но taskB может выполняться отдельно.


finalizedBy:
Указывает задачу, которая выполняется после завершения текущей, даже при ошибке.

Пример:
task build {
doLast { println 'Building' }
}
task cleanUp {
doLast { println 'Cleaning up' }
}
build.finalizedBy cleanUp


В памяти: DAG задач хранится как структура данных в JVM, где каждая задача — объект с метаданными (зависимости, действия). Размер графа пропорционален количеству задач, что может увеличить потребление памяти до нескольких сотен МБ в крупных проектах.



Incremental Build и Up-to-Date Checks

Gradle оптимизирует производительность за счет инкрементальной сборки, пропуская задачи, чьи входные/выходные данные не изменились.

Механизм:
Gradle проверяет хэши входных (исходные файлы, свойства) и выходных данных (скомпилированные классы, JAR).
Если хэши совпадают, задача помечается как up-to-date и пропускается.


Пример вывода:
> Task :compileJava UP-TO-DATE


Пример настройки:
tasks.named('compileJava') {
inputs.files('src/main/java')
outputs.dir('build/classes/java/main')
}


В памяти: Gradle хранит хэши входов/выходов в памяти и в ~/.gradle/caches для сравнения. Это добавляет небольшой overhead (около 10-50 МБ), но значительно ускоряет сборку.


Нюансы:

Неправильная настройка входов/выходов может привести к ненужному выполнению задач.
Используйте --info для анализа, почему задача не была пропущена:

./gradlew build --info



Gradle Inputs/Outputs (Task Inputs/Outputs)

Задачи Gradle имеют входы и выходы, которые определяют, что влияет на выполнение задачи и что она производит.

Inputs:
Файлы, свойства или другие данные, от которых зависит задача.

Пример:
task processFiles {
inputs.files fileTree('src/main/resources')
doLast {
println 'Processing files'
}
}


Outputs:
Файлы или директории, создаваемые задачей.

Пример:
task generateReport {
outputs.file file('build/report.txt')
doLast {
file('build/report.txt').text = 'Report content'
}
}


В памяти: Gradle хранит метаданные входов/выходов в памяти и кэширует хэши в ~/.gradle/caches. Для задач с большим количеством файлов (например, compileJava) это увеличивает потребление памяти, так как Gradle сканирует файловую систему.


Нюансы:
Явно указывайте входы/выходы для кастомных задач, чтобы включить инкрементальность.

Используйте inputs.property для не-файловых входов:
task customTask {
inputs.property 'version', project.version
doLast { println "Version: ${project.version}" }
}



#Java #middle #Gradle #Task #Lifecycle
👍1
Do-first/do-last и ленивость (Provider, Property)

Gradle поддерживает гибкую настройку задач через doFirst и doLast, а также ленивую конфигурацию через Provider и Property.

doFirst и doLast:
doFirst: Добавляет действие в начало выполнения задачи.
doLast: Добавляет действие в конец выполнения задачи.


Пример:
task example {
doFirst { println 'Starting task' }
doLast { println 'Ending task' }
}


Ленивость (Provider, Property):
Gradle использует ленивую оценку для отсрочки вычислений до фазы выполнения.

Provider: Интерфейс для ленивых значений.def version = providers.provider { project.version }
task printVersion {
doLast {
println "Version: ${version.get()}"
}
}


Property: Для управления свойствами задачи.

task customTask {
def outputFile = objects.property(String)
outputFile.set('build/output.txt')
doLast {
println "Output: ${outputFile.get()}"
}
}


В памяти: doFirst и doLast добавляют действия как объекты в задачу, минимально увеличивая память. Ленивые Provider и Property хранят ссылки на значения, а не сами значения, что оптимизирует память до их вычисления в фазе выполнения.



Gradle Listeners и хуки

Gradle предоставляет хуки для мониторинга и настройки жизненного цикла и задач.

BuildListener:
Устаревший интерфейс для мониторинга событий сборки.

Пример:
gradle.buildFinished {
println 'Build completed'
}



TaskExecutionListener:

Отслеживает выполнение задач.

Пример:
gradle.taskGraph.whenReady {
println 'Task graph is ready'
}


Project.afterEvaluate:
Выполняется после фазы конфигурации.

Пример:
project.afterEvaluate {
println 'Project configured'
}

В памяти: Хуки создают дополнительные объекты-слушатели в памяти, увеличивая overhead. Для крупных проектов с множеством слушателей это может добавить 10-50 МБ памяти.


Нюансы:

Используйте хуки с осторожностью, чтобы избежать замедления сборки.
Для сложной логики создавайте плагины вместо хуков.



Gradle Build Cache

Build Cache позволяет кэшировать результаты задач для повторного использования между сборками или машинами.

Настройка:
buildCache {
local {
enabled = true
}
remote(HttpBuildCache) {
url = 'https://cache.example.com/'
push = true
}
}


Как работает:
Gradle кэширует выходные данные задач (например, скомпилированные классы, JAR) в ~/.gradle/caches/build-cache или на удаленном сервере.
При повторной сборке Gradle проверяет хэши входов/выходов и использует кэшированные результаты, если они совпадают.
Пример: Задача compileJava кэширует классы в build/classes.


Использование:
Включите кэш:
./gradlew build --build-cache


Очистка локального кэша:
rm -rf ~/.gradle/caches/build-cache


В памяти: Build Cache требует хранения хэшей и метаданных в памяти во время выполнения, что добавляет 50-100 МБ overhead. Удаленный кэш увеличивает сетевые операции, но снижает локальные вычисления.


Нюансы:
Настройте входы/выходы задач точно, чтобы кэш работал корректно.
Build Cache наиболее эффективен для CI/CD, где результаты задач переиспользуются между сборками.



#Java #middle #Gradle #Task #Lifecycle
👍1
Плагины и расширение функциональности в Gradle

Плагины в Gradle — это основной механизм расширения функциональности, позволяющий добавлять задачи, конфигурации и зависимости для автоматизации сборки. Они обеспечивают модульность и гибкость, позволяя адаптировать Gradle под конкретные проекты. Эта статья подробно описывает типы плагинов, встроенные и сторонние плагины, создание собственных плагинов, публикацию на Gradle Plugin Portal, стратегии разрешения плагинов и управление через pluginManagement. Особое внимание уделяется внутренним механизмам, управлению памятью и нюансам.



Типы плагинов

Gradle поддерживает два основных типа плагинов: Script Plugins и Binary Plugins.

Script Plugin (apply from)
Описание: Скриптовые плагины — это файлы Gradle (обычно .gradle или .gradle.kts), которые содержат логику сборки и подключаются к build.gradle через apply from.

Пример (Groovy DSL):
apply from: 'other.gradle'


Содержимое other.gradle:
task customTask {
doLast {
println 'Custom task from script plugin'
}
}


Kotlin DSL:
apply(from = "other.gradle.kts")


Содержимое other.gradle.kts:
tasks.register("customTask") {
doLast {
println("Custom task from script plugin")
}
}


Использование: Для небольших проектов или повторно используемых фрагментов кода в рамках одного проекта.
В памяти: Скриптовые плагины парсятся как обычные Gradle-скрипты, добавляя задачи и конфигурации в модель проекта. Это увеличивает потребление памяти, аналогично основному build.gradle, но overhead минимален (10-50 МБ).



Binary Plugin (apply plugin:)

Описание: Бинарные плагины — это скомпилированные Java/Groovy/Kotlin-классы, распространяемые как JAR-файлы. Они подключаются через apply plugin или блок plugins.

Пример (Groovy DSL):
apply plugin: 'java'


В памяти: Бинарные плагины загружаются как Java-классы в JVM, включая их зависимости. Это увеличивает потребление памяти пропорционально сложности плагина (50-200 МБ для крупных плагинов, таких как android).


plugins {} vs apply plugin:

plugins {}:
Современный способ подключения плагинов, введенный в Gradle 2.1.
Использует декларативный синтаксис и разрешает плагины из Gradle Plugin Portal или репозиториев.


Пример:
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.18'
}


Kotlin DSL:
plugins {
java
id("org.springframework.boot") version "2.7.18"
}


Преимущества: Автоматическое разрешение версий, поддержка Gradle Plugin Portal, меньшая вероятность ошибок.



apply plugin:
Традиционный способ, используемый в старых версиях Gradle.

Требует явного указания зависимости в buildscript:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.7.18'
}
}
apply plugin: 'org.springframework.boot'


Недостатки: Более многословный, требует ручного управления зависимостями.

В памяти: plugins {} использует внутренний механизм разрешения Gradle, минимизируя overhead по сравнению с buildscript, который загружает дополнительные зависимости в classpath.

Рекомендация: Используйте plugins {} для современных проектов, так как он проще и поддерживает автоматическое разрешение.


#Java #middle #Gradle #Task #Plugin
👍2
Built-in плагины

Gradle поставляется с набором встроенных плагинов, которые покрывают стандартные сценарии сборки.

java:
Добавляет задачи для компиляции, тестирования и упаковки Java-проектов (например, compileJava, test, jar).

Пример:
plugins {
id 'java'
}


В памяти: Загружает задачи и конфигурации (implementation, testImplementation), увеличивая модель проекта (50-100 МБ).


application:
Добавляет задачи для запуска Java-приложений (run, installDist).

Пример:
plugins {
id 'application'
}
application {
mainClass = 'com.example.Main'
}


В памяти: Добавляет задачи и classpath, минимально увеличивая overhead.


base:
Базовый плагин, добавляющий задачи жизненного цикла (clean, assemble, check).

Пример:
plugins {
id 'base'
}

В памяти: Легковесный, добавляет минимальное количество задач.


java-library:
Расширяет java, добавляя конфигурации api и implementation для библиотек.

Пример:

plugins {
id 'java-library'
}
dependencies {
api 'org.apache.commons:commons-lang3:3.12.0'
}

В памяти: Увеличивает граф зависимостей за счет дополнительных конфигураций.


checkstyle:
Интегрирует проверку стиля кода с помощью Checkstyle.

Пример:
plugins {
id 'checkstyle'
}
checkstyle {
toolVersion = '8.45'
configFile = file('config/checkstyle/checkstyle.xml')
}

В памяти: Загружает конфигурацию Checkstyle и отчеты, увеличивая потребление памяти (50-100 МБ для крупных проектов).


maven-publish:

Позволяет публиковать артефакты в Maven-репозитории.

Пример:
plugins {
id 'maven-publish'
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
maven {
url 'https://nexus.example.com/repository/maven-releases'
}
}
}

В памяти: Загружает метаданные публикации и артефакты, увеличивая overhead при публикации.



#Java #middle #Gradle #Task #Plugin
👍2
Плагины для Kotlin, Android

Kotlin:
Плагин org.jetbrains.kotlin.jvm для JVM-проектов или org.jetbrains.kotlin.android для Android.

Пример:
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.9.0'
}
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.0'
}

В памяти: Загружает Kotlin-компилятор и зависимости, добавляя 100-200 МБ overhead.


Android:

Плагин com.android.application или com.android.library для Android-проектов.

Пример:
plugins {
id 'com.android.application' version '8.1.0'
id 'org.jetbrains.kotlin.android' version '1.9.0'
}
android {
compileSdk 33
defaultConfig {
applicationId 'com.example.app'
minSdk 21
targetSdk 33
versionCode 1
versionName '1.0'
}
}

В памяти: Android-плагины загружают Android SDK, инструменты сборки (dexer, aapt2) и зависимости, что может потребовать 500-1000 МБ памяти.



Создание собственных плагинов

Собственные плагины позволяют кастомизировать сборку. Они могут быть написаны на Groovy, Kotlin или Java.

Плагин на Groovy

Создайте проект с структурой:
my-plugin/
├── src/main/groovy/com/example/MyPlugin.groovy
├── build.gradle


Реализуйте плагин:
package com.example
import org.gradle.api.Plugin
import org.gradle.api.Project

class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.tasks.register('myTask') {
doLast {
println 'Hello from MyPlugin!'
}
}
}
}


Настройте build.gradle:
plugins {
id 'groovy'
id 'maven-publish'
}
group = 'com.example'
version = '1.0'
publishing {
publications {
maven(MavenPublication) {
from components.java
}
}
}


Опубликуйте:
./gradlew publish.

В памяти: Groovy-плагины загружают Groovy-библиотеки, добавляя 50-100 МБ overhead.



Плагин на Kotlin

Создайте проект:
my-plugin/
├── src/main/kotlin/com/example/MyPlugin.kt
├── build.gradle.kts


Реализуйте плагин:
package com.example
import org.gradle.api.Plugin
import org.gradle.api.Project

class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("myTask") {
doLast {
println("Hello from MyPlugin!")
}
}
}
}


Настройте build.gradle.kts:
plugins {
`kotlin-dsl`
`maven-publish`
}
group = "com.example"
version = "1.0"
publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
}


Опубликуйте:
./gradlew publish.

В памяти: Kotlin-плагины загружают Kotlin-библиотеки, добавляя 50-100 МБ overhead, но обеспечивают строгую типизацию.


Плагин на Java

Аналогично, но используйте Java-классы:
package com.example;
import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class MyPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().register("myTask", task -> {
task.doLast(t -> System.out.println("Hello from MyPlugin!"));
});
}
}

В памяти: Java-плагины легче, чем Groovy/Kotlin, так как не требуют дополнительных библиотек DSL.



Публикация плагина
Опубликуйте в локальный репозиторий:
./gradlew publishToMavenLocal


Используйте в другом проекте:
plugins {
id 'com.example.my-plugin' version '1.0'
}



#Java #middle #Gradle #Task #Plugin
👍2
Gradle Plugin Portal

Gradle Plugin Portal (plugins.gradle.org) — центральный репозиторий для публикации и загрузки плагинов.

Публикация:
Зарегистрируйтесь на plugins.gradle.org.
Получите API-ключ.


Настройте build.gradle:
plugins {
id 'com.gradle.plugin-publish' version '1.2.0'
}
pluginBundle {
plugins {
myPlugin {
id = 'com.example.my-plugin'
displayName = 'My Plugin'
description = 'A custom Gradle plugin'
tags = ['custom', 'example']
version = '1.0'
}
}
}


Опубликуйте:
./gradlew publishPlugins.


Использование:
plugins {
id 'com.example.my-plugin' version '1.0'
}

В памяти: Gradle Plugin Portal загружает метаданные плагинов в память при разрешении, добавляя небольшой overhead (10-50 МБ).


Plugin Resolution Strategy

Gradle разрешает плагины из репозиториев, указанных в pluginManagement или buildscript.

Стратегия разрешения:
Поиск: Gradle ищет плагин в Gradle Plugin Portal, Maven Central или пользовательских репозиториях.
Конфликты: Если плагин доступен в нескольких версиях, Gradle выбирает новейшую или указанную версию.


Настройка:
configurations.all {
resolutionStrategy {
force 'com.example:my-plugin:1.0'
}
}

В памяти: Разрешение плагинов загружает их метаданные и зависимости в память, аналогично зависимостям проекта.


pluginManagement в settings.gradle

Блок pluginManagement в settings.gradle позволяет централизованно управлять плагинами для всех модулей.

Пример:
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
plugins {
id 'org.springframework.boot' version '2.7.18'
}
}


Kotlin DSL:
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
plugins {
id("org.springframework.boot") version "2.7.18"
}
}


Назначение:
Указывает репозитории для плагинов.
Фиксирует версии плагинов для всех модулей.


Поддерживает кастомные разрешения:

pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.id == 'com.example.my-plugin') {
useModule('com.example:my-plugin:1.0')
}
}
}
}

В памяти: pluginManagement загружает метаданные плагинов во время инициализации, добавляя минимальный overhead (10-30 МБ), но обеспечивая согласованность версий.



Нюансы и внутренние механизмы

Управление памятью:
Плагины загружаются как Java-классы в JVM, включая их зависимости. Крупные плагины (например, android) могут добавлять 500-1000 МБ overhead.
Скриптовые плагины парсятся как Groovy/Kotlin-скрипты, увеличивая потребление памяти из-за динамической компиляции.
Оптимизируйте с помощью pluginManagement для централизованного управления и минимизации дублирования.


Кэширование:
Плагины и их зависимости кэшируются в ~/.gradle/caches/modules-2, снижая сетевые запросы.
Очистка кэша:
rm -rf ~/.gradle/caches.


Производительность:
plugins {} быстрее, чем apply plugin:, за счет оптимизированного разрешения.
Параллельное выполнение задач (--parallel) ускоряет сборку, но увеличивает пиковое потребление памяти.
Используйте --configure-on-demand для сокращения времени конфигурации.


Отладка:
Используйте --info или --debug для анализа загрузки плагинов:
./gradlew build --debug


Проверьте список задач:
./gradlew tasks --all.


Build Scans (--scan) показывают влияние плагинов на сборку.

Совместимость:

Убедитесь, что плагины совместимы с версией Gradle (например, Android-плагины требуют Gradle 7.0+).
Используйте JAVA_HOME с JDK 8+ (рекомендуется 11+).


Безопасность:
Храните учетные данные для репозиториев в ~/.gradle/gradle.properties с ограниченными правами (chmod 600).
Проверяйте плагины из Gradle Plugin Portal на наличие GPG-подписей.


#Java #middle #Gradle #Task #Plugin
👍2
Обзор JSON Web Tokens (JWT) в Java

JSON Web Tokens (JWT) — это стандарт для создания компактных, самодостаточных токенов, используемых для безопасной передачи информации между сторонами в виде JSON-объекта. JWT широко применяется для аутентификации и авторизации в веб-приложениях, особенно в REST API. В Java экосистема библиотек, таких как jjwt и java-jwt, предоставляет мощные инструменты для работы с JWT.


Структура JWT

JWT состоит из трех основных частей, разделенных точками (.):
Header — содержит метаданные о токене, такие как тип (typ: JWT) и алгоритм подписи (например, alg: HS256 или RS256).
Payload — содержит полезные данные (claims), такие как идентификатор пользователя (sub), время выпуска (iat), срок действия (exp) и кастомные данные.
Signature — подпись, созданная с использованием секретного ключа или пары ключей (для асимметричных алгоритмов), для проверки целостности и подлинности токена.


Каждая часть кодируется в Base64Url и объединяется в строку вида: Header.Payload.Signature.


Пример JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c


Использование JWT в Java

Наиболее популярная библиотека для работы с JWT в Java — это io.jsonwebtoken:jjwt. Она поддерживает создание, парсинг и валидацию токенов с использованием различных алгоритмов подписи.

Установка зависимости

Добавьте зависимость в pom.xml для Maven:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.6</version>
</dependency>


Создание JWT

Пример создания JWT с использованием HMAC-SHA256:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;

public class JwtExample {
public static String createJwt(String subject, long ttlMillis) {
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); // Генерация ключа
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + ttlMillis))
.signWith(key)
.compact();
}
}


Парсинг и валидация JWT


Пример проверки и извлечения данных из токена:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;

public class JwtExample {
public static Claims parseJwt(String jwt, String secret) {
Key key = Keys.hmacShaKeyFor(secret.getBytes());
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(jwt)
.getBody();
}
}



Управление памятью и производительность

1. Размер токена и влияние на память

JWT компактны, но их размер зависит от содержимого payload и используемого алгоритма.

Например:
HMAC-SHA256 (симметричный) создает токены меньшего размера, так как используется один ключ.
RSA/ECDSA (асимметричные алгоритмы) увеличивают размер подписи, что может быть заметно при большом количестве токенов.
Payload с большим количеством claims (например, сложные JSON-объекты) увеличивает объем токена, что влияет на объем передаваемых данных и потребление памяти.


Рекомендации:
Минимизируйте количество claims в payload. Храните только необходимые данные, такие как sub, iat, exp.
Используйте сжатие (например, JWS Compression с DEF в jjwt) для уменьшения размера токена, если это допустимо.



#Java #middle #on_request #Jwt
👍4
2. Кэширование ключей

Создание и парсинг ключей (особенно для асимметричных алгоритмов, таких как RSA) — дорогостоящая операция с точки зрения CPU и памяти.

Например:
Генерация RSA-ключей требует значительных вычислительных ресурсов.
Повторное декодирование Base64-строк для ключей при каждом запросе увеличивает нагрузку.


Рекомендации:
Кэшируйте ключи в памяти (например, в ConcurrentHashMap или с использованием Spring Cache).
Используйте пул ключей для многопоточных приложений, чтобы избежать создания новых экземпляров Key для каждого запроса.


Пример кэширования ключа:

private static final Key SIGNING_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);

public static String createJwt(String subject, long ttlMillis) {
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + ttlMillis))
.signWith(SIGNING_KEY)
.compact();
}


3. Многопоточность

Библиотека jjwt потокобезопасна, но неправильное управление ключами или токенами может привести к проблемам.


Например:
Неправильное использование ThreadLocal для хранения временных ключей может привести к утечкам памяти.
Частое создание JwtParser без повторного использования увеличивает потребление ресурсов.


Рекомендации:
Создавайте и конфигурируйте JwtParserBuilder один раз и переиспользуйте его.
Используйте ThreadLocal только для временных данных, которые очищаются после обработки запроса.


Пример потокобезопасного парсера:
private static final JwtParser JWT_PARSER = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor("secret".getBytes()))
.build();

public static Claims parseJwt(String jwt) {
return JWT_PARSER.parseClaimsJws(jwt).getBody();
}



Нюансы безопасности

1. Выбор алгоритма подписи
HMAC-SHA (HS256, HS384, HS512): Быстрее, но требует безопасного хранения секретного ключа на всех серверах. Утечка ключа компрометирует всю систему.
RSA/ECDSA: Медленнее, но безопаснее, так как публичный ключ используется для проверки, а приватный хранится только на сервере, выдающем токены.
None-алгоритм: Никогда не используйте alg: none, так как это позволяет подделывать токены без подписи.


Рекомендации:
Для микросервисов с централизованным управлением ключами предпочтительнее RSA/ECDSA.

Используйте jjwt с настройкой require("alg"), чтобы предотвратить атаки с изменением алгоритма:
Jwts.parserBuilder()
.require("alg", "RS256")
.setSigningKey(publicKey)
.build();


2. Срок действия токена
Короткий срок действия (exp) снижает риск использования украденных токенов, но увеличивает нагрузку на сервер из-за частого обновления токенов (refresh tokens).

Рекомендации:
Устанавливайте exp в пределах 15-60 минут для access-токенов.
Используйте refresh-токены с более длинным сроком действия и строгим контролем (например, храните их в базе данных с возможностью отзыва).


3. Уязвимости
JWT Header Injection: Атакующий может изменить заголовок, чтобы подменить алгоритм (например, с RS256 на HS256). Всегда проверяйте алгоритм при парсинге.
Weak Keys: Слабые или предсказуемые ключи для HMAC-SHA делают токены уязвимыми для brute-force атак.
Payload Tampering: Если токен не подписан или подпись не проверяется, злоумышленник может изменить payload.


Рекомендации:
Используйте ключи достаточной длины (например, 256 бит для HS256).
Проверяйте подпись токена на каждом запросе.
Включайте jti (JWT ID) для отслеживания и отзыва токенов.



#Java #middle #on_request #Jwt
👍4
Оптимизация и масштабирование

1. Хранение и ротация ключей

Для HMAC-SHA ключи должны безопасно храниться (например, в Vault или AWS KMS).
Для RSA/ECDSA используйте ротацию ключей с поддержкой JWK (JSON Web Key) для автоматического обновления публичных ключей.


2. Масштабирование в микросервисах
Централизуйте выдачу токенов через отдельный сервис (например, Auth Service).
Используйте JWK для распространения публичных ключей между сервисами.


3. Кэширование токенов
Кэшируйте проверенные токены в Redis или другом in-memory хранилище, чтобы снизить нагрузку на парсинг и валидацию.
Используйте TTL кэша, соответствующий exp токена.


4. Логирование и мониторинг
Логируйте попытки использования невалидных или истекших токенов для анализа атак.
Мониторьте время парсинга и валидации токенов, чтобы выявить узкие места.



Пример интеграции с Spring Security

JWT часто используется в связке с Spring Security для защиты REST API.

Пример конфигурации:

import io.jsonwebtoken.Jwts;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}

public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor("secret".getBytes()))
.build()
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
if (username != null) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
username, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
SecurityContextHolder.clearContext();
}
}
chain.doFilter(request, response);
}
}


#Java #middle #on_request #Jwt
👍4