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