3333import org .springframework .security .config .annotation .web .configuration .EnableWebSecurity ;
3434import org .springframework .security .config .test .SpringTestContext ;
3535import org .springframework .security .config .test .SpringTestContextExtension ;
36+ import org .springframework .security .core .authority .AuthorityUtils ;
3637import org .springframework .security .core .context .SecurityContextHolder ;
3738import org .springframework .security .core .context .SecurityContextImpl ;
39+ import org .springframework .security .core .userdetails .User ;
3840import org .springframework .security .core .userdetails .UserDetailsService ;
3941import org .springframework .security .provisioning .InMemoryUserDetailsManager ;
4042import org .springframework .security .web .FilterChainProxy ;
4143import org .springframework .security .web .SecurityFilterChain ;
4244import org .springframework .security .web .authentication .ui .DefaultResourcesFilter ;
4345import org .springframework .security .web .webauthn .api .PublicKeyCredentialCreationOptions ;
46+ import org .springframework .security .web .webauthn .api .PublicKeyCredentialRequestOptions ;
47+ import org .springframework .security .web .webauthn .api .PublicKeyCredentialUserEntity ;
4448import org .springframework .security .web .webauthn .api .TestPublicKeyCredentialCreationOptions ;
49+ import org .springframework .security .web .webauthn .api .TestPublicKeyCredentialRequestOptions ;
50+ import org .springframework .security .web .webauthn .api .TestPublicKeyCredentialUserEntity ;
51+ import org .springframework .security .web .webauthn .authentication .HttpSessionPublicKeyCredentialRequestOptionsRepository ;
4552import org .springframework .security .web .webauthn .management .WebAuthnRelyingPartyOperations ;
4653import org .springframework .security .web .webauthn .registration .HttpSessionPublicKeyCredentialCreationOptionsRepository ;
4754import org .springframework .test .web .servlet .MockMvc ;
@@ -67,6 +74,21 @@ public class WebAuthnConfigurerTests {
6774
6875 public final SpringTestContext spring = new SpringTestContext (this );
6976
77+ private static final String WEBAUTHN_LOGIN_BODY = """
78+ {
79+ "id": "dYF7EGnRFFIXkpXi9XU2wg",
80+ "rawId": "dYF7EGnRFFIXkpXi9XU2wg",
81+ "response": {
82+ "authenticatorData": "y9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNgdAAAAAA",
83+ "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRFVsRzRDbU9naWhKMG1vdXZFcE9HdUk0ZVJ6MGRRWmxUQmFtbjdHQ1FTNCIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
84+ "signature": "MEYCIQCW2BcUkRCAXDmGxwMi78jknenZ7_amWrUJEYoTkweldAIhAMD0EMp1rw2GfwhdrsFIeDsL7tfOXVPwOtfqJntjAo4z",
85+ "userHandle": "Q3_0Xd64_HW0BlKRAJnVagJTpLKLgARCj8zjugpRnVo"
86+ },
87+ "clientExtensionResults": {},
88+ "authenticatorAttachment": "platform"
89+ }
90+ """ ;
91+
7092 @ Autowired
7193 MockMvc mvc ;
7294
@@ -182,6 +204,53 @@ public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepositoryBe
182204 .andExpect (request ().sessionAttribute (attrName , options ));
183205 }
184206
207+ @ Test
208+ public void webauthnWhenConfiguredPublicKeyCredentialRequestOptionsRepositoryBeanPresent () throws Exception {
209+ PublicKeyCredentialRequestOptions options = TestPublicKeyCredentialRequestOptions .create ()
210+ .build ();
211+ WebAuthnRelyingPartyOperations rpOperations = mock (WebAuthnRelyingPartyOperations .class );
212+ ConfigCredentialRequestOptionsRepositoryFromBean .rpOperations = rpOperations ;
213+ given (rpOperations .createCredentialRequestOptions (any ())).willReturn (options );
214+ String attrName = "attrName" ;
215+ HttpSessionPublicKeyCredentialRequestOptionsRepository requestOptionsRepository = new HttpSessionPublicKeyCredentialRequestOptionsRepository ();
216+ requestOptionsRepository .setAttrName (attrName );
217+ ConfigCredentialRequestOptionsRepositoryFromBean .requestOptionsRepository = requestOptionsRepository ;
218+ this .spring .register (ConfigCredentialRequestOptionsRepositoryFromBean .class ).autowire ();
219+ this .mvc .perform (post ("/webauthn/authenticate/options" ))
220+ .andExpect (status ().isOk ())
221+ .andExpect (request ().sessionAttribute (attrName , options ));
222+ PublicKeyCredentialUserEntity userEntity = TestPublicKeyCredentialUserEntity .userEntity ().build ();
223+ given (rpOperations .authenticate (any ())).willReturn (userEntity );
224+ this .mvc .perform (post ("/login/webauthn" )
225+ .content (WEBAUTHN_LOGIN_BODY )
226+ .sessionAttr (attrName , options ))
227+ .andExpect (status ().isOk ());
228+ }
229+
230+ @ Test
231+ public void webauthnWhenConfiguredPublicKeyCredentialRequestOptionsRepository () throws Exception {
232+ PublicKeyCredentialRequestOptions options = TestPublicKeyCredentialRequestOptions
233+ .create ()
234+ .build ();
235+ WebAuthnRelyingPartyOperations rpOperations = mock (WebAuthnRelyingPartyOperations .class );
236+ ConfigCredentialRequestOptionsRepository .rpOperations = rpOperations ;
237+ given (rpOperations .createCredentialRequestOptions (any ())).willReturn (options );
238+ String attrName = "attrName" ;
239+ HttpSessionPublicKeyCredentialRequestOptionsRepository requestOptionsRepository = new HttpSessionPublicKeyCredentialRequestOptionsRepository ();
240+ requestOptionsRepository .setAttrName (attrName );
241+ ConfigCredentialRequestOptionsRepository .requestOptionsRepository = requestOptionsRepository ;
242+ this .spring .register (ConfigCredentialRequestOptionsRepository .class ).autowire ();
243+ this .mvc .perform (post ("/webauthn/authenticate/options" ))
244+ .andExpect (status ().isOk ())
245+ .andExpect (request ().sessionAttribute (attrName , options ));
246+ PublicKeyCredentialUserEntity userEntity = TestPublicKeyCredentialUserEntity .userEntity ().build ();
247+ given (rpOperations .authenticate (any ())).willReturn (userEntity );
248+ this .mvc .perform (post ("/login/webauthn" )
249+ .content (WEBAUTHN_LOGIN_BODY )
250+ .sessionAttr (attrName , options ))
251+ .andExpect (status ().isOk ());
252+ }
253+
185254 @ Test
186255 public void webauthnWhenConfiguredMessageConverter () throws Exception {
187256 TestingAuthenticationToken user = new TestingAuthenticationToken ("user" , "password" , "ROLE_USER" );
@@ -264,6 +333,74 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
264333
265334 }
266335
336+ @ Configuration
337+ @ EnableWebSecurity
338+ static class ConfigCredentialRequestOptionsRepositoryFromBean {
339+
340+ private static HttpSessionPublicKeyCredentialRequestOptionsRepository requestOptionsRepository ;
341+
342+ private static WebAuthnRelyingPartyOperations rpOperations ;
343+
344+ @ Bean
345+ WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations () {
346+ return ConfigCredentialRequestOptionsRepositoryFromBean .rpOperations ;
347+ }
348+
349+ @ Bean
350+ UserDetailsService userDetailsService () {
351+ InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager ();
352+ userDetailsService .createUser (User .builder ().username ("user" )
353+ .password ("{noop}password" )
354+ .authorities (AuthorityUtils .createAuthorityList ("ROLE_USER" ))
355+ .build ());
356+ return userDetailsService ;
357+ }
358+
359+ @ Bean
360+ HttpSessionPublicKeyCredentialRequestOptionsRepository credentialRequestOptionsRepository () {
361+ return ConfigCredentialRequestOptionsRepositoryFromBean .requestOptionsRepository ;
362+ }
363+
364+ @ Bean
365+ SecurityFilterChain securityFilterChain (HttpSecurity http ) throws Exception {
366+ return http .csrf (AbstractHttpConfigurer ::disable ).webAuthn (Customizer .withDefaults ()).build ();
367+ }
368+
369+ }
370+
371+ @ Configuration
372+ @ EnableWebSecurity
373+ static class ConfigCredentialRequestOptionsRepository {
374+
375+ private static HttpSessionPublicKeyCredentialRequestOptionsRepository requestOptionsRepository ;
376+
377+ private static WebAuthnRelyingPartyOperations rpOperations ;
378+
379+ @ Bean
380+ WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations () {
381+ return ConfigCredentialRequestOptionsRepository .rpOperations ;
382+ }
383+
384+ @ Bean
385+ UserDetailsService userDetailsService () {
386+ InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager ();
387+ userDetailsService .createUser (User .builder ().username ("user" )
388+ .password ("{noop}password" )
389+ .authorities (AuthorityUtils .createAuthorityList ("ROLE_USER" ))
390+ .build ());
391+ return userDetailsService ;
392+ }
393+
394+ @ Bean
395+ SecurityFilterChain securityFilterChain (HttpSecurity http ) throws Exception {
396+ return http .csrf (AbstractHttpConfigurer ::disable )
397+ .webAuthn ((c ) -> c .requestOptionsRepository (requestOptionsRepository ))
398+ .build ();
399+ }
400+
401+ }
402+
403+
267404 @ Configuration
268405 @ EnableWebSecurity
269406 static class ConfigMessageConverter {
0 commit comments