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 .grants .jwt ;
20+
21+ import java .lang .reflect .Field ;
22+
23+ import jakarta .ws .rs .core .MultivaluedMap ;
24+ import org .apache .cxf .jaxrs .impl .MetadataMap ;
25+ import org .apache .cxf .message .Message ;
26+ import org .apache .cxf .message .MessageImpl ;
27+ import org .apache .cxf .phase .PhaseInterceptorChain ;
28+ import org .apache .cxf .rs .security .jose .jwa .SignatureAlgorithm ;
29+ import org .apache .cxf .rs .security .jose .jws .HmacJwsSignatureProvider ;
30+ import org .apache .cxf .rs .security .jose .jws .HmacJwsSignatureVerifier ;
31+ import org .apache .cxf .rs .security .jose .jws .JwsJwtCompactProducer ;
32+ import org .apache .cxf .rs .security .jose .jwt .JwtClaims ;
33+ import org .apache .cxf .rs .security .oauth2 .common .AccessTokenRegistration ;
34+ import org .apache .cxf .rs .security .oauth2 .common .Client ;
35+ import org .apache .cxf .rs .security .oauth2 .common .ServerAccessToken ;
36+ import org .apache .cxf .rs .security .oauth2 .grants .OAuthDataProviderImpl ;
37+ import org .apache .cxf .rs .security .oauth2 .provider .OAuthServiceException ;
38+ import org .apache .cxf .rs .security .oauth2 .tokens .bearer .BearerAccessToken ;
39+
40+ import org .junit .After ;
41+ import org .junit .Before ;
42+ import org .junit .Test ;
43+
44+ import static org .junit .Assert .assertEquals ;
45+ import static org .junit .Assert .assertNotNull ;
46+ import static org .junit .Assert .fail ;
47+
48+ public class JwtBearerGrantHandlerTest {
49+
50+ private static final String SIGNING_KEY = "jwt-grant-test-signing-key" ;
51+
52+ @ Before
53+ public void setUp () throws Exception {
54+ setThreadLocalMessage (new MessageImpl ());
55+ }
56+
57+ @ After
58+ public void tearDown () throws Exception {
59+ setThreadLocalMessage (null );
60+ }
61+
62+ @ Test
63+ public void testMismatchedClientAndSubjectRejected () {
64+ JwtBearerGrantHandler handler = new JwtBearerGrantHandler ();
65+ handler .setDataProvider (new SubjectAwareDataProvider ());
66+ handler .setJwsVerifier (new HmacJwsSignatureVerifier (SIGNING_KEY , SignatureAlgorithm .HS256 ));
67+
68+ Client client = new Client ("fuzz-client" , "secret" , true );
69+ String assertion = createSignedAssertion ("trusted-issuer" , "victim-user" );
70+
71+ MultivaluedMap <String , String > params = new MetadataMap <>();
72+ params .putSingle (Constants .CLIENT_GRANT_ASSERTION_PARAM , assertion );
73+
74+ try {
75+ handler .createAccessToken (client , params );
76+ fail ("OAuthServiceException expected" );
77+ } catch (OAuthServiceException expected ) {
78+ assertEquals ("invalid_grant" , expected .getMessage ());
79+ }
80+ }
81+
82+ @ Test
83+ public void testMatchingClientAndSubjectAccepted () {
84+ JwtBearerGrantHandler handler = new JwtBearerGrantHandler ();
85+ handler .setDataProvider (new SubjectAwareDataProvider ());
86+ handler .setJwsVerifier (new HmacJwsSignatureVerifier (SIGNING_KEY , SignatureAlgorithm .HS256 ));
87+
88+ Client client = new Client ("fuzz-client" , "secret" , true );
89+ String assertion = createSignedAssertion ("trusted-issuer" , client .getClientId ());
90+
91+ MultivaluedMap <String , String > params = new MetadataMap <>();
92+ params .putSingle (Constants .CLIENT_GRANT_ASSERTION_PARAM , assertion );
93+
94+ ServerAccessToken token = handler .createAccessToken (client , params );
95+
96+ assertNotNull (token );
97+ assertNotNull (token .getSubject ());
98+ assertEquals (client .getClientId (), token .getSubject ().getLogin ());
99+ }
100+
101+ private static String createSignedAssertion (String issuer , String subject ) {
102+ long now = System .currentTimeMillis () / 1000 ;
103+ JwtClaims claims = new JwtClaims ();
104+ claims .setIssuer (issuer );
105+ claims .setSubject (subject );
106+ claims .setIssuedAt (now );
107+ claims .setExpiryTime (now + 300 );
108+
109+ JwsJwtCompactProducer producer = new JwsJwtCompactProducer (claims );
110+ return producer .signWith (new HmacJwsSignatureProvider (SIGNING_KEY , SignatureAlgorithm .HS256 ));
111+ }
112+
113+ private static final class SubjectAwareDataProvider extends OAuthDataProviderImpl {
114+ @ Override
115+ public ServerAccessToken createAccessToken (AccessTokenRegistration accessToken ) {
116+ BearerAccessToken token = new BearerAccessToken (accessToken .getClient (), 3600 );
117+ token .setSubject (accessToken .getSubject ());
118+ token .setGrantType (accessToken .getGrantType ());
119+ return token ;
120+ }
121+ }
122+
123+ private static void setThreadLocalMessage (Message message ) throws Exception {
124+ Field f = PhaseInterceptorChain .class .getDeclaredField ("CURRENT_MESSAGE" );
125+ f .setAccessible (true );
126+ @ SuppressWarnings ("unchecked" )
127+ ThreadLocal <Message > tl = (ThreadLocal <Message >) f .get (null );
128+ if (message == null ) {
129+ tl .remove ();
130+ } else {
131+ tl .set (message );
132+ }
133+ }
134+ }
0 commit comments