Skip to content

Commit 9022c9a

Browse files
ng-galienclaude
andauthored
[Spring] Add clientRegistrationId option for OAuth2 HTTP Interface (#22726)
* feat(spring): Add @ClientRegistrationId support for Spring HTTP Interface Add support for the @ClientRegistrationId annotation in Spring HTTP Interface generated clients to enable OAuth2 authentication integration with Spring Security. Changes: - Add new clientRegistrationId configuration option in SpringCodegen - Update api.mustache template to include @ClientRegistrationId annotation - Add import for org.springframework.security.oauth2.client.annotation.ClientRegistrationId - Process clientRegistrationId in postProcessOperationsWithModels - Add sample configuration and example output The @ClientRegistrationId annotation automatically associates OAuth2 tokens with HTTP requests when using Spring Security 7.0+ HTTP Service Client integration. Usage: openapi-generator-cli generate -g spring \ --library spring-http-interface \ --additional-properties clientRegistrationId=my-oauth-client \ -i spec.yaml -o ./output Related documentation: https://docs.spring.io/spring-security/reference/features/integrations/rest/http-service-client.html * refactor: Move @ClientRegistrationId annotation to class level Move the @ClientRegistrationId annotation from individual methods to the interface class level, following Spring Security's recommended practice. Changes: - Update api.mustache to place annotation on interface declaration - Modify SpringCodegen to set clientRegistrationId on operations map - Update sample code to show class-level annotation - Update README with improved example and explanation This approach is cleaner and avoids repeating the annotation on every method, as recommended in Spring Security documentation. * test(spring): Add unit tests for clientRegistrationId option Add tests to verify: - @ClientRegistrationId annotation is generated when option is set - Annotation is not present when option is not configured Also regenerate complete samples for spring-http-interface-oauth config. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: Add OAuth2 configuration example to README - Add README.md to .openapi-generator-ignore to preserve custom docs - Include proper OAuth2ClientHttpRequestInterceptor configuration example - Document Spring Boot 3.5+ / Spring Security 6.5+ requirements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ci: Retrigger CI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: Regenerate samples and docs after rebase on master Make the spring-http-interface-oauth sample fully generator-driven instead of hand-patching its pom: - pom-sb3/sb4.mustache: emit spring-boot-starter-oauth2-client when clientRegistrationId is set - spring-http-interface-oauth.yaml: set parent to Spring Boot 3.5.0 (required by @ClientRegistrationId / Spring Security 6.5+) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: update java-camel generator options * chore: refresh spring http interface oauth sample * fix: require Spring Boot 4 for client registration id --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9a6fa8c commit 9022c9a

69 files changed

Lines changed: 9393 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
generatorName: spring
2+
library: spring-http-interface
3+
outputDir: samples/client/petstore/spring-http-interface-oauth
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/spring/petstore-with-fake-endpoints-models-for-testing.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/JavaSpring
6+
additionalProperties:
7+
artifactId: spring-http-interface-oauth
8+
snapshotVersion: "true"
9+
hideGenerationTimestamp: "true"
10+
modelNameSuffix: 'Dto'
11+
generatedConstructorWithRequiredArgs: "false"
12+
clientRegistrationId: "petstore-oauth"
13+
useSpringBoot4: "true"
14+
useJackson3: true
15+
openApiNullable: false

docs/generators/java-camel.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
4343
|camelSecurityDefinitions|generate camel security definitions| |true|
4444
|camelUseDefaultValidationErrorProcessor|generate default validation error processor| |true|
4545
|camelValidationErrorProcessor|validation error processor bean name| |validationErrorProcessor|
46+
|clientRegistrationId|Client registration ID for OAuth2 in Spring HTTP Interface (@ClientRegistrationId annotation). Requires library=spring-http-interface and useSpringBoot4=true (Spring Security 7).| |null|
4647
|configPackage|configuration package for generated code| |org.openapitools.configuration|
4748
|containerDefaultToNull|Set containers (array, set, map) default to null| |false|
4849
|dateLibrary|Option. Date library to use|<dl><dt>**joda**</dt><dd>Joda (for legacy app only)</dd><dt>**legacy**</dt><dd>Legacy java.util.Date</dd><dt>**java8-localdatetime**</dt><dd>Java 8 using LocalDateTime (for legacy app only)</dd><dt>**java8**</dt><dd>Java 8 native JSR310 (preferred for jdk 1.8+)</dd></dl>|java8|

docs/generators/spring.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
3636
|bigDecimalAsString|Treat BigDecimal values as Strings to avoid precision loss.| |false|
3737
|booleanGetterPrefix|Set booleanGetterPrefix| |get|
3838
|camelCaseDollarSign|Fix camelCase when starting with $ sign. when true : $Value when false : $value| |false|
39+
|clientRegistrationId|Client registration ID for OAuth2 in Spring HTTP Interface (@ClientRegistrationId annotation). Requires library=spring-http-interface and useSpringBoot4=true (Spring Security 7).| |null|
3940
|configPackage|configuration package for generated code| |org.openapitools.configuration|
4041
|containerDefaultToNull|Set containers (array, set, map) default to null| |false|
4142
|dateLibrary|Option. Date library to use|<dl><dt>**joda**</dt><dd>Joda (for legacy app only)</dd><dt>**legacy**</dt><dd>Legacy java.util.Date</dd><dt>**java8-localdatetime**</dt><dd>Java 8 using LocalDateTime (for legacy app only)</dd><dt>**java8**</dt><dd>Java 8 native JSR310 (preferred for jdk 1.8+)</dd></dl>|java8|

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ public class SpringCodegen extends AbstractJavaCodegen
122122
public static final String GENERATE_SORT_VALIDATION = "generateSortValidation";
123123
public static final String GENERATE_PAGEABLE_CONSTRAINT_VALIDATION = "generatePageableConstraintValidation";
124124
public static final String SUBSTITUTE_GENERIC_PAGED_MODEL = "substituteGenericPagedModel";
125+
public static final String CLIENT_REGISTRATION_ID = "clientRegistrationId";
125126

126127
@Getter
127128
public enum RequestMappingMode {
@@ -196,6 +197,8 @@ public enum RequestMappingMode {
196197
@Setter protected boolean generateSortValidation = false;
197198
@Setter protected boolean generatePageableConstraintValidation = false;
198199
@Setter protected boolean substituteGenericPagedModel = false;
200+
@Getter @Setter
201+
protected String clientRegistrationId = null;
199202

200203
// Map from operationId to allowed sort values for @ValidSort annotation generation
201204
private Map<String, List<String>> sortValidationEnums = new HashMap<>();
@@ -344,6 +347,7 @@ public SpringCodegen() {
344347
.defaultValue("false")
345348
);
346349
cliOptions.add(CliOption.newBoolean(USE_JSPECIFY, "Use Jspecify for null checks", useJspecify));
350+
cliOptions.add(CliOption.newString(CLIENT_REGISTRATION_ID, "Client registration ID for OAuth2 in Spring HTTP Interface (@ClientRegistrationId annotation). Requires library=spring-http-interface and useSpringBoot4=true (Spring Security 7)."));
347351
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
348352
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
349353
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
@@ -566,6 +570,7 @@ public void processOpts() {
566570
convertPropertyToBooleanAndWriteBack(OPTIONAL_ACCEPT_NULLABLE, this::setOptionalAcceptNullable);
567571
convertPropertyToBooleanAndWriteBack(USE_SPRING_BUILT_IN_VALIDATION, this::setUseSpringBuiltInValidation);
568572
convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_DEDUCTION_FOR_ONE_OF_INTERFACES, this::setUseDeductionForOneOfInterfaces);
573+
convertPropertyToStringAndWriteBack(CLIENT_REGISTRATION_ID, this::setClientRegistrationId);
569574

570575
additionalProperties.put("springHttpStatus", new SpringHttpStatusLambda());
571576

@@ -577,6 +582,14 @@ public void processOpts() {
577582
if (isUseSpringBoot4()) {
578583
setUseSpringBoot3(false);
579584
}
585+
if (isNotEmpty(clientRegistrationId)) {
586+
if (!SPRING_HTTP_INTERFACE.equals(library)) {
587+
throw new IllegalArgumentException(CLIENT_REGISTRATION_ID + " is only supported with the " + SPRING_HTTP_INTERFACE + " library");
588+
}
589+
if (!isUseSpringBoot4()) {
590+
throw new IllegalArgumentException(CLIENT_REGISTRATION_ID + " requires " + USE_SPRING_BOOT4 + "=true because @ClientRegistrationId is provided by Spring Security 7");
591+
}
592+
}
580593

581594
if (isUseSpringBoot3() || isUseSpringBoot4()) {
582595
if (AnnotationLibrary.SWAGGER1.equals(getAnnotationLibrary())) {
@@ -1012,6 +1025,11 @@ public void setIsVoid(boolean isVoid) {
10121025
// But use a sensible tag name if there is none
10131026
objs.put("tagName", "default".equals(firstTagName) ? firstOperation.baseName : firstTagName);
10141027
objs.put("tagDescription", escapeText(firstTag.getDescription()));
1028+
1029+
// Add clientRegistrationId for spring-http-interface with OAuth
1030+
if (SPRING_HTTP_INTERFACE.equals(library) && clientRegistrationId != null && !clientRegistrationId.isEmpty()) {
1031+
operations.put("clientRegistrationId", clientRegistrationId);
1032+
}
10151033
}
10161034

10171035
removeImport(objs, "java.util.List");

modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-http-interface/api.mustache

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import org.springframework.http.ResponseEntity;
1515
{{/useResponseEntity}}
1616
import org.springframework.web.bind.annotation.*;
1717
import org.springframework.web.service.annotation.*;
18+
{{#clientRegistrationId}}
19+
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId;
20+
{{/clientRegistrationId}}
1821
import org.springframework.web.multipart.MultipartFile;
1922
{{#useBeanValidation}}
2023
import {{javaxPackage}}.validation.Valid;
@@ -38,6 +41,9 @@ import {{javaxPackage}}.annotation.Generated;
3841
{{>generatedAnnotation}}
3942

4043
{{#operations}}
44+
{{#clientRegistrationId}}
45+
@ClientRegistrationId("{{clientRegistrationId}}")
46+
{{/clientRegistrationId}}
4147
public interface {{classname}} {
4248
{{#operation}}
4349

modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-http-interface/pom-sb4.mustache

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@
112112
<artifactId>spring-boot-starter-validation</artifactId>
113113
</dependency>
114114
{{/performBeanValidation}}{{/useBeanValidation}}
115+
{{#clientRegistrationId}}
116+
<dependency>
117+
<groupId>org.springframework.boot</groupId>
118+
<artifactId>spring-boot-starter-oauth2-client</artifactId>
119+
</dependency>
120+
{{/clientRegistrationId}}
115121
<dependency>
116122
<groupId>org.springframework.boot</groupId>
117123
<artifactId>spring-boot-starter-{{#reactive}}web{{/reactive}}{{^reactive}}rest{{/reactive}}client-test</artifactId>

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6584,6 +6584,83 @@ public void shouldAddNullableImportForArrayTypeModels() throws IOException {
65846584
.hasImports("org.springframework.lang.Nullable");
65856585
}
65866586

6587+
@Test
6588+
public void testClientRegistrationIdAnnotation() throws IOException {
6589+
final SpringCodegen codegen = new SpringCodegen();
6590+
codegen.setLibrary("spring-http-interface");
6591+
codegen.setUseSpringBoot4(true);
6592+
codegen.setClientRegistrationId("my-oauth-client");
6593+
6594+
final Map<String, File> files = generateFiles(codegen, "src/test/resources/3_0/petstore.yaml");
6595+
6596+
// Check that the @ClientRegistrationId annotation is generated at class level
6597+
JavaFileAssert.assertThat(files.get("PetApi.java"))
6598+
.hasImports("org.springframework.security.oauth2.client.annotation.ClientRegistrationId")
6599+
.assertTypeAnnotations()
6600+
.containsWithNameAndAttributes("ClientRegistrationId", ImmutableMap.of("value", "\"my-oauth-client\""));
6601+
}
6602+
6603+
@Test
6604+
public void testClientRegistrationIdAnnotationNotPresentWhenNotConfigured() throws IOException {
6605+
final SpringCodegen codegen = new SpringCodegen();
6606+
codegen.setLibrary("spring-http-interface");
6607+
codegen.setUseSpringBoot4(true);
6608+
// clientRegistrationId not set
6609+
6610+
final Map<String, File> files = generateFiles(codegen, "src/test/resources/3_0/petstore.yaml");
6611+
6612+
// Check that the @ClientRegistrationId annotation is NOT generated
6613+
assertFileNotContains(files.get("PetApi.java").toPath(), "@ClientRegistrationId", "ClientRegistrationId");
6614+
}
6615+
6616+
@Test
6617+
public void shouldRefuseClientRegistrationIdWithoutSpringBoot4() throws IOException {
6618+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
6619+
output.deleteOnExit();
6620+
6621+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/petstore.yaml");
6622+
final SpringCodegen codegen = new SpringCodegen();
6623+
codegen.setOpenAPI(openAPI);
6624+
codegen.setOutputDir(output.getAbsolutePath());
6625+
codegen.setLibrary("spring-http-interface");
6626+
codegen.setClientRegistrationId("my-oauth-client");
6627+
6628+
ClientOptInput input = new ClientOptInput();
6629+
input.openAPI(openAPI);
6630+
input.config(codegen);
6631+
6632+
Generator generator = new DefaultGenerator()
6633+
.opts(input);
6634+
6635+
Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
6636+
.isThrownBy(generator::generate)
6637+
.withMessageContaining(SpringCodegen.USE_SPRING_BOOT4);
6638+
}
6639+
6640+
@Test
6641+
public void shouldRefuseClientRegistrationIdOutsideSpringHttpInterface() throws IOException {
6642+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
6643+
output.deleteOnExit();
6644+
6645+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/petstore.yaml");
6646+
final SpringCodegen codegen = new SpringCodegen();
6647+
codegen.setOpenAPI(openAPI);
6648+
codegen.setOutputDir(output.getAbsolutePath());
6649+
codegen.setUseSpringBoot4(true);
6650+
codegen.setClientRegistrationId("my-oauth-client");
6651+
6652+
ClientOptInput input = new ClientOptInput();
6653+
input.openAPI(openAPI);
6654+
input.config(codegen);
6655+
6656+
Generator generator = new DefaultGenerator()
6657+
.opts(input);
6658+
6659+
Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
6660+
.isThrownBy(generator::generate)
6661+
.withMessageContaining("spring-http-interface");
6662+
}
6663+
65876664
@Test
65886665
public void shouldRefuseJackson3WithoutSpringboot4() throws IOException {
65896666
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
24+
25+
# Preserve custom OAuth2 documentation
26+
README.md
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
pom.xml
2+
src/main/java/org/openapitools/api/AnotherFakeApi.java
3+
src/main/java/org/openapitools/api/FakeApi.java
4+
src/main/java/org/openapitools/api/FakeClassnameTags123Api.java
5+
src/main/java/org/openapitools/api/PetApi.java
6+
src/main/java/org/openapitools/api/StoreApi.java
7+
src/main/java/org/openapitools/api/UserApi.java
8+
src/main/java/org/openapitools/configuration/HttpInterfacesAbstractConfigurator.java
9+
src/main/java/org/openapitools/model/AdditionalPropertiesAnyTypeDto.java
10+
src/main/java/org/openapitools/model/AdditionalPropertiesArrayDto.java
11+
src/main/java/org/openapitools/model/AdditionalPropertiesBooleanDto.java
12+
src/main/java/org/openapitools/model/AdditionalPropertiesClassDto.java
13+
src/main/java/org/openapitools/model/AdditionalPropertiesIntegerDto.java
14+
src/main/java/org/openapitools/model/AdditionalPropertiesNumberDto.java
15+
src/main/java/org/openapitools/model/AdditionalPropertiesObjectDto.java
16+
src/main/java/org/openapitools/model/AdditionalPropertiesStringDto.java
17+
src/main/java/org/openapitools/model/AnimalDto.java
18+
src/main/java/org/openapitools/model/ApiResponseDto.java
19+
src/main/java/org/openapitools/model/ArrayOfArrayOfNumberOnlyDto.java
20+
src/main/java/org/openapitools/model/ArrayOfNumberOnlyDto.java
21+
src/main/java/org/openapitools/model/ArrayTestDto.java
22+
src/main/java/org/openapitools/model/BigCatDto.java
23+
src/main/java/org/openapitools/model/CapitalizationDto.java
24+
src/main/java/org/openapitools/model/CatDto.java
25+
src/main/java/org/openapitools/model/CategoryDto.java
26+
src/main/java/org/openapitools/model/ChildWithNullableDto.java
27+
src/main/java/org/openapitools/model/ClassModelDto.java
28+
src/main/java/org/openapitools/model/ClientDto.java
29+
src/main/java/org/openapitools/model/ContainerDefaultValueDto.java
30+
src/main/java/org/openapitools/model/DogDto.java
31+
src/main/java/org/openapitools/model/EnumArraysDto.java
32+
src/main/java/org/openapitools/model/EnumClassDto.java
33+
src/main/java/org/openapitools/model/EnumTestDto.java
34+
src/main/java/org/openapitools/model/FileDto.java
35+
src/main/java/org/openapitools/model/FileSchemaTestClassDto.java
36+
src/main/java/org/openapitools/model/FormatTestDto.java
37+
src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java
38+
src/main/java/org/openapitools/model/ListDto.java
39+
src/main/java/org/openapitools/model/MapTestDto.java
40+
src/main/java/org/openapitools/model/MixedPropertiesAndAdditionalPropertiesClassDto.java
41+
src/main/java/org/openapitools/model/Model200ResponseDto.java
42+
src/main/java/org/openapitools/model/NameDto.java
43+
src/main/java/org/openapitools/model/NullableMapPropertyDto.java
44+
src/main/java/org/openapitools/model/NumberOnlyDto.java
45+
src/main/java/org/openapitools/model/OrderDto.java
46+
src/main/java/org/openapitools/model/OuterCompositeDto.java
47+
src/main/java/org/openapitools/model/OuterEnumDto.java
48+
src/main/java/org/openapitools/model/ParentWithNullableDto.java
49+
src/main/java/org/openapitools/model/PetDto.java
50+
src/main/java/org/openapitools/model/ReadOnlyFirstDto.java
51+
src/main/java/org/openapitools/model/ResponseObjectWithDifferentFieldNamesDto.java
52+
src/main/java/org/openapitools/model/ReturnDto.java
53+
src/main/java/org/openapitools/model/SpecialModelNameDto.java
54+
src/main/java/org/openapitools/model/TagDto.java
55+
src/main/java/org/openapitools/model/TypeHolderDefaultDto.java
56+
src/main/java/org/openapitools/model/TypeHolderExampleDto.java
57+
src/main/java/org/openapitools/model/UserDto.java
58+
src/main/java/org/openapitools/model/XmlItemDto.java
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7.23.0-SNAPSHOT

0 commit comments

Comments
 (0)