001/******************************************************************************* 002 * Copyright 2014 The MITRE Corporation 003 * and the MIT Kerberos and Internet Trust Consortium 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 ******************************************************************************/ 017/** 018 * 019 */ 020package org.mitre.jwt.signer.service.impl; 021 022import java.util.concurrent.ExecutionException; 023import java.util.concurrent.TimeUnit; 024 025import org.apache.http.client.HttpClient; 026import org.apache.http.impl.client.HttpClientBuilder; 027import org.mitre.jose.keystore.JWKSetKeyStore; 028import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService; 029import org.mitre.jwt.encryption.service.impl.DefaultJwtEncryptionAndDecryptionService; 030import org.mitre.jwt.signer.service.JwtSigningAndValidationService; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 034import org.springframework.stereotype.Service; 035import org.springframework.web.client.RestTemplate; 036 037import com.google.common.cache.CacheBuilder; 038import com.google.common.cache.CacheLoader; 039import com.google.common.cache.LoadingCache; 040import com.google.common.util.concurrent.UncheckedExecutionException; 041import com.nimbusds.jose.jwk.JWKSet; 042 043/** 044 * 045 * Creates a caching map of JOSE signers/validators and encrypters/decryptors 046 * keyed on the JWK Set URI. Dynamically loads JWK Sets to create the services. 047 * 048 * @author jricher 049 * 050 */ 051@Service 052public class JWKSetCacheService { 053 054 private static Logger logger = LoggerFactory.getLogger(JWKSetCacheService.class); 055 056 // map of jwk set uri -> signing/validation service built on the keys found in that jwk set 057 private LoadingCache<String, JwtSigningAndValidationService> validators; 058 059 // map of jwk set uri -> encryption/decryption service built on the keys found in that jwk set 060 private LoadingCache<String, JwtEncryptionAndDecryptionService> encrypters; 061 062 public JWKSetCacheService() { 063 this.validators = CacheBuilder.newBuilder() 064 .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch 065 .maximumSize(100) 066 .build(new JWKSetVerifierFetcher()); 067 this.encrypters = CacheBuilder.newBuilder() 068 .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch 069 .maximumSize(100) 070 .build(new JWKSetEncryptorFetcher()); 071 } 072 073 /** 074 * @param jwksUri 075 * @return 076 * @throws ExecutionException 077 * @see com.google.common.cache.Cache#get(java.lang.Object) 078 */ 079 public JwtSigningAndValidationService getValidator(String jwksUri) { 080 try { 081 return validators.get(jwksUri); 082 } catch (UncheckedExecutionException ue) { 083 logger.warn("Couldn't load JWK Set from " + jwksUri, ue); 084 return null; 085 } catch (ExecutionException e) { 086 logger.warn("Couldn't load JWK Set from " + jwksUri, e); 087 return null; 088 } 089 } 090 091 public JwtEncryptionAndDecryptionService getEncrypter(String jwksUri) { 092 try { 093 return encrypters.get(jwksUri); 094 } catch (UncheckedExecutionException ue) { 095 logger.warn("Couldn't load JWK Set from " + jwksUri, ue); 096 return null; 097 } catch (ExecutionException e) { 098 logger.warn("Couldn't load JWK Set from " + jwksUri, e); 099 return null; 100 } 101 } 102 103 /** 104 * @author jricher 105 * 106 */ 107 private class JWKSetVerifierFetcher extends CacheLoader<String, JwtSigningAndValidationService> { 108 private HttpClient httpClient = HttpClientBuilder.create() 109 .useSystemProperties() 110 .build(); 111 private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); 112 private RestTemplate restTemplate = new RestTemplate(httpFactory); 113 114 /** 115 * Load the JWK Set and build the appropriate signing service. 116 */ 117 @Override 118 public JwtSigningAndValidationService load(String key) throws Exception { 119 120 String jsonString = restTemplate.getForObject(key, String.class); 121 JWKSet jwkSet = JWKSet.parse(jsonString); 122 123 JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet); 124 125 JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keyStore); 126 127 return service; 128 129 } 130 131 } 132 133 /** 134 * @author jricher 135 * 136 */ 137 private class JWKSetEncryptorFetcher extends CacheLoader<String, JwtEncryptionAndDecryptionService> { 138 private HttpClient httpClient = HttpClientBuilder.create() 139 .useSystemProperties() 140 .build(); 141 private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); 142 private RestTemplate restTemplate = new RestTemplate(httpFactory); 143 /* (non-Javadoc) 144 * @see com.google.common.cache.CacheLoader#load(java.lang.Object) 145 */ 146 @Override 147 public JwtEncryptionAndDecryptionService load(String key) throws Exception { 148 String jsonString = restTemplate.getForObject(key, String.class); 149 JWKSet jwkSet = JWKSet.parse(jsonString); 150 151 JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet); 152 153 JwtEncryptionAndDecryptionService service = new DefaultJwtEncryptionAndDecryptionService(keyStore); 154 155 return service; 156 } 157 } 158 159}