19
19
import java .util .Arrays ;
20
20
import java .util .Collections ;
21
21
import java .util .HashSet ;
22
+ import java .util .List ;
22
23
import java .util .Set ;
24
+ import java .util .function .Consumer ;
23
25
import java .util .function .Function ;
24
26
27
+ import javax .servlet .http .HttpServletResponse ;
28
+
25
29
import com .nimbusds .jose .jwk .JWKSet ;
26
30
import com .nimbusds .jose .jwk .source .ImmutableJWKSet ;
27
31
import com .nimbusds .jose .jwk .source .JWKSource ;
30
34
import org .junit .BeforeClass ;
31
35
import org .junit .Rule ;
32
36
import org .junit .Test ;
37
+ import org .mockito .ArgumentCaptor ;
33
38
34
39
import org .springframework .beans .factory .annotation .Autowired ;
35
40
import org .springframework .context .annotation .Bean ;
36
41
import org .springframework .http .HttpHeaders ;
37
42
import org .springframework .security .config .Customizer ;
43
+ import org .springframework .http .HttpStatus ;
44
+ import org .springframework .security .authentication .AuthenticationProvider ;
38
45
import org .springframework .security .config .annotation .web .builders .HttpSecurity ;
39
46
import org .springframework .security .config .annotation .web .configuration .EnableWebSecurity ;
40
47
import org .springframework .security .config .annotation .web .configurers .oauth2 .server .resource .OAuth2ResourceServerConfigurer ;
61
68
import org .springframework .security .oauth2 .server .authorization .client .TestRegisteredClients ;
62
69
import org .springframework .security .oauth2 .server .authorization .config .annotation .web .configuration .OAuth2AuthorizationServerConfiguration ;
63
70
import org .springframework .security .oauth2 .server .authorization .oidc .authentication .OidcUserInfoAuthenticationContext ;
71
+ import org .springframework .security .oauth2 .server .authorization .oidc .authentication .OidcUserInfoAuthenticationProvider ;
72
+ import org .springframework .security .oauth2 .server .authorization .oidc .authentication .OidcUserInfoAuthenticationToken ;
64
73
import org .springframework .security .oauth2 .server .authorization .settings .AuthorizationServerSettings ;
65
74
import org .springframework .security .oauth2 .server .authorization .test .SpringTestRule ;
75
+ import org .springframework .security .oauth2 .server .resource .authentication .JwtAuthenticationToken ;
66
76
import org .springframework .security .web .SecurityFilterChain ;
77
+ import org .springframework .security .web .authentication .AuthenticationFailureHandler ;
78
+ import org .springframework .security .web .authentication .AuthenticationSuccessHandler ;
67
79
import org .springframework .security .web .context .HttpSessionSecurityContextRepository ;
68
80
import org .springframework .security .web .context .SecurityContextRepository ;
69
81
import org .springframework .security .web .util .matcher .RequestMatcher ;
73
85
74
86
import static org .assertj .core .api .Assertions .assertThat ;
75
87
import static org .mockito .ArgumentMatchers .any ;
88
+ import static org .mockito .ArgumentMatchers .eq ;
89
+ import static org .mockito .Mockito .doAnswer ;
76
90
import static org .mockito .Mockito .mock ;
77
91
import static org .mockito .Mockito .reset ;
78
92
import static org .mockito .Mockito .spy ;
79
93
import static org .mockito .Mockito .verify ;
94
+ import static org .mockito .Mockito .verifyNoInteractions ;
80
95
import static org .mockito .Mockito .when ;
81
96
import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .get ;
82
97
import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .post ;
@@ -101,21 +116,40 @@ public class OidcUserInfoTests {
101
116
@ Autowired
102
117
private JwtEncoder jwtEncoder ;
103
118
119
+ @ Autowired
120
+ private JwtDecoder jwtDecoder ;
121
+
104
122
@ Autowired
105
123
private OAuth2AuthorizationService authorizationService ;
106
124
107
125
private static Function <OidcUserInfoAuthenticationContext , OidcUserInfo > userInfoMapper ;
108
126
127
+ private static AuthenticationProvider authenticationProvider ;
128
+
129
+ private static Consumer <List <AuthenticationProvider >> authenticationProvidersConsumer ;
130
+
131
+ private static AuthenticationSuccessHandler authenticationSuccessHandler ;
132
+
133
+ private static AuthenticationFailureHandler authenticationFailureHandler ;
134
+
109
135
@ BeforeClass
110
136
public static void init () {
111
137
securityContextRepository = spy (new HttpSessionSecurityContextRepository ());
112
138
userInfoMapper = mock (Function .class );
139
+ authenticationProvider = mock (AuthenticationProvider .class );
140
+ authenticationProvidersConsumer = mock (Consumer .class );
141
+ authenticationSuccessHandler = mock (AuthenticationSuccessHandler .class );
142
+ authenticationFailureHandler = mock (AuthenticationFailureHandler .class );
113
143
}
114
144
115
145
@ Before
116
146
public void setup () {
117
147
reset (securityContextRepository );
118
148
reset (userInfoMapper );
149
+ reset (authenticationProvider );
150
+ reset (authenticationProvidersConsumer );
151
+ reset (authenticationSuccessHandler );
152
+ reset (authenticationFailureHandler );
119
153
}
120
154
121
155
@ Test
@@ -156,16 +190,78 @@ public void requestWhenUserInfoEndpointCustomizedThenUsed() throws Exception {
156
190
157
191
OAuth2Authorization authorization = createAuthorization ();
158
192
this .authorizationService .save (authorization );
193
+
159
194
when (userInfoMapper .apply (any ())).thenReturn (createUserInfo ());
160
195
161
196
OAuth2AccessToken accessToken = authorization .getAccessToken ().getToken ();
162
197
// @formatter:off
163
198
this .mvc .perform (get (DEFAULT_OIDC_USER_INFO_ENDPOINT_URI )
164
199
.header (HttpHeaders .AUTHORIZATION , "Bearer " + accessToken .getTokenValue ()))
165
- .andExpect (status ().is2xxSuccessful ())
166
- .andExpectAll (userInfoResponse ());
200
+ .andExpect (status ().is2xxSuccessful ());
167
201
// @formatter:on
168
202
verify (userInfoMapper ).apply (any ());
203
+ verify (authenticationSuccessHandler ).onAuthenticationSuccess (any (), any (), any ());
204
+ verifyNoInteractions (authenticationFailureHandler );
205
+
206
+ ArgumentCaptor <List <AuthenticationProvider >> authenticationProvidersCaptor = ArgumentCaptor .forClass (List .class );
207
+ verify (authenticationProvidersConsumer ).accept (authenticationProvidersCaptor .capture ());
208
+ List <AuthenticationProvider > authenticationProviders = authenticationProvidersCaptor .getValue ();
209
+ assertThat (authenticationProviders ).hasSize (2 ).allMatch (provider ->
210
+ provider == authenticationProvider ||
211
+ provider instanceof OidcUserInfoAuthenticationProvider
212
+ );
213
+ }
214
+
215
+ @ Test
216
+ public void requestWhenUserInfoEndpointCustomizedThenAuthenticationProviderUsed () throws Exception {
217
+ this .spring .register (CustomUserInfoConfiguration .class ).autowire ();
218
+
219
+ OAuth2Authorization authorization = createAuthorization ();
220
+ this .authorizationService .save (authorization );
221
+
222
+ when (authenticationProvider .supports (eq (OidcUserInfoAuthenticationToken .class ))).thenReturn (true );
223
+ String tokenValue = authorization .getAccessToken ().getToken ().getTokenValue ();
224
+ Jwt jwt = this .jwtDecoder .decode (tokenValue );
225
+ OidcUserInfoAuthenticationToken oidcUserInfoAuthentication = new OidcUserInfoAuthenticationToken (
226
+ new JwtAuthenticationToken (jwt ), createUserInfo ());
227
+ when (authenticationProvider .authenticate (any ())).thenReturn (oidcUserInfoAuthentication );
228
+
229
+ OAuth2AccessToken accessToken = authorization .getAccessToken ().getToken ();
230
+ // @formatter:off
231
+ this .mvc .perform (get (DEFAULT_OIDC_USER_INFO_ENDPOINT_URI )
232
+ .header (HttpHeaders .AUTHORIZATION , "Bearer " + accessToken .getTokenValue ()))
233
+ .andExpect (status ().is2xxSuccessful ());
234
+ // @formatter:on
235
+ verify (authenticationSuccessHandler ).onAuthenticationSuccess (any (), any (), any ());
236
+ verify (authenticationProvider ).authenticate (any ());
237
+ verifyNoInteractions (authenticationFailureHandler );
238
+ verifyNoInteractions (userInfoMapper );
239
+ }
240
+
241
+ @ Test
242
+ public void requestWhenUserInfoEndpointCustomizedAndErrorThenUsed () throws Exception {
243
+ this .spring .register (CustomUserInfoConfiguration .class ).autowire ();
244
+ when (userInfoMapper .apply (any ())).thenReturn (createUserInfo ());
245
+ doAnswer (
246
+ invocation -> {
247
+ HttpServletResponse response = invocation .getArgument (1 );
248
+ response .setStatus (HttpStatus .UNAUTHORIZED .value ());
249
+ response .getWriter ().write ("unauthorized" );
250
+ return null ;
251
+ }
252
+ ).when (authenticationFailureHandler ).onAuthenticationFailure (any (), any (), any ());
253
+
254
+ OAuth2AccessToken accessToken = createAuthorization ().getAccessToken ().getToken ();
255
+
256
+
257
+ // @formatter:off
258
+ this .mvc .perform (get (DEFAULT_OIDC_USER_INFO_ENDPOINT_URI )
259
+ .header (HttpHeaders .AUTHORIZATION , "Bearer " + accessToken .getTokenValue ()))
260
+ .andExpect (status ().is4xxClientError ());
261
+ // @formatter:on
262
+ verify (authenticationFailureHandler ).onAuthenticationFailure (any (), any (), any ());
263
+ verifyNoInteractions (authenticationSuccessHandler );
264
+ verifyNoInteractions (userInfoMapper );
169
265
}
170
266
171
267
// gh-482
@@ -291,6 +387,10 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
291
387
.oidc (oidc -> oidc
292
388
.userInfoEndpoint (userInfo -> userInfo
293
389
.userInfoMapper (userInfoMapper )
390
+ .authenticationProvider (authenticationProvider )
391
+ .authenticationProviders (authenticationProvidersConsumer )
392
+ .userInfoResponseHandler (authenticationSuccessHandler )
393
+ .errorResponseHandler (authenticationFailureHandler )
294
394
)
295
395
);
296
396
// @formatter:on
0 commit comments