In my team, we try to use a contract-first approach for our REST APIs.
With multiple microservices, we need to pass user token when we call another service.
In this article, I will show how to easily pass this token with a generated RestTemplate client and Springboot.
SetUp
You can download the base project on github.
It contains two servers.
The API
For this article I will use a simple CRUD API for ponies.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
---
openapi: "3.0.1"
info:
title: "My Little Pony"
description: "Friendship is magic"
contact: {}
version: "1.0.0"
paths:
/ponies:
summary: "Everything for ponies"
get:
summary: "List"
operationId: "list"
parameters:
- name: "name"
in: "query"
required: false
schema:
type: "string"
example: "Rainbow Dash"
responses:
"200":
description: "Status 200"
content:
application/json:
schema:
type: "array"
items:
$ref: "#/components/schemas/Pony"
"400":
description: "Status 400"
post:
summary: "Create"
operationId: "create"
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Pony"
responses:
"201":
description: "Status 201"
content:
application/json:
schema:
$ref: "#/components/schemas/Pony"
"400":
description: "Status 400"
/ponies/{ponyId}:
get:
summary: "Get one"
operationId: "getOne"
responses:
"200":
description: "Status 200"
content:
application/json:
schema:
$ref: "#/components/schemas/Pony"
put:
summary: "Update"
operationId: "update"
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Pony"
responses:
"200":
description: "Status 200"
content:
application/json:
schema:
$ref: "#/components/schemas/Pony"
delete:
summary: "Delete one"
operationId: "deleteOne"
responses:
"200":
description: "Status 200"
parameters:
- name: "ponyId"
in: "path"
required: true
schema:
type: "string"
components:
schemas:
Pony:
type: "object"
required:
- "Color"
- "Name"
properties:
Id:
type: "string"
Name:
type: "string"
minLength: 1
maxLength: 100
example: "Big McIntosh"
Color:
$ref: "#/components/schemas/Color"
CreatedAt:
type: "integer"
format: "int64"
description: "Timestamp"
Color:
type: "string"
enum:
- "RED"
- "RAINBOW"
- "BLUE"
example: "RED"
|
Front
It is the Authorization Server.
You can obtain a JWT token with this call :
1
2
3
4
5
6
|
curl --location --request POST 'localhost:8080/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0' \
--data-urlencode 'username=user1' \
--data-urlencode 'password=password' \
--data-urlencode 'grant_type=password'
|
This server will also contains the generated client.
Back
This one contains the generated server-side.
All endpoints required an authenticated connexion with a bearer token generated by the front.
Client
The client is generated with java/restTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${basedir}/src/main/resources/api.yml</inputSpec>
<generatorName>java</generatorName>
<library>resttemplate</library>
<configOptions>
<sourceFolder>src/gen/java/main</sourceFolder>
<dateLibrary>java8</dateLibrary>
</configOptions>
<modelPackage>bzh.zomzog.demo.openapispringclienttoken.front.domain.api</modelPackage>
<apiPackage>bzh.zomzog.demo.openapispringclienttoken.front.controller.api</apiPackage>
<generateSupportingFiles>true</generateSupportingFiles>
<generateApiTests>false</generateApiTests>
<generateModelTests>false</generateModelTests>
</configuration>
</execution>
</executions>
</plugin>
|
This generator creates Spring @Component
for each API and a common ApiClient
.
This ApiClient required a RestTemplate
bean for spring injection,
so we will modify this part for adding authentification to external calls.
By default, spring-web provides a predefined RestTemplateBuilder
so we just need to add an interceptor to it.
We just need to extract the token from SecurityContextHolder
and add it to the headers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.additionalInterceptors((httpRequest, bytes, clientHttpRequestExecution) -> {
Object details = SecurityContextHolder.getContext().getAuthentication().getDetails();
if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oauthsDetails = (OAuth2AuthenticationDetails) details;
String type = oauthsDetails.getTokenType();
String token = oauthsDetails.getTokenValue();
httpRequest.getHeaders().add(HttpHeaders.AUTHORIZATION, type + " " + token);
} else {
throw new RuntimeException("Not oauth2");
}
return clientHttpRequestExecution.execute(httpRequest, bytes);
}).build();
}
|
ApiClient basePath can set with a @PostConstruct
1
2
3
4
5
6
|
private final ApiClient apiClient;
@PostConstruct
public void config(){
apiClient.setBasePath("http://localhost:8081");
}
|
Conclusion
With this configuration, each calls with generated clients will contains the token.
If multiple clients need to be generated the ApiClient can be generated separately and mutualized.
Source code example can be found on Github