1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.plugins.oauth2.jwt;
35
36 import java.nio.charset.StandardCharsets;
37 import java.util.Map;
38 import java.util.concurrent.ConcurrentHashMap;
39
40 import org.apache.log4j.Logger;
41
42 import com.fasterxml.jackson.core.JsonProcessingException;
43 import com.fasterxml.jackson.databind.ObjectMapper;
44
45 import fr.paris.lutece.plugins.oauth2.business.AuthClientConf;
46 import fr.paris.lutece.plugins.oauth2.business.AuthServerConf;
47 import fr.paris.lutece.plugins.oauth2.business.IDToken;
48 import fr.paris.lutece.plugins.oauth2.business.Token;
49 import fr.paris.lutece.plugins.oauth2.service.CachingHttpAccessService;
50 import fr.paris.lutece.plugins.oauth2.web.Constants;
51 import fr.paris.lutece.util.httpaccess.HttpAccess;
52 import fr.paris.lutece.util.httpaccess.HttpAccessService;
53 import fr.paris.lutece.util.httpaccess.PropertiesHttpClientConfiguration;
54 import io.jsonwebtoken.Claims;
55 import io.jsonwebtoken.ExpiredJwtException;
56 import io.jsonwebtoken.Header;
57 import io.jsonwebtoken.Jws;
58 import io.jsonwebtoken.Jwt;
59 import io.jsonwebtoken.JwtParser;
60 import io.jsonwebtoken.JwtParserBuilder;
61 import io.jsonwebtoken.Jwts;
62 import io.jsonwebtoken.MalformedJwtException;
63 import io.jsonwebtoken.UnsupportedJwtException;
64 import io.jsonwebtoken.security.Keys;
65 import io.jsonwebtoken.security.SignatureException;
66
67
68
69
70 public class JjwtJWTParser implements JWTParser
71 {
72 private final Map<String, KeyLocator> _keyLocatorsMap = new ConcurrentHashMap<>( );
73 private final HttpAccess _httpAccess;
74
75 public JjwtJWTParser( )
76 {
77 HttpAccessService accessService = new CachingHttpAccessService( new PropertiesHttpClientConfiguration( ) );
78 this._httpAccess = new HttpAccess( accessService );
79 }
80
81 private KeyLocator getKeyLocator( String strwksEndpointUri )
82 {
83 return _keyLocatorsMap.computeIfAbsent( strwksEndpointUri, uri -> new KeyLocator( uri, _httpAccess ) );
84 }
85
86
87
88
89 @Override
90 public void parseJWT( Token token, AuthClientConf clientConfig, AuthServerConf serverConfig, String strStoredNonce, Logger logger )
91 throws TokenValidationException
92 {
93 String strCompactJwt = token.getIdTokenString( );
94
95 try
96 {
97 Claims claims = getClaims( strCompactJwt, clientConfig, serverConfig );
98
99 IDTokenoauth2/business/IDToken.html#IDToken">IDToken idToken = new IDToken( );
100 idToken.setAudience( claims.getAudience( ) );
101 idToken.setIssuer( claims.getIssuer( ) );
102 idToken.setSubject( claims.getSubject( ) );
103
104
105 idToken.setNonce( getVerifiedNonce( claims, strStoredNonce ) );
106 idToken.setExpiration( getExpiration( claims ) );
107 idToken.setIssueAt( getIssueAt( claims ) );
108
109
110 idToken.setIdProvider( (String) claims.get( Constants.CLAIM_IDP ) );
111 idToken.setAcr( (String) claims.get( Constants.CLAIM_ACR ) );
112
113 logger.debug( "ID Token retrieved by JJWT parser implementation : " + idToken );
114
115 token.setIdToken( idToken );
116 }
117 catch( SignatureException ex )
118 {
119 throw new TokenValidationException( ex.getMessage( ), ex );
120 }
121 catch( ExpiredJwtException ex )
122 {
123 throw new TokenValidationException( ex.getMessage( ), ex );
124 }
125 catch( IllegalArgumentException ex )
126 {
127 throw new TokenValidationException( ex.getMessage( ), ex );
128 }
129 catch( MalformedJwtException ex )
130 {
131 throw new TokenValidationException( ex.getMessage( ), ex );
132 }
133 catch( UnsupportedJwtException ex )
134 {
135 throw new TokenValidationException( ex.getMessage( ), ex );
136 }
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150 private String getVerifiedNonce( Claims claims, String strStoredNonce ) throws TokenValidationException
151 {
152
153 String strNonce = (String) claims.get( Constants.CLAIM_NONCE );
154
155 if ( strNonce == null )
156 {
157 throw new TokenValidationException( "The token doesn't contains the nonce info." );
158 }
159
160 if ( !strNonce.equals( strStoredNonce ) )
161 {
162 throw new TokenValidationException( "The nonce info has not the value expected." );
163 }
164
165 return strNonce;
166 }
167
168
169
170
171
172
173
174
175 private String getExpiration( Claims claims )
176 {
177 long lExpiration = claims.getExpiration( ).getTime( );
178
179 return String.valueOf( lExpiration / 1000L );
180 }
181
182
183
184
185
186
187
188
189 private String getIssueAt( Claims claims )
190 {
191 long lIssueAt = claims.getIssuedAt( ).getTime( );
192
193 return String.valueOf( lIssueAt / 1000L );
194 }
195
196 @Override
197 public String parseJWT( String strJwt, AuthClientConf clientConfig, AuthServerConf serverConfig, Logger logger ) throws TokenValidationException
198 {
199 String strClaims;
200
201 try
202 {
203 Claims claims = getClaims( strJwt, clientConfig, serverConfig );
204 strClaims = new ObjectMapper( ).writeValueAsString( claims );
205 } catch ( TokenValidationException | JsonProcessingException e )
206 {
207 throw new TokenValidationException( e.getMessage( ), e );
208 }
209
210 return strClaims;
211 }
212
213
214
215
216
217
218
219
220
221 private Claims getClaims ( String strCompactJwt, AuthClientConf clientConfig, AuthServerConf serverConfig) throws TokenValidationException
222 {
223 JwtParserBuilder parserBuilder = Jwts.parser( );
224
225 if ( serverConfig.getJwksEndpointUri( ) != null )
226 {
227 parserBuilder.keyLocator( getKeyLocator( serverConfig.getJwksEndpointUri( ) ) );
228 }
229 else
230 {
231 parserBuilder.verifyWith( Keys.hmacShaKeyFor( clientConfig.getClientSecret( ).getBytes( StandardCharsets.UTF_8 ) ) );
232 }
233
234 JwtParser parser = parserBuilder.build( );
235 Claims claims;
236 if ( serverConfig == null || serverConfig.getIDTokenSignatureAlgorithmNames( ) == null )
237 {
238
239 Jwt<Header, Claims> jwt = parser.parse( strCompactJwt ).accept( Jwt.UNSECURED_CLAIMS );
240 claims = jwt.getPayload( );
241 }
242 else
243 {
244
245 Jws<Claims> jws = parser.parse( strCompactJwt ).accept( Jws.CLAIMS );
246 if ( !serverConfig.getIDTokenSignatureAlgorithmNames( ).contains( jws.getHeader( ).getAlgorithm( ) ) )
247 {
248 throw new TokenValidationException( "Expected alg is one of <" + serverConfig.getIDTokenSignatureAlgorithmNames( ) + "> but got <" + jws.getHeader( ).getAlgorithm( ) + ">" );
249 }
250 claims = jws.getPayload( );
251 }
252
253 return claims;
254 }
255 }