31
31
import org .springframework .security .oauth2 .core .AuthorizationGrantType ;
32
32
import org .springframework .security .oauth2 .core .endpoint .OAuth2AuthorizationResponseType ;
33
33
import org .springframework .security .oauth2 .core .endpoint .OAuth2ParameterNames ;
34
+ import org .springframework .security .oauth2 .core .endpoint .PkceParameterNames ;
34
35
import org .springframework .security .oauth2 .server .authorization .client .RegisteredClient ;
35
36
import org .springframework .test .web .servlet .MockMvc ;
36
37
import org .springframework .test .web .servlet .MvcResult ;
@@ -85,20 +86,36 @@ public void addScope(String scope) {
85
86
* @return The state parameter for submitting consent for authorization
86
87
*/
87
88
public String authorize (RegisteredClient registeredClient ) throws Exception {
89
+ return authorize (registeredClient , null );
90
+ }
91
+
92
+ /**
93
+ * Perform the authorization request and obtain a state parameter.
94
+ *
95
+ * @param registeredClient The registered client
96
+ * @param additionalParameters Additional parameters for the request
97
+ * @return The state parameter for submitting consent for authorization
98
+ */
99
+ public String authorize (RegisteredClient registeredClient , MultiValueMap <String , String > additionalParameters ) throws Exception {
88
100
MultiValueMap <String , String > parameters = new LinkedMultiValueMap <>();
89
101
parameters .set (OAuth2ParameterNames .RESPONSE_TYPE , OAuth2AuthorizationResponseType .CODE .getValue ());
90
102
parameters .set (OAuth2ParameterNames .CLIENT_ID , registeredClient .getClientId ());
91
103
parameters .set (OAuth2ParameterNames .REDIRECT_URI , registeredClient .getRedirectUris ().iterator ().next ());
92
104
parameters .set (OAuth2ParameterNames .SCOPE ,
93
105
StringUtils .collectionToDelimitedString (registeredClient .getScopes (), " " ));
94
106
parameters .set (OAuth2ParameterNames .STATE , "state" );
107
+ if (additionalParameters != null ) {
108
+ parameters .addAll (additionalParameters );
109
+ }
95
110
111
+ // @formatter:off
96
112
MvcResult mvcResult = this .mockMvc .perform (get ("/oauth2/authorize" )
97
113
.params (parameters )
98
114
.with (user (this .username ).roles ("USER" )))
99
115
.andExpect (status ().isOk ())
100
116
.andExpect (header ().string ("content-type" , containsString (MediaType .TEXT_HTML_VALUE )))
101
117
.andReturn ();
118
+ // @formatter:on
102
119
String responseHtml = mvcResult .getResponse ().getContentAsString ();
103
120
Matcher matcher = HIDDEN_STATE_INPUT_PATTERN .matcher (responseHtml );
104
121
@@ -120,14 +137,16 @@ public String submitConsent(RegisteredClient registeredClient, String state) thr
120
137
parameters .add (OAuth2ParameterNames .SCOPE , scope );
121
138
}
122
139
140
+ // @formatter:off
123
141
MvcResult mvcResult = this .mockMvc .perform (post ("/oauth2/authorize" )
124
142
.params (parameters )
125
143
.with (user (this .username ).roles ("USER" )))
126
144
.andExpect (status ().is3xxRedirection ())
127
145
.andReturn ();
146
+ // @formatter:on
128
147
String redirectedUrl = mvcResult .getResponse ().getRedirectedUrl ();
129
148
assertThat (redirectedUrl ).isNotNull ();
130
- assertThat (redirectedUrl ).matches ("http://127.0.0.1:8080/ \\ S+\\ ?code=.{15,}&state=state" );
149
+ assertThat (redirectedUrl ).matches ("\\ S+\\ ?code=.{15,}&state=state" );
131
150
132
151
String locationHeader = URLDecoder .decode (redirectedUrl , StandardCharsets .UTF_8 .name ());
133
152
UriComponents uriComponents = UriComponentsBuilder .fromUriString (locationHeader ).build ();
@@ -143,29 +162,67 @@ public String submitConsent(RegisteredClient registeredClient, String state) thr
143
162
* @return The token response
144
163
*/
145
164
public Map <String , Object > getTokenResponse (RegisteredClient registeredClient , String authorizationCode ) throws Exception {
165
+ return getTokenResponse (registeredClient , authorizationCode , null );
166
+ }
167
+
168
+ /**
169
+ * Exchange an authorization code for an access token.
170
+ *
171
+ * @param registeredClient The registered client
172
+ * @param authorizationCode The authorization code obtained from the authorization request
173
+ * @param additionalParameters Additional parameters for the request
174
+ * @return The token response
175
+ */
176
+ public Map <String , Object > getTokenResponse (RegisteredClient registeredClient , String authorizationCode , MultiValueMap <String , String > additionalParameters ) throws Exception {
146
177
MultiValueMap <String , String > parameters = new LinkedMultiValueMap <>();
178
+ parameters .set (OAuth2ParameterNames .CLIENT_ID , registeredClient .getClientId ());
147
179
parameters .set (OAuth2ParameterNames .GRANT_TYPE , AuthorizationGrantType .AUTHORIZATION_CODE .getValue ());
148
180
parameters .set (OAuth2ParameterNames .CODE , authorizationCode );
149
181
parameters .set (OAuth2ParameterNames .REDIRECT_URI , registeredClient .getRedirectUris ().iterator ().next ());
182
+ if (additionalParameters != null ) {
183
+ parameters .addAll (additionalParameters );
184
+ }
150
185
151
- HttpHeaders basicAuth = new HttpHeaders ();
152
- basicAuth .setBasicAuth (registeredClient .getClientId (), "secret" );
186
+ boolean publicClient = (registeredClient .getClientSecret () == null );
187
+ HttpHeaders headers = new HttpHeaders ();
188
+ if (!publicClient ) {
189
+ headers .setBasicAuth (registeredClient .getClientId (),
190
+ registeredClient .getClientSecret ().replace ("{noop}" , "" ));
191
+ }
153
192
193
+ // @formatter:off
154
194
MvcResult mvcResult = this .mockMvc .perform (post ("/oauth2/token" )
155
195
.params (parameters )
156
- .headers (basicAuth ))
196
+ .headers (headers ))
157
197
.andExpect (status ().isOk ())
158
198
.andExpect (header ().string (HttpHeaders .CONTENT_TYPE , containsString (MediaType .APPLICATION_JSON_VALUE )))
159
199
.andExpect (jsonPath ("$.access_token" ).isNotEmpty ())
160
200
.andExpect (jsonPath ("$.token_type" ).isNotEmpty ())
161
201
.andExpect (jsonPath ("$.expires_in" ).isNotEmpty ())
162
- .andExpect (jsonPath ("$.refresh_token" ).isNotEmpty ())
202
+ .andExpect (publicClient
203
+ ? jsonPath ("$.refresh_token" ).doesNotExist ()
204
+ : jsonPath ("$.refresh_token" ).isNotEmpty ()
205
+ )
163
206
.andExpect (jsonPath ("$.scope" ).isNotEmpty ())
164
207
.andExpect (jsonPath ("$.id_token" ).isNotEmpty ())
165
208
.andReturn ();
209
+ // @formatter:on
166
210
167
211
ObjectMapper objectMapper = new ObjectMapper ();
168
212
String responseJson = mvcResult .getResponse ().getContentAsString ();
169
213
return objectMapper .readValue (responseJson , TOKEN_RESPONSE_TYPE_REFERENCE );
170
214
}
215
+
216
+ public static MultiValueMap <String , String > withCodeChallenge () {
217
+ MultiValueMap <String , String > parameters = new LinkedMultiValueMap <>();
218
+ parameters .set (PkceParameterNames .CODE_CHALLENGE , "BqZZ8pTVLsiA3t3tDOys2flJTSH7LoL3Pp5ZqM_YOnE" );
219
+ parameters .set (PkceParameterNames .CODE_CHALLENGE_METHOD , "S256" );
220
+ return parameters ;
221
+ }
222
+
223
+ public static MultiValueMap <String , String > withCodeVerifier () {
224
+ MultiValueMap <String , String > parameters = new LinkedMultiValueMap <>();
225
+ parameters .set (PkceParameterNames .CODE_VERIFIER , "yZ6eB-lEB4BBhIzqoDPqXTTATC0Vkgov7qDF8ar2qT4" );
226
+ return parameters ;
227
+ }
171
228
}
0 commit comments