2727import java .security .spec .InvalidKeySpecException ;
2828import java .security .spec .PKCS8EncodedKeySpec ;
2929import java .util .Date ;
30- import java .util .HashMap ;
31- import java .util .Map ;
30+ import java .util .concurrent .ConcurrentHashMap ;
3231import java .util .concurrent .ExecutorService ;
3332import java .util .concurrent .Executors ;
33+ import java .util .concurrent .Future ;
34+ import java .util .concurrent .TimeUnit ;
35+ import java .util .concurrent .locks .ReentrantLock ;
3436import java .util .regex .Matcher ;
3537import java .util .regex .Pattern ;
3638
3941 */
4042public class ApplicationTokenCredentials extends AzureTokenCredentials {
4143 /** 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 ;
4347 /** The active directory application client id. */
44- private String clientId ;
48+ private final String clientId ;
4549 /** The authentication secret for the application. */
4650 private String clientSecret ;
4751 /** The PKCS12 certificate byte array. */
4852 private byte [] clientCertificate ;
4953 /** The certificate password. */
5054 private String clientCertificatePassword ;
55+ /** The timeout for authentication calls. */
56+ private long timeoutInSeconds = 60 ;
5157
5258 /**
5359 * Initializes a new instance of the ApplicationTokenCredentials.
@@ -62,7 +68,8 @@ public ApplicationTokenCredentials(String clientId, String domain, String secret
6268 super (environment , domain ); // defer token acquisition
6369 this .clientId = clientId ;
6470 this .clientSecret = secret ;
65- this .tokens = new HashMap <>();
71+ this .tokens = new ConcurrentHashMap <>();
72+ this .authenticationLocks = new ConcurrentHashMap <>();
6673 }
6774
6875 /**
@@ -80,7 +87,8 @@ public ApplicationTokenCredentials(String clientId, String domain, byte[] certif
8087 this .clientId = clientId ;
8188 this .clientCertificate = certificate ;
8289 this .clientCertificatePassword = password ;
83- this .tokens = new HashMap <>();
90+ this .tokens = new ConcurrentHashMap <>();
91+ this .authenticationLocks = new ConcurrentHashMap <>();
8492 }
8593
8694 /**
@@ -132,19 +140,61 @@ String clientCertificatePassword() {
132140 return clientCertificatePassword ;
133141 }
134142
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+
135163 @ Override
136- public synchronized String getToken (String resource ) throws IOException {
164+ public String getToken (String resource ) throws IOException {
137165 AuthenticationResult authenticationResult = tokens .get (resource );
138166 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+ }
140192 }
141- tokens .put (resource , authenticationResult );
142193 return authenticationResult .getAccessToken ();
143194 }
144195
145- private AuthenticationResult acquireAccessToken (String resource ) throws IOException {
196+ Future < AuthenticationResult > acquireAccessToken (String resource , ExecutorService executor ) throws IOException {
146197 String authorityUrl = this .environment ().activeDirectoryEndpoint () + this .domain ();
147- ExecutorService executor = Executors .newSingleThreadExecutor ();
148198 AuthenticationContext context = new AuthenticationContext (authorityUrl , false , executor );
149199 if (proxy () != null ) {
150200 context .setProxy (proxy ());
@@ -157,23 +207,21 @@ private AuthenticationResult acquireAccessToken(String resource) throws IOExcept
157207 return context .acquireToken (
158208 resource ,
159209 new ClientCredential (this .clientId (), clientSecret ),
160- null ). get () ;
210+ null );
161211 } else if (clientCertificate != null && clientCertificatePassword != null ) {
162212 return context .acquireToken (
163213 resource ,
164214 AsymmetricKeyCredential .create (clientId , new ByteArrayInputStream (clientCertificate ), clientCertificatePassword ),
165- null ). get () ;
215+ null );
166216 } else if (clientCertificate != null ) {
167217 return context .acquireToken (
168218 resource ,
169219 AsymmetricKeyCredential .create (clientId (), privateKeyFromPem (new String (clientCertificate )), publicKeyFromPem (new String (clientCertificate ))),
170- null ). get () ;
220+ null );
171221 }
172222 throw new AuthenticationException ("Please provide either a non-null secret or a non-null certificate." );
173223 } catch (Exception e ) {
174224 throw new IOException (e .getMessage (), e );
175- } finally {
176- executor .shutdown ();
177225 }
178226 }
179227
0 commit comments