-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathAuthenticationTokenGenerator.java
More file actions
193 lines (165 loc) · 7.09 KB
/
AuthenticationTokenGenerator.java
File metadata and controls
193 lines (165 loc) · 7.09 KB
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package com.yoti.auth;
import static com.yoti.validation.Validation.notNull;
import static com.yoti.validation.Validation.notNullOrEmpty;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyPair;
import java.time.OffsetDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import com.yoti.api.client.InitialisationException;
import com.yoti.api.client.KeyPairSource;
import com.yoti.api.client.spi.remote.KeyStreamVisitor;
import com.yoti.api.client.spi.remote.call.ResourceException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
/**
* The {@link AuthenticationTokenGenerator} is used for generation authorization tokens
* that can be used for accessing Yoti services. An authorization token must have
* a unique identifier, and an expiry timestamp. One or more scopes can be provided
* to allow the authorization token access to different parts of Yoti systems.
* <p>
* The {@link AuthenticationTokenGenerator.Builder} can be accessed via {@code AuthorizationTokenGenerator.builder()}
* method, and then configured via the fluent API.
*/
public class AuthenticationTokenGenerator {
private final String sdkId;
private final KeyPair keyPair;
private final Supplier<String> jwtIdSupplier;
private final FormRequestClient formRequestClient;
private final URL authApiUrl;
private final ObjectMapper objectMapper;
AuthenticationTokenGenerator(
String sdkId,
KeyPair keyPair,
Supplier<String> jwtIdSupplier,
FormRequestClient formRequestClient,
ObjectMapper objectMapper) {
this.sdkId = sdkId;
this.keyPair = keyPair;
this.jwtIdSupplier = jwtIdSupplier;
this.formRequestClient = formRequestClient;
this.objectMapper = objectMapper;
try {
authApiUrl = new URL(System.getProperty(Properties.PROPERTY_YOTI_AUTH_URL, Properties.DEFAULT_YOTI_AUTH_URL));
} catch (MalformedURLException e) {
throw new IllegalStateException("Invalid Yoti auth url", e);
}
}
/**
* Creates a new instance of {@link AuthenticationTokenGenerator.Builder}
*
* @return the builder
*/
public static AuthenticationTokenGenerator.Builder builder() {
return new AuthenticationTokenGenerator.Builder();
}
/**
* Creates a new authentication token, using the supplied scopes and comment.
*
* @param scopes a list of scopes to be used by the authentication token
* @return a {@link CreateAuthenticationTokenResponse} containing information about the created token.
* @throws ResourceException if something was incorrect with the request to the Yoti authentication service
* @throws IOException
*/
public CreateAuthenticationTokenResponse generate(List<String> scopes) throws ResourceException, IOException {
notNullOrEmpty(scopes, "scopes");
String jwts = createSignedJwt(sdkId, keyPair, jwtIdSupplier, authApiUrl);
Map<String, String> formParams = new HashMap<>();
formParams.put("grant_type", "client_credentials");
formParams.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
formParams.put("scope", String.join(" ", scopes));
formParams.put("client_assertion", jwts);
byte[] responseBody = formRequestClient.performRequest(authApiUrl, "POST", formParams);
return objectMapper.readValue(responseBody, CreateAuthenticationTokenResponse.class);
}
private String createSignedJwt(String sdkId, KeyPair keyPair, Supplier<String> jwtIdSupplier, URL authApiUrl) {
String sdkIdProperty = String.format("sdk:%s", sdkId);
OffsetDateTime now = OffsetDateTime.now();
return Jwts.builder()
.issuer(sdkIdProperty)
.subject(sdkIdProperty)
.id(jwtIdSupplier.get())
.audience()
.add(authApiUrl.toString())
.and()
.expiration(new Date(now.plusMinutes(5).toInstant().toEpochMilli()))
.issuedAt(new Date(now.toInstant().toEpochMilli()))
.header()
.add("alg", "PS384")
.add("typ", "JWT")
.and()
.signWith(keyPair.getPrivate(), Jwts.SIG.PS384)
.compact();
}
public static final class Builder {
private String sdkId;
private KeyPairSource keyPairSource;
private Supplier<String> jwtIdSupplier = () -> UUID.randomUUID().toString();
private Builder() {
}
/**
* Sets the SDK ID that the authorization token will be generated against.
*
* @param sdkId the SDK ID
* @return the builder for method chaining.
*/
public Builder withSdkId(String sdkId) {
this.sdkId = sdkId;
return this;
}
/**
* Sets the {@link KeyPairSource} that will be used to load the {@link KeyPair}
*
* @param keyPairSource the key pair source that will be used to load the {@link KeyPair}
* @return the builder for method chaining.
*/
public Builder withKeyPairSource(KeyPairSource keyPairSource) {
this.keyPairSource = keyPairSource;
return this;
}
/**
* Sets the supplier that will be used to generate a unique ID for the
* authorization token. By default, this will be a UUID v4.
*
* @param jwtIdSupplier the supplier used for generating authorization token ID
* @return the builder for method chaining.
*/
public Builder withJwtIdSupplier(Supplier<String> jwtIdSupplier) {
this.jwtIdSupplier = jwtIdSupplier;
return this;
}
/**
* Builds an {@link AuthenticationTokenGenerator} using the values supplied to the {@link Builder}.
*
* @return the configured {@link AuthenticationTokenGenerator}
*/
public AuthenticationTokenGenerator build() {
notNullOrEmpty(sdkId, "sdkId");
notNull(keyPairSource, "keyPairSource");
notNull(jwtIdSupplier, "jwtIdSupplier");
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return new AuthenticationTokenGenerator(
sdkId,
loadKeyPair(keyPairSource),
jwtIdSupplier,
new FormRequestClient(),
objectMapper
);
}
private KeyPair loadKeyPair(KeyPairSource kpSource) throws InitialisationException {
try {
return kpSource.getFromStream(new KeyStreamVisitor());
} catch (IOException e) {
throw new InitialisationException("Cannot load key pair", e);
}
}
}
}