add Login+2FA support for webauthn
This commit is contained in:
@@ -86,8 +86,7 @@ public class LocalAuthenticationProvider extends DaoAuthenticationProvider imple
|
|||||||
} else {
|
} else {
|
||||||
for (AdditionalAuthenticationProvider<?> provider : providers) {
|
for (AdditionalAuthenticationProvider<?> provider : providers) {
|
||||||
if (provider.supports(auth.getClass())) {
|
if (provider.supports(auth.getClass())) {
|
||||||
auth = provider.authenticate(auth);
|
return provider.authenticate(auth);
|
||||||
return this.secondFactorCheck(auth);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<unit-api.version>2.2</unit-api.version>
|
<unit-api.version>2.2</unit-api.version>
|
||||||
<commons-csv.version>1.14.1</commons-csv.version>
|
<commons-csv.version>1.14.1</commons-csv.version>
|
||||||
<dnsjava.version>3.6.3</dnsjava.version>
|
<dnsjava.version>3.6.3</dnsjava.version>
|
||||||
<revision>4.0.0</revision>
|
<revision>4.0.1</revision>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<parent>
|
<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() {
|
public String createLoginRequest() {
|
||||||
try {
|
try {
|
||||||
StartAssertionOptions.StartAssertionOptionsBuilder optionsBuilder = StartAssertionOptions.builder()
|
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) {
|
public Optional<Long> validateLoginRequest(String assertionJson) {
|
||||||
try {
|
try {
|
||||||
JsonNode assertionNode = objectMapper.readTree(assertionJson);
|
JsonNode assertionNode = objectMapper.readTree(assertionJson);
|
||||||
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc = PublicKeyCredential
|
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc = PublicKeyCredential
|
||||||
.parseAssertionResponseJson(assertionJson);
|
.parseAssertionResponseJson(assertionJson);
|
||||||
|
WebAuthnCredential credential = credentialRepository.findByCredentialId(pkc.getId().getBase64Url());
|
||||||
ByteArray credentialId = pkc.getId();
|
if (credential == null || !credential.isEnabled() || credential.getUsage() != WebAuthnUsage.LOGIN
|
||||||
WebAuthnCredential credential = credentialRepository.findByCredentialId(credentialId.getBase64Url());
|
&& credential.getUsage() != WebAuthnUsage.LOGIN_2FA) {
|
||||||
if (credential == null || !credential.isEnabled() || credential.getUsage() != WebAuthnUsage.LOGIN) {
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,12 +324,9 @@ public class WebAuthnManager implements UserDataProvider {
|
|||||||
|
|
||||||
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc = PublicKeyCredential
|
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc = PublicKeyCredential
|
||||||
.parseAssertionResponseJson(assertionJson);
|
.parseAssertionResponseJson(assertionJson);
|
||||||
|
WebAuthnCredential credential = credentialRepository.findByCredentialId(pkc.getId().getBase64Url());
|
||||||
|
|
||||||
ByteArray credentialId = pkc.getId();
|
if (credential == null || !credential.isEnabled() || (credential.getUsage() != usage)) {
|
||||||
WebAuthnCredential credential = credentialRepository.findByCredentialId(credentialId.getBase64Url());
|
|
||||||
|
|
||||||
if (credential == null || !credential.isEnabled()
|
|
||||||
|| (credential.getUsage() != usage)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,6 +366,16 @@ public class WebAuthnManager implements UserDataProvider {
|
|||||||
return false;
|
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) {
|
public ByteArray getUserHandle(Long userId) {
|
||||||
WebAuthnUserHandle webAuthnUserHandle = webAuthnUserHandleRepository.findByTarget(userId);
|
WebAuthnUserHandle webAuthnUserHandle = webAuthnUserHandleRepository.findByTarget(userId);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package de.bstly.we.webauthn.model;
|
package de.bstly.we.webauthn.model;
|
||||||
|
|
||||||
public enum WebAuthnUsage {
|
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.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import de.bstly.we.businesslogic.UserManager;
|
import de.bstly.we.businesslogic.UserManager;
|
||||||
import de.bstly.we.model.User;
|
import de.bstly.we.model.User;
|
||||||
import de.bstly.we.security.LocalUserDetailsService;
|
import de.bstly.we.security.LocalUserDetailsService;
|
||||||
import de.bstly.we.security.businesslogic.AdditionalAuthenticationProvider;
|
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.businesslogic.WebAuthnManager;
|
||||||
|
import de.bstly.we.webauthn.model.WebAuthnCredential;
|
||||||
|
import de.bstly.we.webauthn.model.WebAuthnUsage;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class WebAuthnAuthenticationProvider implements AdditionalAuthenticationProvider<WebAuthnAuthenticationToken> {
|
public class WebAuthnAuthenticationProvider implements AdditionalAuthenticationProvider<WebAuthnAuthenticationToken> {
|
||||||
@@ -26,6 +31,8 @@ public class WebAuthnAuthenticationProvider implements AdditionalAuthenticationP
|
|||||||
private LocalUserDetailsService userDetailsService;
|
private LocalUserDetailsService userDetailsService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private WebAuthnManager webAuthnManager;
|
private WebAuthnManager webAuthnManager;
|
||||||
|
@Autowired
|
||||||
|
private SecondFactorProviderManager secondFactorProviderManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
@@ -48,6 +55,26 @@ public class WebAuthnAuthenticationProvider implements AdditionalAuthenticationP
|
|||||||
|
|
||||||
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
|
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
|
||||||
List<GrantedAuthority> authorities = List.copyOf(userDetails.getAuthorities());
|
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);
|
return new UsernamePasswordAuthenticationToken(userDetails, assertionJson, authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user