27
27
import java .security .spec .InvalidKeySpecException ;
28
28
import java .security .spec .PKCS8EncodedKeySpec ;
29
29
import java .util .Date ;
30
- import java .util .HashMap ;
31
- import java .util .Map ;
30
+ import java .util .concurrent .ConcurrentHashMap ;
32
31
import java .util .concurrent .ExecutorService ;
33
32
import java .util .concurrent .Executors ;
33
+ import java .util .concurrent .Future ;
34
+ import java .util .concurrent .TimeUnit ;
35
+ import java .util .concurrent .locks .ReentrantLock ;
34
36
import java .util .regex .Matcher ;
35
37
import java .util .regex .Pattern ;
36
38
39
41
*/
40
42
public class ApplicationTokenCredentials extends AzureTokenCredentials {
41
43
/** A mapping from resource endpoint to its cached access token. */
42
- private Map <String , AuthenticationResult > tokens ;
44
+ private final ConcurrentHashMap <String , AuthenticationResult > tokens ;
45
+ /** A mapping from resource endpoint to its current authentication locks. */
46
+ private final ConcurrentHashMap <String , ReentrantLock > authenticationLocks ;
43
47
/** The active directory application client id. */
44
- private String clientId ;
48
+ private final String clientId ;
45
49
/** The authentication secret for the application. */
46
50
private String clientSecret ;
47
51
/** The PKCS12 certificate byte array. */
48
52
private byte [] clientCertificate ;
49
53
/** The certificate password. */
50
54
private String clientCertificatePassword ;
55
+ /** The timeout for authentication calls. */
56
+ private long timeoutInSeconds = 60 ;
51
57
52
58
/**
53
59
* Initializes a new instance of the ApplicationTokenCredentials.
@@ -62,7 +68,8 @@ public ApplicationTokenCredentials(String clientId, String domain, String secret
62
68
super (environment , domain ); // defer token acquisition
63
69
this .clientId = clientId ;
64
70
this .clientSecret = secret ;
65
- this .tokens = new HashMap <>();
71
+ this .tokens = new ConcurrentHashMap <>();
72
+ this .authenticationLocks = new ConcurrentHashMap <>();
66
73
}
67
74
68
75
/**
@@ -80,7 +87,8 @@ public ApplicationTokenCredentials(String clientId, String domain, byte[] certif
80
87
this .clientId = clientId ;
81
88
this .clientCertificate = certificate ;
82
89
this .clientCertificatePassword = password ;
83
- this .tokens = new HashMap <>();
90
+ this .tokens = new ConcurrentHashMap <>();
91
+ this .authenticationLocks = new ConcurrentHashMap <>();
84
92
}
85
93
86
94
/**
@@ -132,19 +140,61 @@ String clientCertificatePassword() {
132
140
return clientCertificatePassword ;
133
141
}
134
142
143
+ /**
144
+ * Gets the timeout for AAD authentication calls. Default is 60 seconds.
145
+ *
146
+ * @return the timeout in seconds.
147
+ */
148
+ public long timeoutInSeconds () {
149
+ return timeoutInSeconds ;
150
+ }
151
+
152
+ /**
153
+ * Sets the timeout for AAD authentication calls. Default is 60 seconds.
154
+ *
155
+ * @param timeoutInSeconds the timeout in seconds.
156
+ * @return the modified ApplicationTokenCredentials instance
157
+ */
158
+ public ApplicationTokenCredentials withTimeoutInSeconds (long timeoutInSeconds ) {
159
+ this .timeoutInSeconds = timeoutInSeconds ;
160
+ return this ;
161
+ }
162
+
135
163
@ Override
136
- public synchronized String getToken (String resource ) throws IOException {
164
+ public String getToken (String resource ) throws IOException {
137
165
AuthenticationResult authenticationResult = tokens .get (resource );
138
166
if (authenticationResult == null || authenticationResult .getExpiresOnDate ().before (new Date ())) {
139
- authenticationResult = acquireAccessToken (resource );
167
+ ReentrantLock lock ;
168
+ synchronized (authenticationLocks ) {
169
+ lock = authenticationLocks .get (resource );
170
+ if (lock == null ) {
171
+ lock = new ReentrantLock ();
172
+ authenticationLocks .put (resource , lock );
173
+ }
174
+ }
175
+ lock .lock ();
176
+ try {
177
+ authenticationResult = tokens .get (resource );
178
+ if (authenticationResult == null || authenticationResult .getExpiresOnDate ().before (new Date ())) {
179
+ ExecutorService executor = Executors .newSingleThreadExecutor ();
180
+ try {
181
+ authenticationResult = acquireAccessToken (resource , executor ).get (timeoutInSeconds (), TimeUnit .SECONDS );
182
+ tokens .put (resource , authenticationResult );
183
+ } finally {
184
+ executor .shutdown ();
185
+ }
186
+ }
187
+ } catch (Exception e ) {
188
+ throw new IOException (e .getMessage (), e );
189
+ } finally {
190
+ lock .unlock ();
191
+ }
140
192
}
141
- tokens .put (resource , authenticationResult );
142
193
return authenticationResult .getAccessToken ();
143
194
}
144
195
145
- private AuthenticationResult acquireAccessToken (String resource ) throws IOException {
196
+ Future < AuthenticationResult > acquireAccessToken (String resource , ExecutorService executor ) throws IOException {
146
197
String authorityUrl = this .environment ().activeDirectoryEndpoint () + this .domain ();
147
- ExecutorService executor = Executors .newSingleThreadExecutor ();
148
198
AuthenticationContext context = new AuthenticationContext (authorityUrl , false , executor );
149
199
if (proxy () != null ) {
150
200
context .setProxy (proxy ());
@@ -157,23 +207,21 @@ private AuthenticationResult acquireAccessToken(String resource) throws IOExcept
157
207
return context .acquireToken (
158
208
resource ,
159
209
new ClientCredential (this .clientId (), clientSecret ),
160
- null ). get () ;
210
+ null );
161
211
} else if (clientCertificate != null && clientCertificatePassword != null ) {
162
212
return context .acquireToken (
163
213
resource ,
164
214
AsymmetricKeyCredential .create (clientId , new ByteArrayInputStream (clientCertificate ), clientCertificatePassword ),
165
- null ). get () ;
215
+ null );
166
216
} else if (clientCertificate != null ) {
167
217
return context .acquireToken (
168
218
resource ,
169
219
AsymmetricKeyCredential .create (clientId (), privateKeyFromPem (new String (clientCertificate )), publicKeyFromPem (new String (clientCertificate ))),
170
- null ). get () ;
220
+ null );
171
221
}
172
222
throw new AuthenticationException ("Please provide either a non-null secret or a non-null certificate." );
173
223
} catch (Exception e ) {
174
224
throw new IOException (e .getMessage (), e );
175
- } finally {
176
- executor .shutdown ();
177
225
}
178
226
}
179
227
0 commit comments