1 /*
2 * Copyright (c) 2002-2018, Mairie de Paris
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright notice
10 * and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright notice
13 * and the following disclaimer in the documentation and/or other materials
14 * provided with the distribution.
15 *
16 * 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *
32 * License 1.0
33 */
34 package fr.paris.lutece.util.jwt.service;
35
36 import io.jsonwebtoken.Claims;
37 import io.jsonwebtoken.JwtBuilder;
38 import io.jsonwebtoken.JwtException;
39 import io.jsonwebtoken.Jwts;
40 import io.jsonwebtoken.SignatureAlgorithm;
41 import java.io.UnsupportedEncodingException;
42 import java.security.Key;
43 import java.time.Instant;
44 import java.util.Date;
45 import java.util.Enumeration;
46 import java.util.Map;
47 import java.util.Map.Entry;
48 import javax.crypto.spec.SecretKeySpec;
49 import javax.servlet.http.HttpServletRequest;
50 import org.apache.logging.log4j.LogManager;
51 import org.apache.logging.log4j.Logger;
52
53
54 /**
55 * Utils class for JWT
56 */
57 public class JWTUtil
58 {
59 protected static final Logger LOGGER = LogManager.getLogger( "lutece.security.jwt" );
60
61 /**
62 * Check if provided request contains a JWT
63 *
64 * @param request
65 * @param strHeaderName
66 * @return true if the request contains a JWT, false othewise
67 */
68 public static boolean containsValidUnsafeJWT( HttpServletRequest request, String strHeaderName )
69 {
70 String strBase64JWT = request.getHeader( strHeaderName );
71
72 // If no specific Header is provided, use spec JWT : try to fetch in Authorization: Bearer HTTP Header
73 if ( strBase64JWT == null )
74 {
75 strBase64JWT = getAuthozirationBearerValue( request );
76 }
77
78 if ( strBase64JWT != null )
79 {
80 strBase64JWT = removeSignature( strBase64JWT );
81 try
82 {
83 Jwts.parser( ).parseClaimsJwt( strBase64JWT );
84 return true;
85 }
86 catch( JwtException e )
87 {
88 LOGGER.error( "Provided request doesn't contains any JWT in HTTP headers ", e );
89 }
90 }
91 return false;
92 }
93
94 /**
95 * Checks claims key/value inside the JWT payload
96 *
97 * @param request
98 * @param strHeaderName
99 * @param claimsToCheck
100 * @return true if the key/values are present, false otherwise
101 */
102 public static boolean checkPayloadValues( HttpServletRequest request, String strHeaderName, Map<String, String> claimsToCheck )
103 {
104 String strBase64JWT = request.getHeader( strHeaderName );
105
106 // If no specific Header is provided, use spec JWT : try to fetch in Authorization: Bearer HTTP Header
107 if ( strBase64JWT == null )
108 {
109 strBase64JWT = getAuthozirationBearerValue( request );
110 }
111
112 if ( strBase64JWT != null )
113 {
114 strBase64JWT = removeSignature( strBase64JWT );
115 try
116 {
117 Claims claims = Jwts.parser( ).parseClaimsJwt( strBase64JWT ).getBody( );
118
119 for ( Entry<String, String> entry : claimsToCheck.entrySet( ) )
120 {
121 if ( !claims.get( entry.getKey( ), String.class ).equals( entry.getValue( ) ) )
122 {
123 return false;
124 }
125 }
126 }
127 catch( Exception e )
128 {
129 LOGGER.error( "Unable to check JWT payload for checking claims", e );
130 return false;
131 }
132 }
133 return true;
134 }
135
136 /**
137 * Get a payload value with given claimName;
138 *
139 * @param request
140 * @param strHeaderName
141 * @param strClaimName
142 * @return true if the key/values are present, false otherwise
143 */
144 public static String getPayloadValue( HttpServletRequest request, String strHeaderName, String strClaimName )
145 {
146 String strBase64JWT = request.getHeader( strHeaderName );
147
148 // If no specific Header is provided, use spec JWT : try to fetch in Authorization: Bearer HTTP Header
149 if ( strBase64JWT == null )
150 {
151 strBase64JWT = getAuthozirationBearerValue( request );
152 }
153
154 getPayloadValue( strBase64JWT, strClaimName );
155 return null;
156 }
157
158 /**
159 * Get a payload value with given claimName;
160 *
161 * @param strBase64JWT
162 * @param strClaimName
163 * @return true if the key/values are present, false otherwise
164 */
165 public static String getPayloadValue( String strBase64JWT, String strClaimName )
166 {
167 if ( strBase64JWT != null && !strBase64JWT.isEmpty( ) )
168 {
169 strBase64JWT = removeSignature( strBase64JWT );
170 try
171 {
172 Claims claims = Jwts.parser( ).parseClaimsJwt( strBase64JWT ).getBody( );
173
174 return (String) claims.get( strClaimName );
175 }
176 catch( Exception e )
177 {
178 LOGGER.error( "Unable to get JWT Payload value", e );
179 }
180 }
181 return null;
182 }
183
184 /**
185 * Check the JWT signature with provided java security Key: this can be a RSA Public Key
186 *
187 * @param request
188 * The request
189 * @param strHeaderName
190 * The header name
191 * @param key
192 * The key
193 * @return true if the signature of the JWT is checked; false otherwise
194 */
195 public static boolean checkSignature( HttpServletRequest request, String strHeaderName, Key key )
196 {
197 String strBase64JWT = request.getHeader( strHeaderName );
198
199 // If no specific Header is provided, use spec JWT : try to fetch in Authorization: Bearer HTTP Header
200 if ( strBase64JWT == null )
201 {
202 strBase64JWT = getAuthozirationBearerValue( request );
203 }
204
205 return checkSignature( strBase64JWT, key );
206 }
207
208
209 /**
210 * Check the signature of the JWT with a secret key
211 *
212 * @param mapClaims
213 * The map of claims
214 * @param expirationDate
215 * The expiration date
216 * @param strAlgo
217 * The algorythm name
218 * @param key
219 * The key
220 * @return true if the signature is checked, false otherwise
221 */
222 public static String buildBase64JWT( Map<String,String> mapClaims, Date expirationDate, String strAlgo, Key key )
223 {
224 JwtBuilder builder = Jwts.builder();
225
226 builder.setIssuedAt( Date.from(Instant.now( ) ) );
227
228 //Set claims
229 for ( Entry<String,String> entry : mapClaims.entrySet( ) )
230 {
231 builder.claim( entry.getKey( ), entry.getValue( ) );
232 }
233
234 if ( expirationDate != null )
235 {
236 builder.setExpiration( expirationDate );
237 }
238
239
240 if ( key != null )
241 {
242 SignatureAlgorithm algo = SignatureAlgorithm.valueOf( strAlgo );
243 if ( algo != null )
244 {
245 builder.signWith( algo, key );
246 }
247 }
248
249 return builder.compact();
250 }
251
252 /**
253 * Get a java security Key from a String secreyKey and algorythm name
254 * @param strSecretKey
255 * The secret Key
256 * @param strAlgoName
257 * The algorythm name
258 * @return The java securitySecretKey
259 */
260 public static Key getKey( String strSecretKey, String strAlgoName )
261 {
262 try
263 {
264 Key key = new SecretKeySpec( strSecretKey.getBytes( "UTF-8"), strAlgoName );
265 return key;
266 }
267 catch ( UnsupportedEncodingException e )
268 {
269 }
270 return null;
271 }
272
273 /*
274 * PRIVATE METHODS
275 */
276 /**
277 * Get the Authorization Bearer value : "Authorization: Bearer XXXXXX" => exract XXXXX
278 *
279 * @param request
280 * The request
281 * @return the Authorization Bearer value in the request
282 */
283 private static String getAuthozirationBearerValue( HttpServletRequest request )
284 {
285 Enumeration<String> headers = request.getHeaders( "Authorization" );
286 while ( headers.hasMoreElements( ) )
287 {
288 String value = headers.nextElement( );
289 if ( value.toLowerCase( ).startsWith( "bearer" ) )
290 {
291 return value.substring( "bearer".length( ) ).trim( );
292 }
293 }
294 return null;
295 }
296
297 /**
298 * Check a JWT signature with a Java security Key
299 *
300 * @param strBase64JWT
301 * @param key
302 * @return true if the JWT is checked, false otherwise
303 */
304 public static boolean checkSignature( String strBase64JWT, Key key )
305 {
306 try
307 {
308 Jwts.parser( ).setSigningKey( key ).parseClaimsJws( strBase64JWT );
309 }
310
311 catch( JwtException e )
312 {
313 return false;
314 }
315 return true;
316 }
317
318 /**
319 * Remove a signature from a base64 JWT string
320 *
321 * @param strBase64JWT
322 * @return the JWT without signature
323 */
324 private static String removeSignature( String strBase64JWT )
325 {
326 int i = strBase64JWT.lastIndexOf( "." );
327 return strBase64JWT.substring( 0, i + 1 );
328 }
329 }