View Javadoc
1   /*
2    * Copyright 2020 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.security.authentication;
18  
19  import com.nimbusds.jwt.EncryptedJWT;
20  import com.nimbusds.jwt.JWT;
21  import com.nimbusds.jwt.PlainJWT;
22  import com.nimbusds.jwt.SignedJWT;
23  import java.text.ParseException;
24  import java.time.Duration;
25  import java.util.Date;
26  import java.util.Objects;
27  import java.util.Optional;
28  import javax.validation.constraints.NotNull;
29  import org.bremersee.exception.ServiceException;
30  import org.springframework.cache.Cache;
31  import org.springframework.validation.annotation.Validated;
32  
33  /**
34   * The access token cache interface.
35   *
36   * @author Christian Bremer
37   */
38  @Validated
39  public interface AccessTokenCache {
40  
41    /**
42     * The constant CACHE_NAME.
43     */
44    String CACHE_NAME = "jwt";
45  
46    /**
47     * Find not expired access token from cache.
48     *
49     * @param key the key
50     * @return the access token
51     */
52    Optional<String> findAccessToken(@NotNull String key);
53  
54    /**
55     * Put new access token into the cache.
56     *
57     * @param key the key
58     * @param accessToken the access token
59     */
60    void putAccessToken(@NotNull String key, @NotNull String accessToken);
61  
62    /**
63     * Checks whether the access token is expired. If no expiration claim is present, the result will always be {@code
64     * true}.
65     *
66     * @param tokenValue the token value
67     * @param accessTokenThreshold the access token threshold
68     * @return the boolean
69     */
70    static boolean isExpired(@NotNull String tokenValue, Duration accessTokenThreshold) {
71      Duration duration = Objects
72          .requireNonNullElseGet(accessTokenThreshold, () -> Duration.ofSeconds(20L));
73      long millis = System.currentTimeMillis() + duration.toMillis();
74      return Optional.ofNullable(getExpirationTime(tokenValue))
75          .map(date -> date.getTime() < millis)
76          .orElse(true);
77    }
78  
79    /**
80     * Gets expiration time.
81     *
82     * @param tokenValue the token value
83     * @return the expiration time or {@code null} if there is no expiration claim
84     */
85    static Date getExpirationTime(@NotNull String tokenValue) {
86      JWT jwt = parse(tokenValue);
87      try {
88        if (jwt.getJWTClaimsSet() != null
89            && jwt.getJWTClaimsSet().getExpirationTime() != null) {
90          return jwt.getJWTClaimsSet().getExpirationTime();
91        }
92  
93      } catch (ParseException e) {
94        // ignored
95      }
96      return null;
97    }
98  
99    /**
100    * Parse jwt.
101    *
102    * @param tokenValue the token value
103    * @return the jwt
104    */
105   static JWT parse(@NotNull String tokenValue) {
106     try {
107       return SignedJWT.parse(tokenValue);
108     } catch (Exception e0) {
109       try {
110         return EncryptedJWT.parse(tokenValue);
111       } catch (Exception e1) {
112         try {
113           return PlainJWT.parse(tokenValue);
114         } catch (Exception e2) {
115           throw ServiceException.internalServerError("Parsing jwt failed.");
116         }
117       }
118     }
119   }
120 
121   /**
122    * Creates a new builder.
123    *
124    * @return the builder
125    */
126   static Builder builder() {
127     return new Builder.Impl();
128   }
129 
130   /**
131    * The builder interface.
132    */
133   interface Builder {
134 
135     /**
136      * With external cache.
137      *
138      * @param externalCache the external cache
139      * @return the builder
140      */
141     Builder withExternalCache(Cache externalCache);
142 
143     /**
144      * With expiration time threshold.
145      *
146      * @param duration the duration
147      * @return the builder
148      */
149     Builder withExpirationTimeThreshold(Duration duration);
150 
151     /**
152      * With key prefix.
153      *
154      * @param keyPrefix the key prefix
155      * @return the builder
156      */
157     Builder withKeyPrefix(String keyPrefix);
158 
159     /**
160      * Build access token cache.
161      *
162      * @return the access token cache
163      */
164     AccessTokenCache build();
165 
166     /**
167      * The builder implementation.
168      */
169     class Impl implements Builder {
170 
171       private Cache externalCache;
172 
173       private Duration expirationTimeThreshold;
174 
175       private String keyPrefix;
176 
177       @Override
178       public Builder withExternalCache(Cache externalCache) {
179         this.externalCache = externalCache;
180         return this;
181       }
182 
183       @Override
184       public Builder withExpirationTimeThreshold(Duration duration) {
185         this.expirationTimeThreshold = duration;
186         return this;
187       }
188 
189       @Override
190       public Builder withKeyPrefix(String keyPrefix) {
191         this.keyPrefix = keyPrefix;
192         return this;
193       }
194 
195       @Override
196       public AccessTokenCache build() {
197         return new AccessTokenCacheImpl(externalCache, expirationTimeThreshold, keyPrefix);
198       }
199     }
200   }
201 }