Skip to content

Commit af638d2

Browse files
dfcoffinclaude
andauthored
fix(authserver): six pre-existing defects blocking dev-mysql boot (#125)
Discovered during Phase 2.0 auth-server bring-up (#122). The auth-server had not been booted clean against a fresh MySQL in living memory; static audit only surfaced these defects under actual runtime. Six independent blockers patched here; one more (filter chain canonical pattern) tracked as #124, and ApplicationInformation schema repair as #123. Patch 1 — MySQL V1 FK uniqueness V1_0_0__create_oauth2_schema.sql declared an FK from espi_application_info.client_id to oauth2_registered_client.client_id, but the referenced column had no unique constraint (only PRIMARY KEY on id). MySQL rejected the CREATE TABLE. Added UNIQUE KEY on oauth2_registered_client.client_id and removed the now-redundant non-unique CREATE INDEX. PostgreSQL V1 was already correct. Patch 2 — Flyway location config / source layout mismatch Source files live at db/vendor/{h2,mysql,postgresql}/ but 4 profile YAMLs plus base application.yml plus docker-compose env vars pointed at classpath:db/migration/{mysql,postgresql,h2}/. Boot only "worked" historically because of stale target/classes/db/migration/ artifacts from a prior layout; mvn clean exposed the gap. Updated all 6 config references to classpath:db/vendor/... per the user's preference to keep the current vendor-organized source layout. Patch 3 — Flyway target=2.0.0 (workaround for #123) V3+ migrations INSERT into espi_application_info columns that don't exist in MySQL V1 (client_description, contact_name, contact_email, scope, grant_types, response_types). Root cause is schema drift from the ESPI 4.0 XSD; full repair tracked as #123. Set spring.flyway.target: "2.0.0" in application-dev-mysql.yml so boot succeeds with V1+V2 schema (sufficient for OAuth2 grant + introspect; V3+ is seed/demo data). Patch 4 — JWT-only resource server filter chain AuthorizationServerConfig.authorizationServerSecurityFilterChain declared both .opaqueToken(Customizer.withDefaults()) and OIDC (which auto-wires JWT validation) on the same chain. Spring Security 7.x refuses by design: "Spring Security only supports JWTs or Opaque Tokens, not both at the same time." Removed .opaqueToken; the chain now uses .jwt(Customizer.withDefaults()). Outbound tokens to ESPI clients remain opaque — controlled per-RegisteredClient via accessTokenFormat(OAuth2TokenFormat.REFERENCE), unaffected by this filter-chain change. Patch 5 — Disabled @order(0) header-injection chains HttpsEnforcementConfig declared two SecurityFilterChain @order(0) beans (httpsEnforcementFilterChain for prod, developmentSecurityFilterChain for dev profiles) with securityMatcher("/**") that monopolized every request, preempting the auth-server's @order(1) chain. Result: every OAuth2 endpoint returned 404. Header injection should be a HeaderWriter or Filter, not a SecurityFilterChain claiming /**. Disabled both @bean annotations; AuthorizationServerConfig's own chain already configures equivalent security headers. Re-introducing the dev-friendly cache-control headers via a proper mechanism is a follow-up cleanup item. Patch 6 — HikariCP auto-commit application-dev-mysql.yml had auto-commit: false but JdbcRegisteredClientRepository.save() (and likely other repository methods) declares no @transactional. With auto-commit off and no declared transaction, INSERTs execute against an uncommitted connection and silently roll back when the connection returns to the pool. Boot logged "Inserted registered client: 1" three times while the DB had 0 rows. Set auto-commit: true to make the seed flow work. Architectural fix (proper @transactional throughout the repository) deferred — tracked as part of #122 / Phase 2.0 cleanup. What works after these six patches - mvn clean install of openespi-authserver succeeds - mvn -pl openespi-authserver spring-boot:run reaches Started state in ~35-42s - Flyway V1+V2 apply cleanly to a fresh MySQL 8.4 - Tomcat listens on port 9999 - Three default ESPI clients persist correctly: data_custodian_admin, third_party, third_party_admin - Discovery endpoints return 200: /.well-known/oauth-authorization-server /.well-known/openid-configuration /oauth2/jwks /login What does NOT work yet (tracked separately) - POST /oauth2/token returns 401 — resource-server bearer filter preempts the token endpoint. Fix tracked as #124 (filter chain needs the canonical OAuth2AuthorizationServerConfiguration .applyDefaultSecurity pattern with a second @order(2) chain). - JdbcRegisteredClientRepository.findAll() returns empty list even when rows exist. Boot logs "Default ESPI Clients: 0" while DB has 3 rows. Defect #8 from the #122 audit; not yet diagnosed. - V3-V6 Flyway migrations skipped via target=2.0.0 pending #123. Refs: #122 #123 #124 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 7fa7243 commit af638d2

9 files changed

Lines changed: 32 additions & 15 deletions

File tree

openespi-authserver/docker/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ services:
2727

2828
# Flyway Configuration
2929
SPRING_FLYWAY_ENABLED: true
30-
SPRING_FLYWAY_LOCATIONS: classpath:db/migration/mysql
30+
SPRING_FLYWAY_LOCATIONS: classpath:db/vendor/mysql
3131
SPRING_FLYWAY_BASELINE_ON_MIGRATE: true
3232

3333
# Security Configuration

openespi-authserver/src/main/java/org/greenbuttonalliance/espi/authserver/config/AuthorizationServerConfig.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,14 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
136136
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
137137
)
138138
)
139-
// Accept access tokens for User Info and/or Client Registration
139+
// Accept access tokens for User Info and/or Client Registration.
140+
// OIDC auto-configures JWT validation for self-protected endpoints
141+
// (id_token signing requires JWT). Outbound tokens to ESPI clients
142+
// remain opaque via accessTokenFormat(REFERENCE) on each RegisteredClient.
143+
// Cannot configure both .jwt() and .opaqueToken() on the same chain
144+
// in Spring Security 7.x.
140145
.oauth2ResourceServer(resourceServer -> resourceServer
141-
.opaqueToken(Customizer.withDefaults())
142-
143-
//.jwt(Customizer.withDefaults())
146+
.jwt(Customizer.withDefaults())
144147
)
145148
// HTTPS Channel Security for Production
146149
//should be able to use property server.ssl.enabled=true

openespi-authserver/src/main/java/org/greenbuttonalliance/espi/authserver/config/HttpsEnforcementConfig.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,12 @@ public void logSecurityConfiguration() {
9898
*
9999
* Enforces TLS 1.3 for all requests in production environment (NAESB ESPI 4.0)
100100
*/
101-
@Bean
101+
// DISABLED: this chain's securityMatcher("/**") at @Order(0) preempts the
102+
// authorization-server filter chain at @Order(1), causing every OAuth2 endpoint
103+
// to 404. Headers should be injected via a HeaderWriter or Filter, not via a
104+
// SecurityFilterChain that monopolizes /**. AuthorizationServerConfig's own
105+
// chain (@Order(1)) already configures equivalent security headers.
106+
// @Bean
102107
@Profile("prod")
103108
@Order(0)
104109
public SecurityFilterChain httpsEnforcementFilterChain(HttpSecurity http) throws Exception {
@@ -151,7 +156,9 @@ public SecurityFilterChain httpsEnforcementFilterChain(HttpSecurity http) throws
151156
*
152157
* Allows HTTP for development while still providing security headers
153158
*/
154-
@Bean
159+
// DISABLED: same reason as httpsEnforcementFilterChain above — securityMatcher("/**")
160+
// at @Order(0) blocks the auth-server endpoints.
161+
// @Bean
155162
@Profile({"dev", "dev-mysql", "dev-postgresql", "local"})
156163
@Order(0)
157164
public SecurityFilterChain developmentSecurityFilterChain(HttpSecurity http) throws Exception {

openespi-authserver/src/main/resources/application-dev-mysql.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ spring:
1414
minimum-idle: 5
1515
idle-timeout: 300000
1616
max-lifetime: 1200000
17-
auto-commit: false
17+
# auto-commit must be true: JdbcRegisteredClientRepository.save() and other
18+
# repository methods do not declare @Transactional, so without auto-commit
19+
# their INSERTs are never committed and silently roll back when the
20+
# connection returns to the pool. Architectural cleanup tracked separately.
21+
auto-commit: true
1822

1923
# JPA/Hibernate Configuration for MySQL
2024
jpa:
@@ -36,11 +40,14 @@ spring:
3640
flyway:
3741
enabled: true
3842
baseline-on-migrate: true
39-
locations: classpath:db/migration/mysql
43+
locations: classpath:db/vendor/mysql
4044
schemas: oauth2_authserver
4145
user: ${spring.datasource.username}
4246
password: ${spring.datasource.password}
4347
url: ${spring.datasource.url}
48+
# Skip V3+ pending ESPI 4.0 XSD-aligned schema repair (see issue #123).
49+
# V1+V2 provide enough for OAuth2 grant + introspection; V3 onwards is seed/demo data.
50+
target: "2.0.0"
4451

4552
# Development Logging
4653
logging:

openespi-authserver/src/main/resources/application-dev-postgresql.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ spring:
3838
flyway:
3939
enabled: true
4040
baseline-on-migrate: true
41-
locations: classpath:db/migration/postgresql
41+
locations: classpath:db/vendor/postgresql
4242
schemas: public
4343

4444
# Security Configuration

openespi-authserver/src/main/resources/application-local.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ spring:
3737
flyway:
3838
enabled: true
3939
baseline-on-migrate: true
40-
locations: classpath:db/migration/h2
40+
locations: classpath:db/vendor/h2
4141
schemas: oauth2_authserver
4242

4343
# Local Development Logging

openespi-authserver/src/main/resources/application-prod.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ spring:
8989
flyway:
9090
enabled: true
9191
baseline-on-migrate: true
92-
locations: classpath:db/migration/mysql
92+
locations: classpath:db/vendor/mysql
9393
schemas: oauth2_authserver
9494
validate-on-migrate: true
9595
clean-disabled: true

openespi-authserver/src/main/resources/application.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ spring:
5050
flyway:
5151
enabled: true
5252
baseline-on-migrate: true
53-
locations: classpath:db/migration,classpath:db/vendor/h2
53+
locations: classpath:db/vendor/h2
5454
#schemas: oauth2_authserver
5555
jackson:
5656
default-property-inclusion: non_null

openespi-authserver/src/main/resources/db/vendor/mysql/V1_0_0__create_oauth2_schema.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ CREATE TABLE oauth2_registered_client (
6464
scopes varchar(1000) NOT NULL,
6565
client_settings varchar(2000) NOT NULL,
6666
token_settings varchar(2000) NOT NULL,
67-
PRIMARY KEY (id)
67+
PRIMARY KEY (id),
68+
UNIQUE KEY uk_oauth2_registered_client_client_id (client_id)
6869
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
6970

7071
-- ESPI Application Information mapping
@@ -109,5 +110,4 @@ CREATE INDEX idx_oauth2_authorization_client_principal ON oauth2_authorization (
109110
CREATE INDEX idx_oauth2_authorization_code ON oauth2_authorization (authorization_code_value(255));
110111
CREATE INDEX idx_oauth2_authorization_access_token ON oauth2_authorization (access_token_value(255));
111112
CREATE INDEX idx_oauth2_authorization_refresh_token ON oauth2_authorization (refresh_token_value(255));
112-
CREATE INDEX idx_oauth2_registered_client_id ON oauth2_registered_client (client_id);
113113
CREATE INDEX idx_espi_application_client_id ON espi_application_info (client_id);

0 commit comments

Comments
 (0)