add Login+2FA support for webauthn
This commit is contained in:
@@ -86,8 +86,7 @@ public class LocalAuthenticationProvider extends DaoAuthenticationProvider imple
|
||||
} else {
|
||||
for (AdditionalAuthenticationProvider<?> provider : providers) {
|
||||
if (provider.supports(auth.getClass())) {
|
||||
auth = provider.authenticate(auth);
|
||||
return this.secondFactorCheck(auth);
|
||||
return provider.authenticate(auth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<unit-api.version>2.2</unit-api.version>
|
||||
<commons-csv.version>1.14.1</commons-csv.version>
|
||||
<dnsjava.version>3.6.3</dnsjava.version>
|
||||
<revision>4.0.0</revision>
|
||||
<revision>4.0.1</revision>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
|
||||
@@ -253,12 +253,6 @@ public class WebAuthnManager implements UserDataProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a WebAuthn login request that does not depend on the username.
|
||||
*
|
||||
* This avoids username enumeration and allows resolving the user from the
|
||||
* returned credentialId during the finish step.
|
||||
*/
|
||||
public String createLoginRequest() {
|
||||
try {
|
||||
StartAssertionOptions.StartAssertionOptionsBuilder optionsBuilder = StartAssertionOptions.builder()
|
||||
@@ -281,21 +275,14 @@ public class WebAuthnManager implements UserDataProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a WebAuthn login assertion where the user is resolved from the
|
||||
* credentialId contained in the assertion.
|
||||
*
|
||||
* @return the authenticated userId if successful, otherwise empty.
|
||||
*/
|
||||
public Optional<Long> validateLoginRequest(String assertionJson) {
|
||||
try {
|
||||
JsonNode assertionNode = objectMapper.readTree(assertionJson);
|
||||
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc = PublicKeyCredential
|
||||
.parseAssertionResponseJson(assertionJson);
|
||||
|
||||
ByteArray credentialId = pkc.getId();
|
||||
WebAuthnCredential credential = credentialRepository.findByCredentialId(credentialId.getBase64Url());
|
||||
if (credential == null || !credential.isEnabled() || credential.getUsage() != WebAuthnUsage.LOGIN) {
|
||||
WebAuthnCredential credential = credentialRepository.findByCredentialId(pkc.getId().getBase64Url());
|
||||
if (credential == null || !credential.isEnabled() || credential.getUsage() != WebAuthnUsage.LOGIN
|
||||
&& credential.getUsage() != WebAuthnUsage.LOGIN_2FA) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -337,12 +324,9 @@ public class WebAuthnManager implements UserDataProvider {
|
||||
|
||||
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc = PublicKeyCredential
|
||||
.parseAssertionResponseJson(assertionJson);
|
||||
WebAuthnCredential credential = credentialRepository.findByCredentialId(pkc.getId().getBase64Url());
|
||||
|
||||
ByteArray credentialId = pkc.getId();
|
||||
WebAuthnCredential credential = credentialRepository.findByCredentialId(credentialId.getBase64Url());
|
||||
|
||||
if (credential == null || !credential.isEnabled()
|
||||
|| (credential.getUsage() != usage)) {
|
||||
if (credential == null || !credential.isEnabled() || (credential.getUsage() != usage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -382,6 +366,16 @@ public class WebAuthnManager implements UserDataProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
public WebAuthnCredential getCredentialForAssertionJson(String assertionJson) {
|
||||
try {
|
||||
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc = PublicKeyCredential
|
||||
.parseAssertionResponseJson(assertionJson);
|
||||
return credentialRepository.findByCredentialId(pkc.getId().getBase64Url());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ByteArray getUserHandle(Long userId) {
|
||||
WebAuthnUserHandle webAuthnUserHandle = webAuthnUserHandleRepository.findByTarget(userId);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package de.bstly.we.webauthn.model;
|
||||
|
||||
public enum WebAuthnUsage {
|
||||
NONE, TWO_FA, LOGIN
|
||||
NONE, TWO_FA, LOGIN, LOGIN_2FA
|
||||
}
|
||||
|
||||
+27
@@ -8,14 +8,19 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import de.bstly.we.businesslogic.UserManager;
|
||||
import de.bstly.we.model.User;
|
||||
import de.bstly.we.security.LocalUserDetailsService;
|
||||
import de.bstly.we.security.businesslogic.AdditionalAuthenticationProvider;
|
||||
import de.bstly.we.security.businesslogic.SecondFactorProviderManager;
|
||||
import de.bstly.we.webauthn.businesslogic.WebAuthnManager;
|
||||
import de.bstly.we.webauthn.model.WebAuthnCredential;
|
||||
import de.bstly.we.webauthn.model.WebAuthnUsage;
|
||||
|
||||
@Component
|
||||
public class WebAuthnAuthenticationProvider implements AdditionalAuthenticationProvider<WebAuthnAuthenticationToken> {
|
||||
@@ -26,6 +31,8 @@ public class WebAuthnAuthenticationProvider implements AdditionalAuthenticationP
|
||||
private LocalUserDetailsService userDetailsService;
|
||||
@Autowired
|
||||
private WebAuthnManager webAuthnManager;
|
||||
@Autowired
|
||||
private SecondFactorProviderManager secondFactorProviderManager;
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
@@ -48,6 +55,26 @@ public class WebAuthnAuthenticationProvider implements AdditionalAuthenticationP
|
||||
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
|
||||
List<GrantedAuthority> authorities = List.copyOf(userDetails.getAuthorities());
|
||||
|
||||
WebAuthnCredential credential = webAuthnManager.getCredentialForAssertionJson(assertionJson);
|
||||
|
||||
if (credential == null) {
|
||||
throw new BadCredentialsException("User not authenticated");
|
||||
}
|
||||
|
||||
if (credential.getUsage() != WebAuthnUsage.LOGIN
|
||||
&& credential.getUsage() != WebAuthnUsage.LOGIN_2FA) {
|
||||
throw new BadCredentialsException("User not authenticated");
|
||||
}
|
||||
|
||||
if (credential.getUsage() != WebAuthnUsage.LOGIN_2FA
|
||||
&& !secondFactorProviderManager.getEnabled(userId).isEmpty()) {
|
||||
PreAuthenticatedAuthenticationToken newAuth = new PreAuthenticatedAuthenticationToken(userDetails, "",
|
||||
AuthorityUtils.createAuthorityList("ROLE_PRE_AUTH_USER"));
|
||||
newAuth.setAuthenticated(false);
|
||||
return newAuth;
|
||||
}
|
||||
|
||||
return new UsernamePasswordAuthenticationToken(userDetails, assertionJson, authorities);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user