1+ /**
2+ * Licensed to the Apache Software Foundation (ASF) under one
3+ * or more contributor license agreements. See the NOTICE file
4+ * distributed with this work for additional information
5+ * regarding copyright ownership. The ASF licenses this file
6+ * to you under the Apache License, Version 2.0 (the
7+ * "License"); you may not use this file except in compliance
8+ * with the License. You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing,
13+ * software distributed under the License is distributed on an
14+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+ * KIND, either express or implied. See the License for the
16+ * specific language governing permissions and limitations
17+ * under the License.
18+ */
19+ package org .apache .cxf .rs .security .oauth2 .filters ;
20+
21+ import java .lang .reflect .Field ;
22+
23+ import jakarta .ws .rs .core .MultivaluedHashMap ;
24+ import jakarta .ws .rs .core .MultivaluedMap ;
25+ import org .apache .cxf .jaxrs .ext .MessageContext ;
26+ import org .apache .cxf .message .Message ;
27+ import org .apache .cxf .message .MessageImpl ;
28+ import org .apache .cxf .phase .PhaseInterceptorChain ;
29+ import org .apache .cxf .rs .security .jose .jwa .SignatureAlgorithm ;
30+ import org .apache .cxf .rs .security .jose .jws .HmacJwsSignatureProvider ;
31+ import org .apache .cxf .rs .security .jose .jws .HmacJwsSignatureVerifier ;
32+ import org .apache .cxf .rs .security .jose .jws .JwsJwtCompactProducer ;
33+ import org .apache .cxf .rs .security .jose .jwt .JwtClaims ;
34+ import org .apache .cxf .rs .security .jose .jwt .JwtConstants ;
35+ import org .apache .cxf .rs .security .oauth2 .common .AccessTokenValidation ;
36+ import org .apache .cxf .rs .security .oauth2 .provider .OAuthServiceException ;
37+
38+ import org .junit .After ;
39+ import org .junit .Test ;
40+
41+ import static org .junit .Assert .assertEquals ;
42+ import static org .junit .Assert .assertNotNull ;
43+ import static org .junit .Assert .assertThrows ;
44+ import static org .junit .Assert .assertTrue ;
45+ import static org .mockito .Mockito .mock ;
46+
47+
48+ public class JwtAccessTokenValidatorTest {
49+
50+ private static final String SIGNING_KEY = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoq2QjvA6P5f8" ;
51+ private static final String DIFFERENT_SIGNING_KEY = "hJtXIZ2uSN5kbQfbtTNWbg6X5U0ZSyxP6oJ6H3f3j1k" ;
52+
53+ @ Test
54+ public void testValidateAccessTokenSignedAndSignatureVerified () {
55+ JwtAccessTokenValidator validator = new JwtAccessTokenValidator ();
56+ validator .setJwsVerifier (new HmacJwsSignatureVerifier (SIGNING_KEY , SignatureAlgorithm .HS256 ));
57+ validator .setValidateAudience (false );
58+
59+ String jwt = createSignedToken (SIGNING_KEY , "signed-client" , 3600 );
60+ MultivaluedMap <String , String > params = new MultivaluedHashMap <>();
61+
62+ AccessTokenValidation result = validator .validateAccessToken (
63+ mock (MessageContext .class ), "Bearer" , jwt , params );
64+
65+ assertNotNull (result );
66+ assertTrue (result .isInitialValidationSuccessful ());
67+ assertEquals ("signed-client" , result .getClientId ());
68+ }
69+
70+ @ Test
71+ public void testValidateAccessTokenSignedButSignatureValidationFails () {
72+ JwtAccessTokenValidator validator = new JwtAccessTokenValidator ();
73+ validator .setJwsVerifier (new HmacJwsSignatureVerifier (DIFFERENT_SIGNING_KEY , SignatureAlgorithm .HS256 ));
74+ validator .setValidateAudience (false );
75+
76+ String jwt = createSignedToken (SIGNING_KEY , "signed-client" , 3600 );
77+ MultivaluedMap <String , String > params = new MultivaluedHashMap <>();
78+
79+ OAuthServiceException ex = assertThrows (OAuthServiceException .class , () ->
80+ validator .validateAccessToken (mock (MessageContext .class ), "Bearer" , jwt , params ));
81+
82+ assertNotNull (ex .getCause ());
83+ assertTrue (ex .getCause ().getMessage ().contains ("Invalid Signature" ));
84+ }
85+
86+ @ Test
87+ public void testValidateAccessTokenExpired () {
88+ JwtAccessTokenValidator validator = new JwtAccessTokenValidator ();
89+ validator .setJwsVerifier (new HmacJwsSignatureVerifier (SIGNING_KEY , SignatureAlgorithm .HS256 ));
90+ validator .setValidateAudience (false );
91+
92+ String jwt = createSignedToken (SIGNING_KEY , "signed-client" , -3600 ); // Expired 1 hour ago
93+ MultivaluedMap <String , String > params = new MultivaluedHashMap <>();
94+
95+ OAuthServiceException ex = assertThrows (OAuthServiceException .class , () ->
96+ validator .validateAccessToken (mock (MessageContext .class ), "Bearer" , jwt , params ));
97+
98+ assertNotNull (ex .getCause ());
99+ assertTrue (ex .getCause ().getMessage ().contains ("expired" ));
100+ }
101+
102+ @ Test
103+ public void testValidateAccessTokenNotBefore () {
104+ JwtAccessTokenValidator validator = new JwtAccessTokenValidator ();
105+ validator .setJwsVerifier (new HmacJwsSignatureVerifier (SIGNING_KEY , SignatureAlgorithm .HS256 ));
106+ validator .setValidateAudience (false );
107+
108+ // Not valid before 1 hour from now
109+ String jwt = createSignedToken (SIGNING_KEY , "signed-client" , 3600 , 3600 , null );
110+ MultivaluedMap <String , String > params = new MultivaluedHashMap <>();
111+
112+ OAuthServiceException ex = assertThrows (OAuthServiceException .class , () ->
113+ validator .validateAccessToken (mock (MessageContext .class ), "Bearer" , jwt , params ));
114+
115+ assertNotNull (ex .getCause ());
116+ assertTrue (ex .getCause ().getMessage ().contains ("cannot be accepted" ));
117+ }
118+
119+ @ After
120+ public void clearCurrentMessage () throws Exception {
121+ setThreadLocalMessage (null );
122+ }
123+
124+ @ Test
125+ public void testValidAudience () throws Exception {
126+ JwtAccessTokenValidator validator = new JwtAccessTokenValidator ();
127+ validator .setJwsVerifier (new HmacJwsSignatureVerifier (SIGNING_KEY , SignatureAlgorithm .HS256 ));
128+
129+ String jwt = createSignedToken (SIGNING_KEY , "signed-client" , 3600 , 0 , "valid-audience" );
130+ MultivaluedMap <String , String > params = new MultivaluedHashMap <>();
131+
132+ Message message = new MessageImpl ();
133+ message .put (JwtConstants .EXPECTED_CLAIM_AUDIENCE , "valid-audience" );
134+ setThreadLocalMessage (message );
135+
136+ AccessTokenValidation result = validator .validateAccessToken (
137+ mock (MessageContext .class ), "Bearer" , jwt , params );
138+
139+ assertNotNull (result );
140+ assertTrue (result .isInitialValidationSuccessful ());
141+ assertEquals ("signed-client" , result .getClientId ());
142+ }
143+
144+ @ Test
145+ public void testInvalidAudience () throws Exception {
146+ JwtAccessTokenValidator validator = new JwtAccessTokenValidator ();
147+ validator .setJwsVerifier (new HmacJwsSignatureVerifier (SIGNING_KEY , SignatureAlgorithm .HS256 ));
148+
149+ String jwt = createSignedToken (SIGNING_KEY , "signed-client" , 3600 , 0 , "invalid-audience" );
150+ MultivaluedMap <String , String > params = new MultivaluedHashMap <>();
151+
152+ Message message = new MessageImpl ();
153+ message .put (JwtConstants .EXPECTED_CLAIM_AUDIENCE , "valid-audience" );
154+ setThreadLocalMessage (message );
155+
156+ OAuthServiceException ex = assertThrows (OAuthServiceException .class , () ->
157+ validator .validateAccessToken (mock (MessageContext .class ), "Bearer" , jwt , params ));
158+
159+ assertNotNull (ex .getCause ());
160+ assertTrue (ex .getCause ().getMessage ().contains ("Invalid audience restriction" ));
161+ }
162+
163+ private static String createSignedToken (String key , String clientId , long expiresInSeconds ) {
164+ return createSignedToken (key , clientId , expiresInSeconds , 0 , null );
165+ }
166+
167+ private static String createSignedToken (String key , String clientId , long expiresInSeconds ,
168+ long notBeforeOffsetSeconds , String audience ) {
169+ long now = System .currentTimeMillis () / 1000 ;
170+ JwtClaims claims = new JwtClaims ();
171+ claims .setIssuedAt (now );
172+ claims .setExpiryTime (now + expiresInSeconds );
173+ if (clientId != null ) {
174+ claims .setClaim ("client_id" , clientId );
175+ }
176+ claims .setIssuer ("SomeIssuer" );
177+ claims .setSubject ("SomeSubject" );
178+ if (notBeforeOffsetSeconds != 0 ) {
179+ claims .setNotBefore (now + notBeforeOffsetSeconds );
180+ }
181+ if (audience != null ) {
182+ claims .setAudience (audience );
183+ }
184+
185+ JwsJwtCompactProducer producer = new JwsJwtCompactProducer (claims );
186+ return producer .signWith (new HmacJwsSignatureProvider (key , SignatureAlgorithm .HS256 ));
187+ }
188+
189+ private static void setThreadLocalMessage (Message message ) throws Exception {
190+ Field f = PhaseInterceptorChain .class .getDeclaredField ("CURRENT_MESSAGE" );
191+ f .setAccessible (true );
192+ @ SuppressWarnings ("unchecked" )
193+ ThreadLocal <Message > tl = (ThreadLocal <Message >) f .get (null );
194+ if (message == null ) {
195+ tl .remove ();
196+ } else {
197+ tl .set (message );
198+ }
199+ }
200+ }
0 commit comments