@ngxtm/devkit 3.4.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/rules/README.md +141 -0
- package/rules/dart/best-practices/SKILL.md +23 -0
- package/rules/dart/language/SKILL.md +52 -0
- package/rules/dart/tooling/SKILL.md +43 -0
- package/rules/dotnet/aspnet-core/SKILL.md +92 -0
- package/rules/dotnet/aspnet-core/references/REFERENCE.md +335 -0
- package/rules/dotnet/best-practices/SKILL.md +101 -0
- package/rules/dotnet/best-practices/references/REFERENCE.md +256 -0
- package/rules/dotnet/blazor/SKILL.md +146 -0
- package/rules/dotnet/blazor/references/REFERENCE.md +392 -0
- package/rules/dotnet/language/SKILL.md +82 -0
- package/rules/dotnet/language/references/REFERENCE.md +222 -0
- package/rules/dotnet/patterns.rule.md +388 -0
- package/rules/dotnet/razor-pages/SKILL.md +124 -0
- package/rules/dotnet/razor-pages/references/REFERENCE.md +321 -0
- package/rules/dotnet/security/SKILL.md +89 -0
- package/rules/dotnet/security/references/REFERENCE.md +295 -0
- package/rules/dotnet/tooling/SKILL.md +92 -0
- package/rules/dotnet/tooling/references/REFERENCE.md +300 -0
- package/rules/flutter/auto-route-navigation/SKILL.md +43 -0
- package/rules/flutter/auto-route-navigation/references/REFERENCE.md +19 -0
- package/rules/flutter/auto-route-navigation/references/router-config.md +62 -0
- package/rules/flutter/bloc-state-management/SKILL.md +64 -0
- package/rules/flutter/bloc-state-management/references/REFERENCE.md +20 -0
- package/rules/flutter/bloc-state-management/references/auth-bloc-example.md +52 -0
- package/rules/flutter/bloc-state-management/references/equatable-usage.md +56 -0
- package/rules/flutter/bloc-state-management/references/property-based-state.md +68 -0
- package/rules/flutter/bloc.rule.md +76 -0
- package/rules/flutter/cicd/SKILL.md +48 -0
- package/rules/flutter/cicd/references/advanced-workflow.md +66 -0
- package/rules/flutter/cicd/references/fastlane.md +139 -0
- package/rules/flutter/cicd/references/github-actions.md +59 -0
- package/rules/flutter/dependency-injection/SKILL.md +42 -0
- package/rules/flutter/dependency-injection/references/REFERENCE.md +15 -0
- package/rules/flutter/dependency-injection/references/modules.md +37 -0
- package/rules/flutter/error-handling/SKILL.md +32 -0
- package/rules/flutter/error-handling/references/REFERENCE.md +19 -0
- package/rules/flutter/error-handling/references/error-mapping.md +31 -0
- package/rules/flutter/feature-based-clean-architecture/SKILL.md +46 -0
- package/rules/flutter/feature-based-clean-architecture/references/REFERENCE.md +14 -0
- package/rules/flutter/feature-based-clean-architecture/references/folder-structure.md +36 -0
- package/rules/flutter/getx-navigation/SKILL.md +70 -0
- package/rules/flutter/getx-navigation/references/app-pages.md +40 -0
- package/rules/flutter/getx-navigation/references/middleware-example.md +29 -0
- package/rules/flutter/getx-state-management/SKILL.md +76 -0
- package/rules/flutter/getx-state-management/references/binding-example.md +32 -0
- package/rules/flutter/getx-state-management/references/reactive-vs-simple.md +39 -0
- package/rules/flutter/go-router-navigation/SKILL.md +57 -0
- package/rules/flutter/idiomatic-flutter/SKILL.md +20 -0
- package/rules/flutter/layer-based-clean-architecture/SKILL.md +50 -0
- package/rules/flutter/layer-based-clean-architecture/references/REFERENCE.md +60 -0
- package/rules/flutter/layer-based-clean-architecture/references/repository-mapping.md +50 -0
- package/rules/flutter/localization/SKILL.md +50 -0
- package/rules/flutter/localization/references/REFERENCE.md +48 -0
- package/rules/flutter/localization/references/sheet-loader.md +33 -0
- package/rules/flutter/navigator-v1-navigation/SKILL.md +71 -0
- package/rules/flutter/navigator-v1-navigation/references/on-generate-route.md +48 -0
- package/rules/flutter/performance/SKILL.md +24 -0
- package/rules/flutter/retrofit-networking/SKILL.md +51 -0
- package/rules/flutter/retrofit-networking/references/REFERENCE.md +19 -0
- package/rules/flutter/retrofit-networking/references/token-refresh.md +40 -0
- package/rules/flutter/riverpod-state-management/SKILL.md +53 -0
- package/rules/flutter/riverpod-state-management/references/architecture.md +124 -0
- package/rules/flutter/riverpod-state-management/references/best-practices.md +89 -0
- package/rules/flutter/riverpod-state-management/references/testing.md +73 -0
- package/rules/flutter/riverpod.rule.md +78 -0
- package/rules/flutter/security/SKILL.md +33 -0
- package/rules/flutter/security/references/REFERENCE.md +15 -0
- package/rules/flutter/security/references/network-security.md +28 -0
- package/rules/flutter/testing/SKILL.md +44 -0
- package/rules/flutter/testing/references/REFERENCE.md +21 -0
- package/rules/flutter/testing/references/bloc-testing.md +38 -0
- package/rules/flutter/testing/references/integration-testing.md +128 -0
- package/rules/flutter/testing/references/robot-pattern.md +82 -0
- package/rules/flutter/testing/references/unit-testing.md +130 -0
- package/rules/flutter/testing/references/widget-testing.md +120 -0
- package/rules/flutter/widgets/SKILL.md +37 -0
- package/rules/golang/chi-router/SKILL.md +219 -0
- package/rules/golang/chi-router/references/REFERENCE.md +13 -0
- package/rules/golang/chi-router/references/routing-patterns.md +205 -0
- package/rules/golang/cobra-cli/SKILL.md +227 -0
- package/rules/golang/cobra-cli/references/REFERENCE.md +13 -0
- package/rules/golang/cobra-cli/references/command-patterns.md +224 -0
- package/rules/golang/core/SKILL.md +210 -0
- package/rules/golang/core/references/REFERENCE.md +14 -0
- package/rules/golang/core/references/concurrency-patterns.md +114 -0
- package/rules/golang/core/references/error-handling.md +87 -0
- package/rules/golang/echo-framework/SKILL.md +215 -0
- package/rules/golang/echo-framework/references/REFERENCE.md +14 -0
- package/rules/golang/echo-framework/references/middleware-patterns.md +141 -0
- package/rules/golang/echo-framework/references/routing-patterns.md +140 -0
- package/rules/golang/ent-orm/SKILL.md +239 -0
- package/rules/golang/ent-orm/references/REFERENCE.md +13 -0
- package/rules/golang/ent-orm/references/schema-patterns.md +255 -0
- package/rules/golang/fiber-framework/SKILL.md +196 -0
- package/rules/golang/fiber-framework/references/REFERENCE.md +13 -0
- package/rules/golang/fiber-framework/references/routing-patterns.md +191 -0
- package/rules/golang/gin-framework/SKILL.md +205 -0
- package/rules/golang/gin-framework/references/REFERENCE.md +14 -0
- package/rules/golang/gin-framework/references/middleware-patterns.md +119 -0
- package/rules/golang/gorm-orm/SKILL.md +196 -0
- package/rules/golang/gorm-orm/references/REFERENCE.md +14 -0
- package/rules/golang/gorm-orm/references/model-definitions.md +167 -0
- package/rules/golang/gorm-orm/references/query-patterns.md +161 -0
- package/rules/golang/grpc/SKILL.md +231 -0
- package/rules/golang/grpc/references/REFERENCE.md +13 -0
- package/rules/golang/grpc/references/service-patterns.md +276 -0
- package/rules/golang/testify/SKILL.md +239 -0
- package/rules/golang/testify/references/REFERENCE.md +13 -0
- package/rules/golang/testify/references/assert-patterns.md +170 -0
- package/rules/golang/validator/SKILL.md +234 -0
- package/rules/golang/validator/references/REFERENCE.md +13 -0
- package/rules/golang/validator/references/validation-tags.md +211 -0
- package/rules/golang/viper-config/SKILL.md +244 -0
- package/rules/golang/viper-config/references/REFERENCE.md +13 -0
- package/rules/golang/viper-config/references/config-loading.md +181 -0
- package/rules/golang/wire-di/SKILL.md +243 -0
- package/rules/golang/wire-di/references/REFERENCE.md +13 -0
- package/rules/golang/wire-di/references/provider-patterns.md +193 -0
- package/rules/golang/zap-logging/SKILL.md +203 -0
- package/rules/golang/zap-logging/references/REFERENCE.md +13 -0
- package/rules/golang/zap-logging/references/logger-config.md +165 -0
- package/rules/java/build-gradle/SKILL.md +92 -0
- package/rules/java/build-gradle/references/REFERENCE.md +14 -0
- package/rules/java/build-gradle/references/kotlin-dsl.md +118 -0
- package/rules/java/build-gradle/references/task-configuration.md +132 -0
- package/rules/java/build-maven/SKILL.md +86 -0
- package/rules/java/build-maven/references/REFERENCE.md +14 -0
- package/rules/java/build-maven/references/dependency-management.md +111 -0
- package/rules/java/build-maven/references/lifecycle-phases.md +114 -0
- package/rules/java/graalvm-native/SKILL.md +105 -0
- package/rules/java/graalvm-native/references/REFERENCE.md +12 -0
- package/rules/java/java-collections-streams/SKILL.md +148 -0
- package/rules/java/java-collections-streams/references/REFERENCE.md +15 -0
- package/rules/java/java-collections-streams/references/collectors-patterns.md +178 -0
- package/rules/java/java-collections-streams/references/stream-pipelines.md +165 -0
- package/rules/java/java-concurrency/SKILL.md +187 -0
- package/rules/java/java-concurrency/references/REFERENCE.md +17 -0
- package/rules/java/java-concurrency/references/completable-future.md +165 -0
- package/rules/java/java-concurrency/references/executor-patterns.md +176 -0
- package/rules/java/java-concurrency/references/virtual-threads.md +190 -0
- package/rules/java/java-core-language/SKILL.md +121 -0
- package/rules/java/java-core-language/references/REFERENCE.md +15 -0
- package/rules/java/java-core-language/references/jvm-memory-model.md +160 -0
- package/rules/java/java-core-language/references/modern-java-features.md +168 -0
- package/rules/java/java-project-structure/SKILL.md +195 -0
- package/rules/java/java-project-structure/references/REFERENCE.md +15 -0
- package/rules/java/java-project-structure/references/maven-project-layout.md +199 -0
- package/rules/java/java-project-structure/references/module-system.md +159 -0
- package/rules/java/micronaut-core/SKILL.md +99 -0
- package/rules/java/micronaut-core/references/REFERENCE.md +12 -0
- package/rules/java/micronaut-reactive/SKILL.md +68 -0
- package/rules/java/micronaut-reactive/references/REFERENCE.md +12 -0
- package/rules/java/quarkus-core/SKILL.md +85 -0
- package/rules/java/quarkus-core/references/REFERENCE.md +12 -0
- package/rules/java/quarkus-reactive/SKILL.md +67 -0
- package/rules/java/quarkus-reactive/references/REFERENCE.md +12 -0
- package/rules/java/spring-batch/SKILL.md +102 -0
- package/rules/java/spring-batch/references/REFERENCE.md +12 -0
- package/rules/java/spring-boot-architecture/SKILL.md +206 -0
- package/rules/java/spring-boot-architecture/references/REFERENCE.md +15 -0
- package/rules/java/spring-boot-architecture/references/auto-configuration.md +158 -0
- package/rules/java/spring-boot-architecture/references/configuration-properties.md +202 -0
- package/rules/java/spring-boot-web/SKILL.md +217 -0
- package/rules/java/spring-boot-web/references/REFERENCE.md +17 -0
- package/rules/java/spring-cloud/SKILL.md +109 -0
- package/rules/java/spring-cloud/references/REFERENCE.md +13 -0
- package/rules/java/spring-data-jpa/SKILL.md +241 -0
- package/rules/java/spring-data-jpa/references/REFERENCE.md +16 -0
- package/rules/java/spring-security/SKILL.md +161 -0
- package/rules/java/spring-security/references/REFERENCE.md +16 -0
- package/rules/java/spring-security/references/jwt-auth-flow.md +213 -0
- package/rules/java/testing-junit-mockito/SKILL.md +135 -0
- package/rules/java/testing-junit-mockito/references/REFERENCE.md +15 -0
- package/rules/java/testing-junit-mockito/references/junit5-patterns.md +159 -0
- package/rules/java/testing-junit-mockito/references/mockito-patterns.md +148 -0
- package/rules/java/testing-junit-mockito/references/spring-boot-testing.md +152 -0
- package/rules/javascript/best-practices/SKILL.md +64 -0
- package/rules/javascript/best-practices/references/REFERENCE.md +91 -0
- package/rules/javascript/language/SKILL.md +71 -0
- package/rules/javascript/language/references/REFERENCE.md +106 -0
- package/rules/javascript/tooling/SKILL.md +60 -0
- package/rules/javascript/tooling/references/REFERENCE.md +107 -0
- package/rules/metadata.json +54 -0
- package/rules/nestjs/api-standards/SKILL.md +47 -0
- package/rules/nestjs/api-standards/references/pagination-wrapper.md +87 -0
- package/rules/nestjs/architecture/SKILL.md +68 -0
- package/rules/nestjs/architecture/references/dynamic-module.md +53 -0
- package/rules/nestjs/caching/SKILL.md +51 -0
- package/rules/nestjs/caching/references/REFERENCE.md +13 -0
- package/rules/nestjs/caching/references/cache-patterns.md +183 -0
- package/rules/nestjs/configuration/SKILL.md +41 -0
- package/rules/nestjs/configuration/references/REFERENCE.md +13 -0
- package/rules/nestjs/configuration/references/config-patterns.md +184 -0
- package/rules/nestjs/controllers-services/SKILL.md +63 -0
- package/rules/nestjs/controllers-services/references/REFERENCE.md +14 -0
- package/rules/nestjs/controllers-services/references/controller-patterns.md +119 -0
- package/rules/nestjs/controllers-services/references/service-patterns.md +129 -0
- package/rules/nestjs/database/SKILL.md +102 -0
- package/rules/nestjs/database/references/REFERENCE.md +14 -0
- package/rules/nestjs/database/references/typeorm-patterns.md +156 -0
- package/rules/nestjs/deployment/SKILL.md +36 -0
- package/rules/nestjs/deployment/references/REFERENCE.md +13 -0
- package/rules/nestjs/deployment/references/deployment-patterns.md +140 -0
- package/rules/nestjs/documentation/SKILL.md +64 -0
- package/rules/nestjs/documentation/references/REFERENCE.md +13 -0
- package/rules/nestjs/documentation/references/swagger-patterns.md +139 -0
- package/rules/nestjs/error-handling/SKILL.md +55 -0
- package/rules/nestjs/error-handling/references/REFERENCE.md +13 -0
- package/rules/nestjs/error-handling/references/exception-filters.md +152 -0
- package/rules/nestjs/file-uploads/SKILL.md +35 -0
- package/rules/nestjs/file-uploads/references/REFERENCE.md +13 -0
- package/rules/nestjs/file-uploads/references/upload-patterns.md +125 -0
- package/rules/nestjs/observability/SKILL.md +39 -0
- package/rules/nestjs/observability/references/REFERENCE.md +13 -0
- package/rules/nestjs/observability/references/logging-metrics.md +175 -0
- package/rules/nestjs/performance/SKILL.md +60 -0
- package/rules/nestjs/performance/references/REFERENCE.md +13 -0
- package/rules/nestjs/performance/references/performance-patterns.md +107 -0
- package/rules/nestjs/real-time/SKILL.md +45 -0
- package/rules/nestjs/real-time/references/REFERENCE.md +13 -0
- package/rules/nestjs/real-time/references/websocket-patterns.md +121 -0
- package/rules/nestjs/scheduling/SKILL.md +39 -0
- package/rules/nestjs/scheduling/references/REFERENCE.md +13 -0
- package/rules/nestjs/scheduling/references/scheduling-patterns.md +137 -0
- package/rules/nestjs/search/SKILL.md +41 -0
- package/rules/nestjs/search/references/REFERENCE.md +13 -0
- package/rules/nestjs/search/references/search-patterns.md +137 -0
- package/rules/nestjs/security/SKILL.md +87 -0
- package/rules/nestjs/security/references/REFERENCE.md +14 -0
- package/rules/nestjs/security/references/authentication.md +151 -0
- package/rules/nestjs/testing/SKILL.md +40 -0
- package/rules/nestjs/testing/references/REFERENCE.md +14 -0
- package/rules/nestjs/testing/references/unit-testing.md +179 -0
- package/rules/nestjs/transport/SKILL.md +45 -0
- package/rules/nestjs/transport/references/REFERENCE.md +13 -0
- package/rules/nestjs/transport/references/microservices-patterns.md +170 -0
- package/rules/nextjs/app-router/SKILL.md +46 -0
- package/rules/nextjs/app-router/references/REFERENCE.md +14 -0
- package/rules/nextjs/app-router/references/routing-patterns.md +182 -0
- package/rules/nextjs/architecture/SKILL.md +44 -0
- package/rules/nextjs/architecture/references/fsd-structure.md +77 -0
- package/rules/nextjs/authentication/SKILL.md +29 -0
- package/rules/nextjs/authentication/references/auth-implementation.md +73 -0
- package/rules/nextjs/caching/SKILL.md +66 -0
- package/rules/nextjs/caching/references/REFERENCE.md +13 -0
- package/rules/nextjs/caching/references/cache-strategies.md +168 -0
- package/rules/nextjs/data-access-layer/SKILL.md +33 -0
- package/rules/nextjs/data-access-layer/references/patterns.md +66 -0
- package/rules/nextjs/data-fetching/SKILL.md +59 -0
- package/rules/nextjs/data-fetching/references/REFERENCE.md +13 -0
- package/rules/nextjs/data-fetching/references/fetch-patterns.md +160 -0
- package/rules/nextjs/internationalization/SKILL.md +105 -0
- package/rules/nextjs/internationalization/references/REFERENCE.md +13 -0
- package/rules/nextjs/internationalization/references/i18n-patterns.md +180 -0
- package/rules/nextjs/optimization/SKILL.md +64 -0
- package/rules/nextjs/optimization/references/REFERENCE.md +13 -0
- package/rules/nextjs/optimization/references/optimization-patterns.md +190 -0
- package/rules/nextjs/rendering/SKILL.md +91 -0
- package/rules/nextjs/rendering/references/REFERENCE.md +13 -0
- package/rules/nextjs/rendering/references/rendering-modes.md +163 -0
- package/rules/nextjs/server-actions/SKILL.md +46 -0
- package/rules/nextjs/server-actions/references/REFERENCE.md +13 -0
- package/rules/nextjs/server-actions/references/action-patterns.md +188 -0
- package/rules/nextjs/server-components/SKILL.md +52 -0
- package/rules/nextjs/server-components/references/REFERENCE.md +13 -0
- package/rules/nextjs/server-components/references/component-patterns.md +175 -0
- package/rules/nextjs/state-management/SKILL.md +73 -0
- package/rules/nextjs/state-management/references/REFERENCE.md +13 -0
- package/rules/nextjs/state-management/references/state-patterns.md +218 -0
- package/rules/nextjs/styling/SKILL.md +31 -0
- package/rules/nextjs/styling/references/implementation.md +56 -0
- package/rules/react/component-patterns/SKILL.md +66 -0
- package/rules/react/component-patterns/references/REFERENCE.md +126 -0
- package/rules/react/hooks/SKILL.md +60 -0
- package/rules/react/hooks/references/REFERENCE.md +132 -0
- package/rules/react/hooks.rule.md +79 -0
- package/rules/react/performance/SKILL.md +69 -0
- package/rules/react/performance/references/REFERENCE.md +143 -0
- package/rules/react/security/SKILL.md +46 -0
- package/rules/react/security/references/REFERENCE.md +170 -0
- package/rules/react/state-management/SKILL.md +56 -0
- package/rules/react/state-management/references/REFERENCE.md +137 -0
- package/rules/react/testing/SKILL.md +45 -0
- package/rules/react/testing/references/REFERENCE.md +149 -0
- package/rules/react/tooling/SKILL.md +39 -0
- package/rules/react/typescript/SKILL.md +53 -0
- package/rules/rust/actix-web/SKILL.md +160 -0
- package/rules/rust/actix-web/references/REFERENCE.md +13 -0
- package/rules/rust/actix-web/references/handler-patterns.md +198 -0
- package/rules/rust/async-graphql/SKILL.md +228 -0
- package/rules/rust/async-graphql/references/REFERENCE.md +13 -0
- package/rules/rust/async-graphql/references/schema-patterns.md +215 -0
- package/rules/rust/axum/SKILL.md +161 -0
- package/rules/rust/axum/references/REFERENCE.md +14 -0
- package/rules/rust/axum/references/handler-patterns.md +97 -0
- package/rules/rust/bevy/SKILL.md +206 -0
- package/rules/rust/bevy/references/REFERENCE.md +13 -0
- package/rules/rust/bevy/references/ecs-patterns.md +226 -0
- package/rules/rust/clap/SKILL.md +217 -0
- package/rules/rust/clap/references/REFERENCE.md +13 -0
- package/rules/rust/clap/references/derive-patterns.md +205 -0
- package/rules/rust/core/SKILL.md +154 -0
- package/rules/rust/core/references/REFERENCE.md +14 -0
- package/rules/rust/core/references/error-handling.md +92 -0
- package/rules/rust/diesel-orm/SKILL.md +176 -0
- package/rules/rust/diesel-orm/references/REFERENCE.md +13 -0
- package/rules/rust/diesel-orm/references/schema-patterns.md +206 -0
- package/rules/rust/rocket/SKILL.md +182 -0
- package/rules/rust/rocket/references/REFERENCE.md +13 -0
- package/rules/rust/rocket/references/handler-patterns.md +209 -0
- package/rules/rust/sea-orm/SKILL.md +230 -0
- package/rules/rust/sea-orm/references/REFERENCE.md +13 -0
- package/rules/rust/sea-orm/references/entity-patterns.md +221 -0
- package/rules/rust/serde-serialization/SKILL.md +150 -0
- package/rules/rust/serde-serialization/references/REFERENCE.md +13 -0
- package/rules/rust/serde-serialization/references/serialization-patterns.md +199 -0
- package/rules/rust/sqlx-database/SKILL.md +140 -0
- package/rules/rust/sqlx-database/references/REFERENCE.md +13 -0
- package/rules/rust/sqlx-database/references/query-patterns.md +210 -0
- package/rules/rust/tauri/SKILL.md +180 -0
- package/rules/rust/tauri/references/REFERENCE.md +13 -0
- package/rules/rust/tauri/references/command-patterns.md +209 -0
- package/rules/rust/tokio-runtime/SKILL.md +167 -0
- package/rules/rust/tokio-runtime/references/REFERENCE.md +14 -0
- package/rules/rust/tokio-runtime/references/async-patterns.md +137 -0
- package/rules/rust/tokio-runtime/references/synchronization.md +152 -0
- package/rules/rust/tonic/SKILL.md +231 -0
- package/rules/rust/tonic/references/REFERENCE.md +13 -0
- package/rules/rust/tonic/references/service-patterns.md +213 -0
- package/rules/rust/tracing/SKILL.md +214 -0
- package/rules/rust/tracing/references/REFERENCE.md +13 -0
- package/rules/rust/tracing/references/instrumentation.md +187 -0
- package/rules/typescript/best-practices/SKILL.md +108 -0
- package/rules/typescript/best-practices/references/REFERENCE.md +68 -0
- package/rules/typescript/language/SKILL.md +72 -0
- package/rules/typescript/language/references/REFERENCE.md +67 -0
- package/rules/typescript/patterns.rule.md +85 -0
- package/rules/typescript/security/SKILL.md +59 -0
- package/rules/typescript/security/references/REFERENCE.md +113 -0
- package/rules/typescript/tooling/SKILL.md +52 -0
- package/rules/typescript/tooling/references/REFERENCE.md +110 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Spring Data JPA
|
|
3
|
+
description: Entity mapping, repositories, transactions, queries, auditing, and fetching strategies.
|
|
4
|
+
metadata:
|
|
5
|
+
labels: [java, spring-boot, jpa, database, hibernate]
|
|
6
|
+
triggers:
|
|
7
|
+
files: ['**/*Entity.java', '**/*Repository.java', '**/*Jpa*.java']
|
|
8
|
+
keywords: [Entity, Id, Repository, JpaRepository, Transactional, Query, ManyToOne, OneToMany, ManyToMany, JoinColumn]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Spring Data JPA Standards
|
|
12
|
+
|
|
13
|
+
## Entity Definition
|
|
14
|
+
|
|
15
|
+
```java
|
|
16
|
+
@Entity
|
|
17
|
+
@Table(name = "users")
|
|
18
|
+
@Getter @Setter
|
|
19
|
+
@NoArgsConstructor
|
|
20
|
+
public class User {
|
|
21
|
+
|
|
22
|
+
@Id
|
|
23
|
+
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
24
|
+
private Long id;
|
|
25
|
+
|
|
26
|
+
@Column(nullable = false, length = 100)
|
|
27
|
+
private String name;
|
|
28
|
+
|
|
29
|
+
@Column(nullable = false, unique = true)
|
|
30
|
+
private String email;
|
|
31
|
+
|
|
32
|
+
@Column(nullable = false)
|
|
33
|
+
private String passwordHash;
|
|
34
|
+
|
|
35
|
+
@Enumerated(EnumType.STRING)
|
|
36
|
+
@Column(nullable = false)
|
|
37
|
+
private UserStatus status = UserStatus.ACTIVE;
|
|
38
|
+
|
|
39
|
+
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
|
|
40
|
+
private List<Order> orders = new ArrayList<>();
|
|
41
|
+
|
|
42
|
+
@CreatedDate
|
|
43
|
+
@Column(nullable = false, updatable = false)
|
|
44
|
+
private LocalDateTime createdAt;
|
|
45
|
+
|
|
46
|
+
@LastModifiedDate
|
|
47
|
+
private LocalDateTime updatedAt;
|
|
48
|
+
|
|
49
|
+
// Business methods
|
|
50
|
+
public void addOrder(Order order) {
|
|
51
|
+
orders.add(order);
|
|
52
|
+
order.setUser(this);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public void removeOrder(Order order) {
|
|
56
|
+
orders.remove(order);
|
|
57
|
+
order.setUser(null);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Repository
|
|
63
|
+
|
|
64
|
+
```java
|
|
65
|
+
public interface UserRepository extends JpaRepository<User, Long> {
|
|
66
|
+
|
|
67
|
+
// Derived query methods
|
|
68
|
+
Optional<User> findByEmail(String email);
|
|
69
|
+
|
|
70
|
+
List<User> findByStatus(UserStatus status);
|
|
71
|
+
|
|
72
|
+
boolean existsByEmail(String email);
|
|
73
|
+
|
|
74
|
+
// JPQL query
|
|
75
|
+
@Query("SELECT u FROM User u WHERE u.status = :status AND u.createdAt > :since")
|
|
76
|
+
List<User> findActiveUsersSince(
|
|
77
|
+
@Param("status") UserStatus status,
|
|
78
|
+
@Param("since") LocalDateTime since
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Native query
|
|
82
|
+
@Query(value = "SELECT * FROM users WHERE email LIKE %:domain", nativeQuery = true)
|
|
83
|
+
List<User> findByEmailDomain(@Param("domain") String domain);
|
|
84
|
+
|
|
85
|
+
// Modifying query
|
|
86
|
+
@Modifying
|
|
87
|
+
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
|
|
88
|
+
int updateStatus(@Param("id") Long id, @Param("status") UserStatus status);
|
|
89
|
+
|
|
90
|
+
// Projection
|
|
91
|
+
@Query("SELECT u.id as id, u.name as name, u.email as email FROM User u")
|
|
92
|
+
List<UserSummary> findAllSummaries();
|
|
93
|
+
|
|
94
|
+
// Pagination
|
|
95
|
+
Page<User> findByStatus(UserStatus status, Pageable pageable);
|
|
96
|
+
|
|
97
|
+
// Specification (dynamic queries)
|
|
98
|
+
List<User> findAll(Specification<User> spec);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Projection interface
|
|
102
|
+
public interface UserSummary {
|
|
103
|
+
Long getId();
|
|
104
|
+
String getName();
|
|
105
|
+
String getEmail();
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Transactions
|
|
110
|
+
|
|
111
|
+
```java
|
|
112
|
+
@Service
|
|
113
|
+
@RequiredArgsConstructor
|
|
114
|
+
public class OrderService {
|
|
115
|
+
|
|
116
|
+
private final OrderRepository orderRepository;
|
|
117
|
+
private final InventoryService inventoryService;
|
|
118
|
+
|
|
119
|
+
@Transactional
|
|
120
|
+
public Order createOrder(CreateOrderRequest request) {
|
|
121
|
+
Order order = new Order();
|
|
122
|
+
// ... setup order
|
|
123
|
+
|
|
124
|
+
for (OrderItem item : request.items()) {
|
|
125
|
+
inventoryService.decreaseStock(item.productId(), item.quantity());
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return orderRepository.save(order);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@Transactional(readOnly = true)
|
|
132
|
+
public Order findById(Long id) {
|
|
133
|
+
return orderRepository.findById(id)
|
|
134
|
+
.orElseThrow(() -> new OrderNotFoundException(id));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
|
138
|
+
public void processPayment(Long orderId) {
|
|
139
|
+
// Runs in new transaction
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@Transactional(
|
|
143
|
+
isolation = Isolation.SERIALIZABLE,
|
|
144
|
+
timeout = 30,
|
|
145
|
+
rollbackFor = PaymentException.class,
|
|
146
|
+
noRollbackFor = NotificationException.class
|
|
147
|
+
)
|
|
148
|
+
public void processWithOptions(Long orderId) {
|
|
149
|
+
// Custom transaction settings
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Relationships
|
|
155
|
+
|
|
156
|
+
```java
|
|
157
|
+
// Many-to-One (owning side)
|
|
158
|
+
@Entity
|
|
159
|
+
public class Order {
|
|
160
|
+
@ManyToOne(fetch = FetchType.LAZY)
|
|
161
|
+
@JoinColumn(name = "user_id", nullable = false)
|
|
162
|
+
private User user;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// One-to-Many (inverse side)
|
|
166
|
+
@Entity
|
|
167
|
+
public class User {
|
|
168
|
+
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
|
|
169
|
+
private List<Order> orders = new ArrayList<>();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Many-to-Many
|
|
173
|
+
@Entity
|
|
174
|
+
public class Student {
|
|
175
|
+
@ManyToMany
|
|
176
|
+
@JoinTable(
|
|
177
|
+
name = "student_courses",
|
|
178
|
+
joinColumns = @JoinColumn(name = "student_id"),
|
|
179
|
+
inverseJoinColumns = @JoinColumn(name = "course_id")
|
|
180
|
+
)
|
|
181
|
+
private Set<Course> courses = new HashSet<>();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// One-to-One
|
|
185
|
+
@Entity
|
|
186
|
+
public class User {
|
|
187
|
+
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
|
|
188
|
+
private UserProfile profile;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Auditing
|
|
193
|
+
|
|
194
|
+
```java
|
|
195
|
+
@Configuration
|
|
196
|
+
@EnableJpaAuditing
|
|
197
|
+
public class JpaConfig {}
|
|
198
|
+
|
|
199
|
+
@MappedSuperclass
|
|
200
|
+
@EntityListeners(AuditingEntityListener.class)
|
|
201
|
+
public abstract class BaseEntity {
|
|
202
|
+
|
|
203
|
+
@CreatedDate
|
|
204
|
+
@Column(nullable = false, updatable = false)
|
|
205
|
+
private LocalDateTime createdAt;
|
|
206
|
+
|
|
207
|
+
@LastModifiedDate
|
|
208
|
+
private LocalDateTime updatedAt;
|
|
209
|
+
|
|
210
|
+
@CreatedBy
|
|
211
|
+
@Column(updatable = false)
|
|
212
|
+
private String createdBy;
|
|
213
|
+
|
|
214
|
+
@LastModifiedBy
|
|
215
|
+
private String updatedBy;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@Component
|
|
219
|
+
public class AuditorAwareImpl implements AuditorAware<String> {
|
|
220
|
+
@Override
|
|
221
|
+
public Optional<String> getCurrentAuditor() {
|
|
222
|
+
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
|
|
223
|
+
.map(Authentication::getName);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Best Practices
|
|
229
|
+
|
|
230
|
+
1. **Use @ManyToOne with LAZY** - avoid N+1 queries
|
|
231
|
+
2. **Fetch eagerly with JOIN FETCH** when needed
|
|
232
|
+
3. **Use @Transactional(readOnly = true)** for read operations
|
|
233
|
+
4. **Avoid @ManyToMany** - use explicit join table entity
|
|
234
|
+
5. **Use projections** for read-only queries
|
|
235
|
+
6. **Paginate large result sets**
|
|
236
|
+
|
|
237
|
+
## References
|
|
238
|
+
|
|
239
|
+
- [Entity Mapping](references/entity-mapping.md) - Inheritance, embeddables, converters
|
|
240
|
+
- [Repository Patterns](references/repository-patterns.md) - Specifications, custom repos
|
|
241
|
+
- [Fetching Strategies](references/fetching-strategies.md) - N+1, EntityGraph, batch
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Spring Data JPA References
|
|
2
|
+
|
|
3
|
+
## References
|
|
4
|
+
|
|
5
|
+
- [**Entity Mapping**](entity-mapping.md) - Inheritance strategies, embeddables, custom converters
|
|
6
|
+
- [**Repository Patterns**](repository-patterns.md) - Specifications, custom implementations
|
|
7
|
+
- [**Fetching Strategies**](fetching-strategies.md) - N+1 problem, EntityGraph, batch fetching
|
|
8
|
+
|
|
9
|
+
## Quick Checks
|
|
10
|
+
|
|
11
|
+
- [ ] Use LAZY fetch for @ManyToOne and @OneToMany
|
|
12
|
+
- [ ] JOIN FETCH for eager loading in queries
|
|
13
|
+
- [ ] @Transactional(readOnly = true) for reads
|
|
14
|
+
- [ ] Projections for read-only partial data
|
|
15
|
+
- [ ] Paginate large result sets
|
|
16
|
+
- [ ] Avoid open-in-view (disabled by default in Spring Boot 3)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Spring Security
|
|
3
|
+
description: Authentication, authorization, JWT, OAuth2, CSRF, CORS, and security filter chain.
|
|
4
|
+
metadata:
|
|
5
|
+
labels: [java, spring-boot, security, authentication, authorization]
|
|
6
|
+
triggers:
|
|
7
|
+
files: ['**/*Security*.java', '**/*Config*.java', '**/*Auth*.java']
|
|
8
|
+
keywords: [SecurityFilterChain, HttpSecurity, Authentication, Authorization, csrf, jwt, OAuth2, UserDetails, PasswordEncoder]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Spring Security Standards
|
|
12
|
+
|
|
13
|
+
## Security Configuration (Spring Boot 3+)
|
|
14
|
+
|
|
15
|
+
```java
|
|
16
|
+
@Configuration
|
|
17
|
+
@EnableWebSecurity
|
|
18
|
+
@EnableMethodSecurity
|
|
19
|
+
public class SecurityConfig {
|
|
20
|
+
|
|
21
|
+
@Bean
|
|
22
|
+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
23
|
+
return http
|
|
24
|
+
.csrf(csrf -> csrf.disable()) // Disable for stateless API
|
|
25
|
+
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
|
26
|
+
.sessionManagement(session ->
|
|
27
|
+
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
28
|
+
.authorizeHttpRequests(auth -> auth
|
|
29
|
+
.requestMatchers("/api/auth/**").permitAll()
|
|
30
|
+
.requestMatchers("/api/public/**").permitAll()
|
|
31
|
+
.requestMatchers("/actuator/health").permitAll()
|
|
32
|
+
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
|
33
|
+
.anyRequest().authenticated()
|
|
34
|
+
)
|
|
35
|
+
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
|
|
36
|
+
.exceptionHandling(ex -> ex
|
|
37
|
+
.authenticationEntryPoint(authEntryPoint)
|
|
38
|
+
.accessDeniedHandler(accessDeniedHandler)
|
|
39
|
+
)
|
|
40
|
+
.build();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Bean
|
|
44
|
+
public PasswordEncoder passwordEncoder() {
|
|
45
|
+
return new BCryptPasswordEncoder();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Bean
|
|
49
|
+
public AuthenticationManager authenticationManager(
|
|
50
|
+
AuthenticationConfiguration config) throws Exception {
|
|
51
|
+
return config.getAuthenticationManager();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## JWT Authentication Filter
|
|
57
|
+
|
|
58
|
+
```java
|
|
59
|
+
@Component
|
|
60
|
+
@RequiredArgsConstructor
|
|
61
|
+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|
62
|
+
|
|
63
|
+
private final JwtService jwtService;
|
|
64
|
+
private final UserDetailsService userDetailsService;
|
|
65
|
+
|
|
66
|
+
@Override
|
|
67
|
+
protected void doFilterInternal(
|
|
68
|
+
HttpServletRequest request,
|
|
69
|
+
HttpServletResponse response,
|
|
70
|
+
FilterChain filterChain) throws ServletException, IOException {
|
|
71
|
+
|
|
72
|
+
String authHeader = request.getHeader("Authorization");
|
|
73
|
+
|
|
74
|
+
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
|
75
|
+
filterChain.doFilter(request, response);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
String jwt = authHeader.substring(7);
|
|
80
|
+
String username = jwtService.extractUsername(jwt);
|
|
81
|
+
|
|
82
|
+
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
|
83
|
+
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
|
84
|
+
|
|
85
|
+
if (jwtService.isTokenValid(jwt, userDetails)) {
|
|
86
|
+
UsernamePasswordAuthenticationToken authToken =
|
|
87
|
+
new UsernamePasswordAuthenticationToken(
|
|
88
|
+
userDetails,
|
|
89
|
+
null,
|
|
90
|
+
userDetails.getAuthorities()
|
|
91
|
+
);
|
|
92
|
+
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
|
93
|
+
SecurityContextHolder.getContext().setAuthentication(authToken);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
filterChain.doFilter(request, response);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Method Security
|
|
103
|
+
|
|
104
|
+
```java
|
|
105
|
+
@Service
|
|
106
|
+
public class UserService {
|
|
107
|
+
|
|
108
|
+
@PreAuthorize("hasRole('ADMIN')")
|
|
109
|
+
public void deleteUser(Long id) { }
|
|
110
|
+
|
|
111
|
+
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
|
|
112
|
+
public User updateUser(Long id, UpdateRequest request) { }
|
|
113
|
+
|
|
114
|
+
@PostAuthorize("returnObject.owner == authentication.name")
|
|
115
|
+
public Resource getResource(Long id) { }
|
|
116
|
+
|
|
117
|
+
@PreAuthorize("@securityService.canAccess(#id)")
|
|
118
|
+
public void accessResource(Long id) { }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@Component("securityService")
|
|
122
|
+
public class SecurityService {
|
|
123
|
+
public boolean canAccess(Long resourceId) {
|
|
124
|
+
// Custom authorization logic
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## CORS Configuration
|
|
131
|
+
|
|
132
|
+
```java
|
|
133
|
+
@Bean
|
|
134
|
+
public CorsConfigurationSource corsConfigurationSource() {
|
|
135
|
+
CorsConfiguration config = new CorsConfiguration();
|
|
136
|
+
config.setAllowedOrigins(List.of("http://localhost:3000", "https://example.com"));
|
|
137
|
+
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
|
138
|
+
config.setAllowedHeaders(List.of("*"));
|
|
139
|
+
config.setAllowCredentials(true);
|
|
140
|
+
config.setMaxAge(3600L);
|
|
141
|
+
|
|
142
|
+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|
143
|
+
source.registerCorsConfiguration("/api/**", config);
|
|
144
|
+
return source;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Best Practices
|
|
149
|
+
|
|
150
|
+
1. **Use BCrypt** for password hashing (strength 10-12)
|
|
151
|
+
2. **Stateless sessions** for REST APIs with JWT
|
|
152
|
+
3. **Method security** for fine-grained authorization
|
|
153
|
+
4. **Validate JWT** on every request
|
|
154
|
+
5. **Short token expiry** with refresh token pattern
|
|
155
|
+
6. **Never log sensitive data** (passwords, tokens)
|
|
156
|
+
|
|
157
|
+
## References
|
|
158
|
+
|
|
159
|
+
- [JWT Auth Flow](references/jwt-auth-flow.md) - Token service, refresh tokens
|
|
160
|
+
- [OAuth2 Resource Server](references/oauth2-resource-server.md) - JWT decoder, claims
|
|
161
|
+
- [Security Filter Chain](references/security-filter-chain.md) - Filter order, custom filters
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Spring Security References
|
|
2
|
+
|
|
3
|
+
## References
|
|
4
|
+
|
|
5
|
+
- [**JWT Auth Flow**](jwt-auth-flow.md) - Token generation, validation, refresh pattern
|
|
6
|
+
- [**OAuth2 Resource Server**](oauth2-resource-server.md) - JWT decoder, extracting claims
|
|
7
|
+
- [**Security Filter Chain**](security-filter-chain.md) - Filter ordering, custom filters
|
|
8
|
+
|
|
9
|
+
## Quick Checks
|
|
10
|
+
|
|
11
|
+
- [ ] BCrypt password encoder (cost 10-12)
|
|
12
|
+
- [ ] Stateless session for REST APIs
|
|
13
|
+
- [ ] JWT validation on every request
|
|
14
|
+
- [ ] CORS configured for allowed origins only
|
|
15
|
+
- [ ] CSRF disabled only for stateless APIs
|
|
16
|
+
- [ ] Method security for fine-grained authorization
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# JWT Authentication Flow
|
|
2
|
+
|
|
3
|
+
## JWT Service
|
|
4
|
+
|
|
5
|
+
```java
|
|
6
|
+
@Service
|
|
7
|
+
@RequiredArgsConstructor
|
|
8
|
+
public class JwtService {
|
|
9
|
+
|
|
10
|
+
@Value("${jwt.secret}")
|
|
11
|
+
private String secretKey;
|
|
12
|
+
|
|
13
|
+
@Value("${jwt.access-token-expiration}")
|
|
14
|
+
private Duration accessTokenExpiration;
|
|
15
|
+
|
|
16
|
+
@Value("${jwt.refresh-token-expiration}")
|
|
17
|
+
private Duration refreshTokenExpiration;
|
|
18
|
+
|
|
19
|
+
public String generateAccessToken(UserDetails userDetails) {
|
|
20
|
+
return generateToken(userDetails, accessTokenExpiration, Map.of());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public String generateRefreshToken(UserDetails userDetails) {
|
|
24
|
+
return generateToken(userDetails, refreshTokenExpiration, Map.of("type", "refresh"));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private String generateToken(
|
|
28
|
+
UserDetails userDetails,
|
|
29
|
+
Duration expiration,
|
|
30
|
+
Map<String, Object> extraClaims) {
|
|
31
|
+
|
|
32
|
+
Instant now = Instant.now();
|
|
33
|
+
return Jwts.builder()
|
|
34
|
+
.claims(extraClaims)
|
|
35
|
+
.subject(userDetails.getUsername())
|
|
36
|
+
.issuedAt(Date.from(now))
|
|
37
|
+
.expiration(Date.from(now.plus(expiration)))
|
|
38
|
+
.signWith(getSigningKey())
|
|
39
|
+
.compact();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public String extractUsername(String token) {
|
|
43
|
+
return extractClaim(token, Claims::getSubject);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
|
47
|
+
Claims claims = extractAllClaims(token);
|
|
48
|
+
return claimsResolver.apply(claims);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private Claims extractAllClaims(String token) {
|
|
52
|
+
return Jwts.parser()
|
|
53
|
+
.verifyWith(getSigningKey())
|
|
54
|
+
.build()
|
|
55
|
+
.parseSignedClaims(token)
|
|
56
|
+
.getPayload();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public boolean isTokenValid(String token, UserDetails userDetails) {
|
|
60
|
+
String username = extractUsername(token);
|
|
61
|
+
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private boolean isTokenExpired(String token) {
|
|
65
|
+
return extractClaim(token, Claims::getExpiration).before(new Date());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private SecretKey getSigningKey() {
|
|
69
|
+
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
|
|
70
|
+
return Keys.hmacShaKeyFor(keyBytes);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Authentication Controller
|
|
76
|
+
|
|
77
|
+
```java
|
|
78
|
+
@RestController
|
|
79
|
+
@RequestMapping("/api/auth")
|
|
80
|
+
@RequiredArgsConstructor
|
|
81
|
+
public class AuthController {
|
|
82
|
+
|
|
83
|
+
private final AuthenticationManager authManager;
|
|
84
|
+
private final JwtService jwtService;
|
|
85
|
+
private final UserDetailsService userDetailsService;
|
|
86
|
+
private final RefreshTokenService refreshTokenService;
|
|
87
|
+
|
|
88
|
+
@PostMapping("/login")
|
|
89
|
+
public AuthResponse login(@Valid @RequestBody LoginRequest request) {
|
|
90
|
+
authManager.authenticate(
|
|
91
|
+
new UsernamePasswordAuthenticationToken(
|
|
92
|
+
request.email(),
|
|
93
|
+
request.password()
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
UserDetails userDetails = userDetailsService.loadUserByUsername(request.email());
|
|
98
|
+
String accessToken = jwtService.generateAccessToken(userDetails);
|
|
99
|
+
String refreshToken = jwtService.generateRefreshToken(userDetails);
|
|
100
|
+
|
|
101
|
+
refreshTokenService.save(request.email(), refreshToken);
|
|
102
|
+
|
|
103
|
+
return new AuthResponse(accessToken, refreshToken);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@PostMapping("/refresh")
|
|
107
|
+
public AuthResponse refresh(@Valid @RequestBody RefreshRequest request) {
|
|
108
|
+
String refreshToken = request.refreshToken();
|
|
109
|
+
|
|
110
|
+
if (!refreshTokenService.isValid(refreshToken)) {
|
|
111
|
+
throw new InvalidTokenException("Invalid refresh token");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
String username = jwtService.extractUsername(refreshToken);
|
|
115
|
+
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
|
116
|
+
|
|
117
|
+
String newAccessToken = jwtService.generateAccessToken(userDetails);
|
|
118
|
+
String newRefreshToken = jwtService.generateRefreshToken(userDetails);
|
|
119
|
+
|
|
120
|
+
refreshTokenService.revoke(refreshToken);
|
|
121
|
+
refreshTokenService.save(username, newRefreshToken);
|
|
122
|
+
|
|
123
|
+
return new AuthResponse(newAccessToken, newRefreshToken);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@PostMapping("/logout")
|
|
127
|
+
public void logout(@RequestHeader("Authorization") String authHeader) {
|
|
128
|
+
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
|
129
|
+
String token = authHeader.substring(7);
|
|
130
|
+
String username = jwtService.extractUsername(token);
|
|
131
|
+
refreshTokenService.revokeAllForUser(username);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public record LoginRequest(
|
|
137
|
+
@NotBlank @Email String email,
|
|
138
|
+
@NotBlank String password
|
|
139
|
+
) {}
|
|
140
|
+
|
|
141
|
+
public record RefreshRequest(
|
|
142
|
+
@NotBlank String refreshToken
|
|
143
|
+
) {}
|
|
144
|
+
|
|
145
|
+
public record AuthResponse(
|
|
146
|
+
String accessToken,
|
|
147
|
+
String refreshToken
|
|
148
|
+
) {}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Refresh Token Storage
|
|
152
|
+
|
|
153
|
+
```java
|
|
154
|
+
@Service
|
|
155
|
+
@RequiredArgsConstructor
|
|
156
|
+
public class RefreshTokenService {
|
|
157
|
+
|
|
158
|
+
private final RefreshTokenRepository repository;
|
|
159
|
+
private final JwtService jwtService;
|
|
160
|
+
|
|
161
|
+
public void save(String username, String token) {
|
|
162
|
+
RefreshToken refreshToken = new RefreshToken();
|
|
163
|
+
refreshToken.setUsername(username);
|
|
164
|
+
refreshToken.setToken(token);
|
|
165
|
+
refreshToken.setExpiresAt(
|
|
166
|
+
jwtService.extractClaim(token, Claims::getExpiration).toInstant()
|
|
167
|
+
);
|
|
168
|
+
repository.save(refreshToken);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public boolean isValid(String token) {
|
|
172
|
+
return repository.findByToken(token)
|
|
173
|
+
.map(rt -> rt.getExpiresAt().isAfter(Instant.now()))
|
|
174
|
+
.orElse(false);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public void revoke(String token) {
|
|
178
|
+
repository.deleteByToken(token);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public void revokeAllForUser(String username) {
|
|
182
|
+
repository.deleteAllByUsername(username);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@Entity
|
|
187
|
+
@Table(name = "refresh_tokens")
|
|
188
|
+
public class RefreshToken {
|
|
189
|
+
@Id
|
|
190
|
+
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
191
|
+
private Long id;
|
|
192
|
+
|
|
193
|
+
@Column(nullable = false)
|
|
194
|
+
private String username;
|
|
195
|
+
|
|
196
|
+
@Column(nullable = false, unique = true)
|
|
197
|
+
private String token;
|
|
198
|
+
|
|
199
|
+
@Column(nullable = false)
|
|
200
|
+
private Instant expiresAt;
|
|
201
|
+
|
|
202
|
+
// getters, setters
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Configuration
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
jwt:
|
|
210
|
+
secret: ${JWT_SECRET} # Base64 encoded, min 256 bits
|
|
211
|
+
access-token-expiration: 15m
|
|
212
|
+
refresh-token-expiration: 7d
|
|
213
|
+
```
|