Skip to content

Commit 35387cb

Browse files
claudeng-galien
authored andcommitted
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
1 parent 8b6df51 commit 35387cb

5 files changed

Lines changed: 206 additions & 0 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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"

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public class SpringCodegen extends AbstractJavaCodegen
101101
public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation";
102102
public static final String USE_DEDUCTION_FOR_ONE_OF_INTERFACES = "useDeductionForOneOfInterfaces";
103103
public static final String SPRING_API_VERSION = "springApiVersion";
104+
public static final String CLIENT_REGISTRATION_ID = "clientRegistrationId";
104105

105106
@Getter
106107
public enum RequestMappingMode {
@@ -163,6 +164,8 @@ public enum RequestMappingMode {
163164
protected boolean useSpringBuiltInValidation = false;
164165
@Getter @Setter
165166
protected boolean useDeductionForOneOfInterfaces = false;
167+
@Getter @Setter
168+
protected String clientRegistrationId = null;
166169

167170
public SpringCodegen() {
168171
super();
@@ -288,6 +291,7 @@ public SpringCodegen() {
288291

289292
cliOptions.add(CliOption.newBoolean(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, "whether to use deduction for generated oneOf interfaces", useDeductionForOneOfInterfaces));
290293
cliOptions.add(CliOption.newString(SPRING_API_VERSION, "Value for 'version' attribute in @RequestMapping (for Spring 7 and above)."));
294+
cliOptions.add(CliOption.newString(CLIENT_REGISTRATION_ID, "Client registration ID for OAuth2 in Spring HTTP Interface (@ClientRegistrationId annotation)."));
291295
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
292296
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
293297
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
@@ -456,6 +460,7 @@ public void processOpts() {
456460
convertPropertyToBooleanAndWriteBack(OPTIONAL_ACCEPT_NULLABLE, this::setOptionalAcceptNullable);
457461
convertPropertyToBooleanAndWriteBack(USE_SPRING_BUILT_IN_VALIDATION, this::setUseSpringBuiltInValidation);
458462
convertPropertyToBooleanAndWriteBack(USE_DEDUCTION_FOR_ONE_OF_INTERFACES, this::setUseDeductionForOneOfInterfaces);
463+
convertPropertyToStringAndWriteBack(CLIENT_REGISTRATION_ID, this::setClientRegistrationId);
459464

460465
additionalProperties.put("springHttpStatus", new SpringHttpStatusLambda());
461466

@@ -795,6 +800,11 @@ public void setIsVoid(boolean isVoid) {
795800

796801
prepareVersioningParameters(ops);
797802
handleImplicitHeaders(operation);
803+
804+
// Add clientRegistrationId for spring-http-interface with OAuth
805+
if (SPRING_HTTP_INTERFACE.equals(library) && clientRegistrationId != null && !clientRegistrationId.isEmpty()) {
806+
operation.vendorExtensions.put("clientRegistrationId", clientRegistrationId);
807+
}
798808
}
799809
// The tag for the controller is the first tag of the first operation
800810
final CodegenOperation firstOperation = ops.get(0);

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+
{{#operations}}{{#operation}}{{#vendorExtensions.clientRegistrationId}}
19+
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId;
20+
{{/vendorExtensions.clientRegistrationId}}{{/operation}}{{/operations}}
1821
import org.springframework.web.multipart.MultipartFile;
1922
{{#reactive}}
2023

@@ -57,6 +60,9 @@ public interface {{classname}} {
5760
{{#isDeprecated}}
5861
@Deprecated
5962
{{/isDeprecated}}
63+
{{#vendorExtensions.clientRegistrationId}}
64+
@ClientRegistrationId("{{vendorExtensions.clientRegistrationId}}")
65+
{{/vendorExtensions.clientRegistrationId}}
6066
{{^useResponseEntity}}
6167
@ResponseStatus({{#springHttpStatus}}{{#responses.0}}{{{code}}}{{/responses.0}}{{/springHttpStatus}})
6268
{{/useResponseEntity}}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Spring HTTP Interface with OAuth2 (@ClientRegistrationId)
2+
3+
This sample demonstrates the use of the `@ClientRegistrationId` annotation with Spring HTTP Interface clients.
4+
5+
## Feature
6+
7+
When generating Spring HTTP Interface clients, you can now specify a `clientRegistrationId` parameter to automatically add the `@ClientRegistrationId` annotation to all generated interface methods.
8+
9+
## Configuration
10+
11+
Add the `clientRegistrationId` property to your generator configuration:
12+
13+
```yaml
14+
generatorName: spring
15+
library: spring-http-interface
16+
additionalProperties:
17+
clientRegistrationId: "petstore-oauth"
18+
```
19+
20+
Or via command line:
21+
22+
```bash
23+
openapi-generator-cli generate \
24+
-g spring \
25+
--library spring-http-interface \
26+
--additional-properties clientRegistrationId=petstore-oauth \
27+
-i petstore.yaml \
28+
-o ./output
29+
```
30+
31+
## Generated Code
32+
33+
The generated interface methods will include the `@ClientRegistrationId` annotation:
34+
35+
```java
36+
@ClientRegistrationId("petstore-oauth")
37+
@HttpExchange(
38+
method = "GET",
39+
value = "/pet/{petId}",
40+
accept = { "application/json" }
41+
)
42+
ResponseEntity<PetDto> getPetById(@PathVariable("petId") Long petId);
43+
```
44+
45+
## Spring Security Integration
46+
47+
This annotation is part of Spring Security's OAuth2 integration for HTTP Service Clients. It automatically associates OAuth2 tokens with HTTP requests.
48+
49+
### Requirements
50+
51+
- Spring Security 7.0+
52+
- Spring Boot 3.x
53+
54+
### Configuration
55+
56+
Configure your Spring application with the OAuth2 client registration:
57+
58+
```yaml
59+
spring:
60+
security:
61+
oauth2:
62+
client:
63+
registration:
64+
petstore-oauth:
65+
client-id: your-client-id
66+
client-secret: your-client-secret
67+
authorization-grant-type: client_credentials
68+
scope: read,write
69+
provider:
70+
petstore-oauth:
71+
token-uri: https://auth.example.com/oauth/token
72+
```
73+
74+
### Bean Configuration
75+
76+
Use `OAuth2RestClientHttpServiceGroupConfigurer` to configure the HTTP Service Proxy Factory:
77+
78+
```java
79+
@Configuration
80+
public class HttpInterfaceConfig {
81+
82+
@Bean
83+
public PetApi petApi(OAuth2RestClientHttpServiceGroupConfigurer configurer) {
84+
RestClient.Builder builder = RestClient.builder()
85+
.baseUrl("https://petstore.example.com/v2");
86+
87+
configurer.configure(builder);
88+
89+
RestClient restClient = builder.build();
90+
RestClientAdapter adapter = RestClientAdapter.create(restClient);
91+
HttpServiceProxyFactory factory = HttpServiceProxyFactory
92+
.builderFor(adapter)
93+
.build();
94+
95+
return factory.createClient(PetApi.class);
96+
}
97+
}
98+
```
99+
100+
## References
101+
102+
- [Spring Security HTTP Service Client Integration](https://docs.spring.io/spring-security/reference/features/integrations/rest/http-service-client.html)
103+
- [Spring Security ClientRegistrationId API](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/oauth2/client/annotation/ClientRegistrationId.html)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
3+
* https://openapi-generator.tech
4+
* Do not edit the class manually.
5+
*/
6+
package org.openapitools.api;
7+
8+
import org.openapitools.model.PetDto;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.web.bind.annotation.*;
11+
import org.springframework.web.service.annotation.*;
12+
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId;
13+
import org.springframework.web.multipart.MultipartFile;
14+
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.Optional;
18+
import jakarta.annotation.Generated;
19+
20+
21+
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
22+
23+
public interface PetApi {
24+
25+
/**
26+
* POST /pet : Add a new pet to the store
27+
*
28+
* @param petDto Pet object that needs to be added to the store (required)
29+
* @return successful operation (status code 200)
30+
*/
31+
@ClientRegistrationId("petstore-oauth")
32+
@HttpExchange(
33+
method = "POST",
34+
value = "/pet",
35+
accept = { "application/json", "application/xml" },
36+
contentType = "application/json"
37+
)
38+
ResponseEntity<Void> addPet(
39+
@RequestBody PetDto petDto
40+
);
41+
42+
/**
43+
* GET /pet/{petId} : Find pet by ID
44+
*
45+
* @param petId ID of pet to return (required)
46+
* @return successful operation (status code 200)
47+
*/
48+
@ClientRegistrationId("petstore-oauth")
49+
@HttpExchange(
50+
method = "GET",
51+
value = "/pet/{petId}",
52+
accept = { "application/json", "application/xml" }
53+
)
54+
ResponseEntity<PetDto> getPetById(
55+
@PathVariable("petId") Long petId
56+
);
57+
58+
/**
59+
* PUT /pet : Update an existing pet
60+
*
61+
* @param petDto Pet object that needs to be updated in the store (required)
62+
* @return successful operation (status code 200)
63+
*/
64+
@ClientRegistrationId("petstore-oauth")
65+
@HttpExchange(
66+
method = "PUT",
67+
value = "/pet",
68+
accept = { "application/json", "application/xml" },
69+
contentType = "application/json"
70+
)
71+
ResponseEntity<Void> updatePet(
72+
@RequestBody PetDto petDto
73+
);
74+
75+
}

0 commit comments

Comments
 (0)