spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.84k stars 5.91k forks source link

Support WebAuthn #5238

Open rwinch opened 6 years ago

rwinch commented 6 years ago

Summary

https://www.w3.org/TR/webauthn

Work on this was started in gh-6842 but stalled. The work is still in https://github.com/rwinch/spring-security-webauthn

ynojima commented 6 years ago

Hello @rwinch, As I've commented on https://github.com/spring-projects/spring-security/issues/2603#issuecomment-377922919, I'm implementing WebAuthn for spring-security (https://github.com/ynojima/spring-security-webauthn). It is still a proof of concept, but in future, I'd like to send pull request. I'll be happy if you look into it.

rwinch commented 6 years ago

@ynojima Thank you for reaching out (again) and pointing me to your comment! Sorry I had missed your comment. Let's move all of the discussion to here going forward.

I'd love for you to send a pull request. I will review your proof of concept and pull request this week.

Thanks again!

ynojima commented 6 years ago

Thank you for your reply, and here is a separate pull request to make a foundation for multi-factor authentication in spring security: https://github.com/spring-projects/spring-security/pull/5196 My spring-security-webauthn is built on the top of it, and other kind multi-factor(step) authentication provider may utilize it to indicate a user who passed the first authentication.

rwinch commented 6 years ago

Thanks! I'm taking a look at your sample now.

rwinch commented 6 years ago

Thank you for putting this together. I have tried the sample in both FireFox and Chrome.

In Chrome 65.0.3325.181 with Web Authentication API I attempt to register my U2F FIDO key on the register page and it gives me the following error in my Boot application:

2018-04-19 17:00:22.195 ERROR 9086 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.webauthn4j.exception.NotImplementedException] with root cause

com.webauthn4j.exception.NotImplementedException: null
        at com.webauthn4j.context.validator.attestation.FIDOU2FAttestationStatementValidator.validateTrustworthiness(FIDOU2FAttestationStatementValidator.java:58)
        at com.webauthn4j.context.validator.attestation.AbstractAttestationStatementValidator.validate(AbstractAttestationStatementValidator.java:15)
        at com.webauthn4j.context.validator.WebAuthnRegistrationContextValidator.validateAttestationStatement(WebAuthnRegistrationContextValidator.java:117)
        at com.webauthn4j.context.validator.WebAuthnRegistrationContextValidator.validate(WebAuthnRegistrationContextValidator.java:105)
        at net.sharplab.springframework.security.webauthn.WebAuthnRegistrationRequestValidator.validate(WebAuthnRegistrationRequestValidator.java:27)
        at net.sharplab.springframework.security.webauthn.sample.app.web.helper.AuthenticatorHelper.lambda$validateAuthenticators$0(AuthenticatorHelper.java:46)
        at java.util.stream.MatchOps$1MatchSink.accept(MatchOps.java:90)
        at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359)
        at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
        at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
        at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:230)
        at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:196)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.allMatch(ReferencePipeline.java:454)
        at net.sharplab.springframework.security.webauthn.sample.app.web.helper.AuthenticatorHelper.validateAuthenticators(AuthenticatorHelper.java:43)
        at net.sharplab.springframework.security.webauthn.sample.app.web.SignupController.create(SignupController.java:68)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:870)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:776)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:881)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:855)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.terasoluna.gfw.web.exception.ExceptionLoggingFilter.doFilter(ExceptionLoggingFilter.java:100)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at net.sharplab.springframework.security.webauthn.metadata.MetadataEndpointFilter.doFilter(MetadataEndpointFilter.java:67)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:124)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)

In FireFox 60.0b13 I am able to register the key, but then upon trying to use it to log in a modal dialog pops up very quickly and then disappears. I also see the following error the console

Credential Management API is not supported.
loginViewModel.js:127
LoginViewModel.prototype.tryLoginWithSavedPasswordCredential/<
loginViewModel.js:127

Can you please help me try to sort out these issues?

ynojima commented 6 years ago

Hi Rob, it seems Chrome 65.0.3325.181 has not implemented AttestationConveyancePreference(https://www.w3.org/TR/webauthn/#attestation-convey), which is used in the sample. Because of it, chrome 65 provides full authenticator attestation, which is optional in WebAuthn Candidate Recomendation, but my PoC cannot validate it for now.

Chrome 66, which was released very recently(https://chromereleases.googleblog.com/2018/04/stable-channel-update-for-desktop.html), has implemented AttestationConveyancePreference. Please update Chrome to latest stable version and try again.

Regarding FireFox 60.0b13, which button in the login page did you use, "Login" or "Password-less Login"? For FIDO-U2F key, "Password-less Login" button doesn't work. It is for user verifying authenticator like finger print sensor. If you are using the "Login" button but still facing the trouble, could you close the Firefox Developer Tools and try again? I'm not sure the reason why, but it interfer login page to work.

ynojima commented 6 years ago

Thank you so much for your review to my pull request. I understand the importance of backward-compatibility. I made change to my patch to keep the existing interface as is. I opened a new pull request within my forked spring-security repository for review (not for merge). https://github.com/ynojima/spring-security/pull/3/files

Could you check the design again? I agree with you that it is not appropriate time to merge the pull request since the user facing code is not ready, but spring-security-webauthn is built on the top of the pull request, I'd like to fix the design to handle multi factor authentication flow.

ynojima commented 6 years ago

Hi @rwinch, I'd like to reboot this thread.

After your review comment, I made a lot of changes to my project (spring-security-webauthn).

There is still room to be improved in the sample application, but the library itself is feature complete for the initial release. I appreciate if you review spring-security-webauthn and the patch again.

hohwille commented 4 years ago

Is https://github.com/webauthn4j/webauthn4j-spring-security the new place or just an alternative to https://github.com/ynojima/spring-security-webauthn ?

ynojima commented 4 years ago

https://github.com/webauthn4j/webauthn4j-spring-security is a playground to test new design. For implentation that intended to be merged into spring-security upstream, I'll keep using https://github.com/ynojima/spring-security-webauthn or https://github.com/ynojima/spring-security

asaikali commented 2 years ago

Example implementation of WebAuthn and Spring Security using the Yubico libs https://github.com/asaikali/devnexus-2022/tree/main/webauthn-basics

guycall commented 1 month ago

I found the https://github.com/rwinch/spring-security-webauthn project via the recent Spring One presentation. The readme mentions "... eventually be merged into Spring Security."

The youtube comments of the Spring One presentation had a comment, possibly from @marcusdacoregio, that it might be ready for Spring Security 6.4. Is that the plan or will it more likely be 7.0?

JohnNiang commented 1 month ago

@guycall , I found https://github.com/spring-projects/spring-security/issues/13305 was closed by https://github.com/spring-projects/spring-security/commit/b0e8730d70ee548cd383ba358ec87e268b52c29b. And the feature was released into 6.4.0-RC1.