View Javadoc
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 }