@rashidee/co2 1.2.5
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/dist/index.js +25449 -0
- package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
- package/drizzle/0001_wise_wolf_cub.sql +13 -0
- package/drizzle/0002_secret_spyke.sql +16 -0
- package/drizzle/0003_workspace_config_singleton.sql +16 -0
- package/drizzle/0004_terminal_claim_v120.sql +11 -0
- package/drizzle/0005_brainstorm_v124.sql +10 -0
- package/drizzle/0006_project_v140.sql +6 -0
- package/drizzle/0007_prd_wizard_v124.sql +14 -0
- package/drizzle/0008_workflow_artifact_v126.sql +24 -0
- package/drizzle/0009_project_import_v150.sql +7 -0
- package/drizzle/0010_quality_v151.sql +3 -0
- package/drizzle/meta/0000_snapshot.json +1335 -0
- package/drizzle/meta/0001_snapshot.json +1416 -0
- package/drizzle/meta/0002_snapshot.json +1510 -0
- package/drizzle/meta/_journal.json +83 -0
- package/package.json +41 -0
- package/plugin/.claude-plugin/marketplace.json +36 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/README.md +625 -0
- package/plugin/SKILLS.md +350 -0
- package/plugin/co2.svg +5 -0
- package/plugin/commands/brainstorm-loop.md +129 -0
- package/plugin/skills/conductor-defect/SKILL.md +898 -0
- package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
- package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
- package/plugin/skills/depgen-k8s/SKILL.md +468 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
- package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
- package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
- package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
- package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
- package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
- package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
- package/plugin/skills/modelgen-relational/SKILL.md +707 -0
- package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
- package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
- package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
- package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
- package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
- package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
- package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
- package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
- package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
- package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
- package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
- package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
- package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
- package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
- package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
- package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
- package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
- package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
- package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
- package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
- package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
- package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
- package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
- package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
- package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
- package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
- package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
- package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
- package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
- package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
- package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
- package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
- package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
- package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
- package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
- package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
- package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
- package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
- package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
- package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
- package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
- package/plugin/skills/testgen-functional/SKILL.md +1381 -0
- package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
- package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
- package/plugin/skills/util-projectinit/SKILL.md +307 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
- package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
- package/plugin/skills/util-projectsync/SKILL.md +501 -0
- package/plugin/skills/util-updprd/SKILL.md +324 -0
- package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
- package/plugin/skills/util-ustagger/SKILL.md +154 -0
- package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
- package/static/assets/arc-DRKw7sjs.js +1 -0
- package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
- package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
- package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
- package/static/assets/channel-C2nVbuwv.js +1 -0
- package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
- package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
- package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
- package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
- package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
- package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
- package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
- package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
- package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
- package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
- package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
- package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
- package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
- package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
- package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
- package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
- package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
- package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
- package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
- package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
- package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
- package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
- package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
- package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
- package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
- package/static/assets/graph-C9eacEi8.js +1 -0
- package/static/assets/index-LZSQkLE2.css +41 -0
- package/static/assets/index-y7XDSgMG.js +495 -0
- package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
- package/static/assets/init-Gi6I4Gst.js +1 -0
- package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
- package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
- package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
- package/static/assets/katex-C5jXJg4s.js +257 -0
- package/static/assets/layout-DEXfKzaS.js +1 -0
- package/static/assets/linear-CLO--ryk.js +1 -0
- package/static/assets/map-Czzmt4hB.js +1 -0
- package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
- package/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
- package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
- package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
- package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
- package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
- package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
- package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
- package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
- package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
- package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
- package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
- package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
- package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
- package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
- package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
- package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
- package/static/index.html +24 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# Security Patterns — Keycloak OAuth2 Resource Server with Spring Security
|
|
2
|
+
|
|
3
|
+
This reference describes the complete security architecture for the REST API spec. Include
|
|
4
|
+
all of this content in Section 6 of the generated specification.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Architecture Overview
|
|
9
|
+
|
|
10
|
+
The application operates as an OAuth2 Resource Server that validates JWT tokens issued by
|
|
11
|
+
Keycloak. Unlike the OAuth2 Client pattern (used in web apps with server-rendered views),
|
|
12
|
+
the Resource Server pattern is stateless — no HTTP sessions, no login pages, no cookies.
|
|
13
|
+
|
|
14
|
+
The client (e.g., a frontend SPA, mobile app, or another service) obtains a JWT access
|
|
15
|
+
token from Keycloak directly and includes it in the `Authorization: Bearer` header of
|
|
16
|
+
every API request. The REST API validates this token and extracts roles for access control.
|
|
17
|
+
|
|
18
|
+
The flow:
|
|
19
|
+
```
|
|
20
|
+
Client → POST /realms/{realm}/protocol/openid-connect/token (to Keycloak)
|
|
21
|
+
→ Keycloak returns JWT access token
|
|
22
|
+
Client → GET /api/v1/resource (with Authorization: Bearer <token>)
|
|
23
|
+
→ Spring Security validates JWT signature via Keycloak's JWKS endpoint
|
|
24
|
+
→ JwtAuthenticationConverter extracts roles from JWT claims
|
|
25
|
+
→ Request proceeds to @RestController
|
|
26
|
+
→ JSON response
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Configuration in application.yml
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
spring:
|
|
33
|
+
security:
|
|
34
|
+
oauth2:
|
|
35
|
+
resourceserver:
|
|
36
|
+
jwt:
|
|
37
|
+
issuer-uri: {{KEYCLOAK_ISSUER_URI}}
|
|
38
|
+
jwk-set-uri: {{KEYCLOAK_ISSUER_URI}}/protocol/openid-connect/certs
|
|
39
|
+
|
|
40
|
+
app:
|
|
41
|
+
security:
|
|
42
|
+
keycloak-client-id: {{KEYCLOAK_CLIENT_ID}}
|
|
43
|
+
public-paths:
|
|
44
|
+
- /api-docs/**
|
|
45
|
+
- /swagger-ui/**
|
|
46
|
+
- /swagger-ui.html
|
|
47
|
+
- /actuator/health
|
|
48
|
+
- /error
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## SecurityConfig.java
|
|
54
|
+
|
|
55
|
+
Complete sample code. The application uses OAuth2 Resource Server with JWT validation.
|
|
56
|
+
Stateless — no sessions. Keycloak provides the JWT tokens.
|
|
57
|
+
|
|
58
|
+
```java
|
|
59
|
+
package {{BASE_PACKAGE}}.config;
|
|
60
|
+
|
|
61
|
+
import lombok.RequiredArgsConstructor;
|
|
62
|
+
import org.springframework.context.annotation.Bean;
|
|
63
|
+
import org.springframework.context.annotation.Configuration;
|
|
64
|
+
import org.springframework.http.HttpMethod;
|
|
65
|
+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
|
66
|
+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
67
|
+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
68
|
+
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
69
|
+
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
|
70
|
+
import org.springframework.security.web.SecurityFilterChain;
|
|
71
|
+
import org.springframework.web.cors.CorsConfigurationSource;
|
|
72
|
+
|
|
73
|
+
@Configuration
|
|
74
|
+
@EnableWebSecurity
|
|
75
|
+
@EnableMethodSecurity
|
|
76
|
+
@RequiredArgsConstructor
|
|
77
|
+
public class SecurityConfig {
|
|
78
|
+
|
|
79
|
+
private final CorsConfigurationSource corsConfigurationSource;
|
|
80
|
+
|
|
81
|
+
@Bean
|
|
82
|
+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
83
|
+
http
|
|
84
|
+
.cors(cors -> cors.configurationSource(corsConfigurationSource))
|
|
85
|
+
.csrf(csrf -> csrf.disable())
|
|
86
|
+
.sessionManagement(session ->
|
|
87
|
+
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
88
|
+
.authorizeHttpRequests(auth -> auth
|
|
89
|
+
.requestMatchers("/api-docs/**", "/swagger-ui/**",
|
|
90
|
+
"/swagger-ui.html", "/actuator/health", "/error").permitAll()
|
|
91
|
+
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
|
92
|
+
.anyRequest().authenticated()
|
|
93
|
+
)
|
|
94
|
+
.oauth2ResourceServer(oauth2 -> oauth2
|
|
95
|
+
.jwt(jwt -> jwt
|
|
96
|
+
.jwtAuthenticationConverter(jwtAuthenticationConverter())
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
return http.build();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Bean
|
|
103
|
+
public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
|
104
|
+
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
|
|
105
|
+
converter.setJwtGrantedAuthoritiesConverter(new KeycloakJwtGrantedAuthoritiesConverter());
|
|
106
|
+
return converter;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## KeycloakJwtGrantedAuthoritiesConverter.java
|
|
114
|
+
|
|
115
|
+
This class bridges Keycloak's JWT claim structure to Spring Security `GrantedAuthority`
|
|
116
|
+
objects. It extracts roles from both `realm_access` and `resource_access` claims in the
|
|
117
|
+
JWT access token.
|
|
118
|
+
|
|
119
|
+
```java
|
|
120
|
+
package {{BASE_PACKAGE}}.config;
|
|
121
|
+
|
|
122
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
123
|
+
import org.springframework.core.convert.converter.Converter;
|
|
124
|
+
import org.springframework.security.core.GrantedAuthority;
|
|
125
|
+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
126
|
+
import org.springframework.security.oauth2.jwt.Jwt;
|
|
127
|
+
import org.springframework.stereotype.Component;
|
|
128
|
+
import java.util.ArrayList;
|
|
129
|
+
import java.util.Collection;
|
|
130
|
+
import java.util.Collections;
|
|
131
|
+
import java.util.List;
|
|
132
|
+
import java.util.Map;
|
|
133
|
+
|
|
134
|
+
@Component
|
|
135
|
+
public class KeycloakJwtGrantedAuthoritiesConverter
|
|
136
|
+
implements Converter<Jwt, Collection<GrantedAuthority>> {
|
|
137
|
+
|
|
138
|
+
@Value("${app.security.keycloak-client-id}")
|
|
139
|
+
private String clientId;
|
|
140
|
+
|
|
141
|
+
@Override
|
|
142
|
+
public Collection<GrantedAuthority> convert(Jwt jwt) {
|
|
143
|
+
List<GrantedAuthority> authorities = new ArrayList<>();
|
|
144
|
+
authorities.addAll(extractRealmRoles(jwt));
|
|
145
|
+
authorities.addAll(extractResourceRoles(jwt));
|
|
146
|
+
return authorities;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@SuppressWarnings("unchecked")
|
|
150
|
+
private Collection<GrantedAuthority> extractRealmRoles(Jwt jwt) {
|
|
151
|
+
Map<String, Object> realmAccess = jwt.getClaim("realm_access");
|
|
152
|
+
if (realmAccess == null) return Collections.emptyList();
|
|
153
|
+
List<String> roles = (List<String>) realmAccess.get("roles");
|
|
154
|
+
if (roles == null) return Collections.emptyList();
|
|
155
|
+
return roles.stream()
|
|
156
|
+
.map(role -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + role))
|
|
157
|
+
.toList();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@SuppressWarnings("unchecked")
|
|
161
|
+
private Collection<GrantedAuthority> extractResourceRoles(Jwt jwt) {
|
|
162
|
+
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
|
|
163
|
+
if (resourceAccess == null) return Collections.emptyList();
|
|
164
|
+
Map<String, Object> resource = (Map<String, Object>) resourceAccess.get(clientId);
|
|
165
|
+
if (resource == null) return Collections.emptyList();
|
|
166
|
+
List<String> roles = (List<String>) resource.get("roles");
|
|
167
|
+
if (roles == null) return Collections.emptyList();
|
|
168
|
+
return roles.stream()
|
|
169
|
+
.map(role -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + role))
|
|
170
|
+
.toList();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Role-Based Access Control
|
|
178
|
+
|
|
179
|
+
### At the endpoint level (method security)
|
|
180
|
+
|
|
181
|
+
```java
|
|
182
|
+
@PreAuthorize("hasRole('ADMIN')")
|
|
183
|
+
@DeleteMapping("/{id}")
|
|
184
|
+
public ResponseEntity<Void> delete(@PathVariable String id) { ... }
|
|
185
|
+
|
|
186
|
+
@PreAuthorize("hasAnyRole('ADMIN', 'OPERATOR')")
|
|
187
|
+
@GetMapping
|
|
188
|
+
public ResponseEntity<Page<ProductDTO>> list(Pageable pageable) { ... }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The `ROLE_` prefix is handled by the converter. In `@PreAuthorize`, use role names
|
|
192
|
+
WITHOUT the prefix (Spring Security strips it automatically in `hasRole()`).
|
|
193
|
+
|
|
194
|
+
### Role Constants
|
|
195
|
+
|
|
196
|
+
Define roles as a constants class to avoid string duplication:
|
|
197
|
+
|
|
198
|
+
```java
|
|
199
|
+
package {{BASE_PACKAGE}}.shared.security;
|
|
200
|
+
|
|
201
|
+
public final class Roles {
|
|
202
|
+
public static final String ADMIN = "ADMIN";
|
|
203
|
+
public static final String OPERATOR = "OPERATOR";
|
|
204
|
+
private Roles() {}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## SecurityContextUtil.java
|
|
211
|
+
|
|
212
|
+
Utility to extract user information from the JWT principal in the current security context.
|
|
213
|
+
|
|
214
|
+
```java
|
|
215
|
+
package {{BASE_PACKAGE}}.shared.security;
|
|
216
|
+
|
|
217
|
+
import org.springframework.security.core.Authentication;
|
|
218
|
+
import org.springframework.security.core.GrantedAuthority;
|
|
219
|
+
import org.springframework.security.core.context.SecurityContextHolder;
|
|
220
|
+
import org.springframework.security.oauth2.jwt.Jwt;
|
|
221
|
+
import org.springframework.stereotype.Component;
|
|
222
|
+
import java.util.Collections;
|
|
223
|
+
import java.util.Set;
|
|
224
|
+
import java.util.stream.Collectors;
|
|
225
|
+
|
|
226
|
+
@Component
|
|
227
|
+
public class SecurityContextUtil {
|
|
228
|
+
|
|
229
|
+
public String getCurrentUserId() {
|
|
230
|
+
Jwt jwt = getJwt();
|
|
231
|
+
return jwt != null ? jwt.getSubject() : null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public String getCurrentUsername() {
|
|
235
|
+
Jwt jwt = getJwt();
|
|
236
|
+
return jwt != null ? jwt.getClaimAsString("preferred_username") : null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public String getCurrentUserEmail() {
|
|
240
|
+
Jwt jwt = getJwt();
|
|
241
|
+
return jwt != null ? jwt.getClaimAsString("email") : null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public Set<String> getCurrentRoles() {
|
|
245
|
+
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
|
246
|
+
if (auth == null) return Collections.emptySet();
|
|
247
|
+
return auth.getAuthorities().stream()
|
|
248
|
+
.map(GrantedAuthority::getAuthority)
|
|
249
|
+
.filter(a -> a.startsWith("ROLE_"))
|
|
250
|
+
.map(a -> a.substring(5))
|
|
251
|
+
.collect(Collectors.toSet());
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
public Jwt getJwt() {
|
|
255
|
+
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
|
256
|
+
if (auth != null && auth.getPrincipal() instanceof Jwt jwt) {
|
|
257
|
+
return jwt;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## AuditAwareImpl.java
|
|
267
|
+
|
|
268
|
+
Implements `AuditorAware<String>` to populate `@CreatedBy` and `@LastModifiedBy` fields
|
|
269
|
+
on JPA entities or MongoDB documents.
|
|
270
|
+
|
|
271
|
+
```java
|
|
272
|
+
package {{BASE_PACKAGE}}.shared.audit;
|
|
273
|
+
|
|
274
|
+
import {{BASE_PACKAGE}}.shared.security.SecurityContextUtil;
|
|
275
|
+
import lombok.RequiredArgsConstructor;
|
|
276
|
+
import org.springframework.data.domain.AuditorAware;
|
|
277
|
+
import org.springframework.stereotype.Component;
|
|
278
|
+
import java.util.Optional;
|
|
279
|
+
|
|
280
|
+
@Component
|
|
281
|
+
@RequiredArgsConstructor
|
|
282
|
+
public class AuditAwareImpl implements AuditorAware<String> {
|
|
283
|
+
|
|
284
|
+
private final SecurityContextUtil securityContextUtil;
|
|
285
|
+
|
|
286
|
+
@Override
|
|
287
|
+
public Optional<String> getCurrentAuditor() {
|
|
288
|
+
return Optional.ofNullable(securityContextUtil.getCurrentUsername())
|
|
289
|
+
.or(() -> Optional.of("system"));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## JWT Access Token Structure Reference
|
|
297
|
+
|
|
298
|
+
For the spec, include a sample decoded JWT access token payload showing where Keycloak
|
|
299
|
+
places roles:
|
|
300
|
+
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"sub": "f1234-abcd-5678",
|
|
304
|
+
"preferred_username": "john.doe",
|
|
305
|
+
"email": "john.doe@example.com",
|
|
306
|
+
"realm_access": {
|
|
307
|
+
"roles": ["USER", "offline_access"]
|
|
308
|
+
},
|
|
309
|
+
"resource_access": {
|
|
310
|
+
"{{KEYCLOAK_CLIENT_ID}}": {
|
|
311
|
+
"roles": ["ADMIN"]
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
"scope": "openid profile email",
|
|
315
|
+
"iss": "{{KEYCLOAK_ISSUER_URI}}",
|
|
316
|
+
"exp": 1710000000,
|
|
317
|
+
"iat": 1709996400
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Testing with JWT
|
|
324
|
+
|
|
325
|
+
For integration tests, mock the JWT token:
|
|
326
|
+
|
|
327
|
+
```java
|
|
328
|
+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
|
|
329
|
+
|
|
330
|
+
@WebMvcTest(ProductController.class)
|
|
331
|
+
class ProductControllerTest {
|
|
332
|
+
|
|
333
|
+
@Autowired private MockMvc mockMvc;
|
|
334
|
+
@MockBean private ProductService productService;
|
|
335
|
+
|
|
336
|
+
@Test
|
|
337
|
+
void list_authenticatedUser_returns200() throws Exception {
|
|
338
|
+
when(productService.findAll(any(), any())).thenReturn(Page.empty());
|
|
339
|
+
|
|
340
|
+
mockMvc.perform(get("/api/v1/products")
|
|
341
|
+
.with(jwt().authorities(new SimpleGrantedAuthority("ROLE_ADMIN"))))
|
|
342
|
+
.andExpect(status().isOk());
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@Test
|
|
346
|
+
void list_unauthenticated_returns401() throws Exception {
|
|
347
|
+
mockMvc.perform(get("/api/v1/products"))
|
|
348
|
+
.andExpect(status().isUnauthorized());
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|