View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.security.jwt;
23  
24  import static com.google.common.base.Preconditions.checkArgument;
25  import static com.google.common.base.Strings.isNullOrEmpty;
26  
27  import com.nimbusds.jose.KeySourceException;
28  import com.nimbusds.jose.jwk.JWK;
29  import com.nimbusds.jose.jwk.JWKMatcher;
30  import com.nimbusds.jose.jwk.JWKSelector;
31  import com.nimbusds.jose.jwk.source.JWKSource;
32  import com.nimbusds.jose.jwk.source.JWKSourceBuilder;
33  import com.nimbusds.jose.proc.SecurityContext;
34  
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import java.net.MalformedURLException;
39  import java.net.URI;
40  import java.net.URISyntaxException;
41  import java.net.URL;
42  import java.util.Collections;
43  import java.util.List;
44  
45  /**
46   * JWK provider that fetches and caches jwk sets
47   */
48  public class JWKSetProvider {
49  
50    /** Logging facility. */
51    private static final Logger logger = LoggerFactory.getLogger(JWKSetProvider.class);
52  
53    private JWKSource<SecurityContext> jwkSource;
54    private JWKSelector selector;
55  
56    /**
57     * Creates a new cached provider from a JWKs URL.
58     *
59     * @param jwksUrl The URL where JWKs are published.
60     * @param ttl time-to-live in milliseconds
61     * @param refreshTimeout in milliseconds
62     */
63    public JWKSetProvider(String jwksUrl, long ttl, long refreshTimeout) {
64      URL url = urlFromString(jwksUrl);
65  
66      selector = new JWKSelector(
67          new JWKMatcher.Builder()
68              .build());
69  
70      jwkSource = JWKSourceBuilder.create(url)
71          .cache(ttl, refreshTimeout)
72          .retrying(true)
73          .build();
74    }
75  
76    /**
77     * Converts a URL string into a {@link URL}.
78     *
79     * @param url The URL string.
80     * @return The {@link URL}.
81     */
82    private static URL urlFromString(String url) {
83      checkArgument(!isNullOrEmpty(url), "A URL is required");
84      try {
85        final URI uri = new URI(url).normalize();
86        return uri.toURL();
87      } catch (MalformedURLException | URISyntaxException e) {
88        throw new IllegalArgumentException("Invalid JWKS URI", e);
89      }
90    }
91  
92    /**
93     * Getter for all JWKs.
94     *
95     * @return The JWKs.
96     */
97    public List<JWK> getAll() {
98      try {
99        return jwkSource.get(selector, null);
100     } catch (KeySourceException e) {
101       logger.error("Error while loading from JWKS cache: " + e.getMessage());
102       return Collections.emptyList();
103     }
104   }
105 }