mirror of
https://gitee.com/dromara/MaxKey.git
synced 2025-12-06 17:08:29 +08:00
passkey优化
This commit is contained in:
parent
a5f8d15b2d
commit
10c5b46810
@ -21,7 +21,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
/**
|
/**
|
||||||
* Passkey配置属性
|
* Passkey配置属性
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties(prefix = "maxkey.passkey")
|
@ConfigurationProperties(prefix = "maxkey.login.passkey")
|
||||||
public class PasskeyProperties {
|
public class PasskeyProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -101,12 +101,12 @@
|
|||||||
{{ 'app.login.login' | i18n }}
|
{{ 'app.login.login' | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
<nz-form-item *ngIf="loginType == 'normal'">
|
<nz-form-item *ngIf="passkeyEnabled">
|
||||||
<button nz-button type="button" nzType="default" nzSize="large" (click)="passkeyLogin()" nzBlock>
|
<button nz-button type="button" nzType="default" nzSize="large" (click)="passkeyLogin()" nzBlock>
|
||||||
<i nz-icon nzType="safety-certificate" nzTheme="outline"></i>
|
<i nz-icon nzType="safety-certificate" nzTheme="outline"></i>
|
||||||
{{ 'mxk.login.passkey-login' | i18n }}
|
{{ 'mxk.login.passkey-login' | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</nz-form-item>
|
</nz-form-item>
|
||||||
</form>
|
</form>
|
||||||
<div class="other" *ngIf="loginType == 'normal'">
|
<div class="other" *ngIf="loginType == 'normal'">
|
||||||
{{ 'app.login.sign-in-with' | i18n }}
|
{{ 'app.login.sign-in-with' | i18n }}
|
||||||
|
|||||||
@ -56,6 +56,8 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
|||||||
loading = false;
|
loading = false;
|
||||||
passwordVisible = false;
|
passwordVisible = false;
|
||||||
qrexpire = false;
|
qrexpire = false;
|
||||||
|
passkeyEnabled = false;
|
||||||
|
passkeyAllowedOrigins = [];
|
||||||
imageCaptcha = '';
|
imageCaptcha = '';
|
||||||
captchaType = '';
|
captchaType = '';
|
||||||
state = '';
|
state = '';
|
||||||
@ -136,6 +138,25 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
|||||||
this.socials = res.data.socials;
|
this.socials = res.data.socials;
|
||||||
this.state = res.data.state;
|
this.state = res.data.state;
|
||||||
this.captchaType = res.data.captcha;
|
this.captchaType = res.data.captcha;
|
||||||
|
this.passkeyEnabled = res.data.passkeyEnabled;
|
||||||
|
this.passkeyAllowedOrigins = res.data.passkeyAllowedOrigins;
|
||||||
|
let passkeyAllowedOriginsMatch = false;
|
||||||
|
for (let allowedOrigin of this.passkeyAllowedOrigins) {
|
||||||
|
console.log(`passkey allowedOrigin ${allowedOrigin}`);
|
||||||
|
console.log(`location ${window.location.href}`);
|
||||||
|
if (
|
||||||
|
window.location.href.startsWith('http://localhost') ||
|
||||||
|
(window.location.href.startsWith('https') && window.location.href.indexOf(allowedOrigin) > -1)
|
||||||
|
) {
|
||||||
|
console.log(window.location.href.indexOf(allowedOrigin) > -1);
|
||||||
|
passkeyAllowedOriginsMatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (window.PublicKeyCredential && this.passkeyEnabled && passkeyAllowedOriginsMatch) {
|
||||||
|
this.passkeyEnabled = true;
|
||||||
|
} else {
|
||||||
|
this.passkeyEnabled = false;
|
||||||
|
}
|
||||||
if (this.captchaType === 'NONE') {
|
if (this.captchaType === 'NONE') {
|
||||||
//清除校验规则
|
//清除校验规则
|
||||||
this.form.get('captcha')?.clearValidators();
|
this.form.get('captcha')?.clearValidators();
|
||||||
@ -537,10 +558,12 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是没有注册 Passkey 的错误
|
// 检查是否是没有注册 Passkey 的错误
|
||||||
if (errorMessage.includes('没有注册任何 Passkey') ||
|
if (
|
||||||
errorMessage.includes('No Passkeys registered') ||
|
errorMessage.includes('没有注册任何 Passkey') ||
|
||||||
errorMessage.includes('还没有注册任何 Passkey') ||
|
errorMessage.includes('No Passkeys registered') ||
|
||||||
errorMessage.includes('系统中还没有注册任何 Passkey')) {
|
errorMessage.includes('还没有注册任何 Passkey') ||
|
||||||
|
errorMessage.includes('系统中还没有注册任何 Passkey')
|
||||||
|
) {
|
||||||
// 直接显示友好提示并返回,不抛出错误避免被全局拦截器捕获
|
// 直接显示友好提示并返回,不抛出错误避免被全局拦截器捕获
|
||||||
this.msg.warning('还未注册 Passkey,请注册 Passkey');
|
this.msg.warning('还未注册 Passkey,请注册 Passkey');
|
||||||
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
||||||
@ -555,10 +578,12 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
|||||||
console.error('Failed to get auth options:', authOptionsResponse);
|
console.error('Failed to get auth options:', authOptionsResponse);
|
||||||
// 检查是否是没有注册 Passkey 的错误
|
// 检查是否是没有注册 Passkey 的错误
|
||||||
const errorMessage = authOptionsResponse?.message || '获取认证选项失败';
|
const errorMessage = authOptionsResponse?.message || '获取认证选项失败';
|
||||||
if (errorMessage.includes('没有注册任何 Passkey') ||
|
if (
|
||||||
errorMessage.includes('No Passkeys registered') ||
|
errorMessage.includes('没有注册任何 Passkey') ||
|
||||||
errorMessage.includes('还没有注册任何 Passkey') ||
|
errorMessage.includes('No Passkeys registered') ||
|
||||||
errorMessage.includes('系统中还没有注册任何 Passkey')) {
|
errorMessage.includes('还没有注册任何 Passkey') ||
|
||||||
|
errorMessage.includes('系统中还没有注册任何 Passkey')
|
||||||
|
) {
|
||||||
// 直接显示友好提示并返回,不抛出错误避免被全局拦截器捕获
|
// 直接显示友好提示并返回,不抛出错误避免被全局拦截器捕获
|
||||||
this.msg.warning('还未注册 Passkey,请注册 Passkey');
|
this.msg.warning('还未注册 Passkey,请注册 Passkey');
|
||||||
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
||||||
@ -597,9 +622,9 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
|||||||
console.log('Step 3: Calling WebAuthn API navigator.credentials.get()...');
|
console.log('Step 3: Calling WebAuthn API navigator.credentials.get()...');
|
||||||
console.log('Available authenticators will be queried automatically');
|
console.log('Available authenticators will be queried automatically');
|
||||||
|
|
||||||
const credential = await navigator.credentials.get({
|
const credential = (await navigator.credentials.get({
|
||||||
publicKey: convertedOptions
|
publicKey: convertedOptions
|
||||||
}) as PublicKeyCredential;
|
})) as PublicKeyCredential;
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
console.error('No credential returned from WebAuthn API');
|
console.error('No credential returned from WebAuthn API');
|
||||||
@ -698,7 +723,6 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
|||||||
console.error('Invalid auth result - missing userId:', authResult);
|
console.error('Invalid auth result - missing userId:', authResult);
|
||||||
throw new Error('认证成功但用户数据无效');
|
throw new Error('认证成功但用户数据无效');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('=== PASSKEY LOGIN ERROR ===');
|
console.error('=== PASSKEY LOGIN ERROR ===');
|
||||||
console.error('Error type:', error.constructor.name);
|
console.error('Error type:', error.constructor.name);
|
||||||
@ -707,14 +731,17 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
|||||||
console.error('Full error object:', error);
|
console.error('Full error object:', error);
|
||||||
|
|
||||||
// 检查是否是没有注册 Passkey 的错误
|
// 检查是否是没有注册 Passkey 的错误
|
||||||
if (error.message && (error.message.includes('PASSKEY_NOT_REGISTERED') ||
|
if (
|
||||||
error.message.includes('没有找到可用的凭据') ||
|
error.message &&
|
||||||
error.message.includes('No credentials available') ||
|
(error.message.includes('PASSKEY_NOT_REGISTERED') ||
|
||||||
error.message.includes('用户未注册') ||
|
error.message.includes('没有找到可用的凭据') ||
|
||||||
error.message.includes('credential not found') ||
|
error.message.includes('No credentials available') ||
|
||||||
error.message.includes('没有注册任何 Passkey') ||
|
error.message.includes('用户未注册') ||
|
||||||
error.message.includes('No Passkeys registered') ||
|
error.message.includes('credential not found') ||
|
||||||
error.message.includes('还没有注册任何 Passkey'))) {
|
error.message.includes('没有注册任何 Passkey') ||
|
||||||
|
error.message.includes('No Passkeys registered') ||
|
||||||
|
error.message.includes('还没有注册任何 Passkey'))
|
||||||
|
) {
|
||||||
this.msg.warning('还未注册 Passkey,请注册 Passkey');
|
this.msg.warning('还未注册 Passkey,请注册 Passkey');
|
||||||
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
||||||
return;
|
return;
|
||||||
@ -747,10 +774,10 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error('Unknown WebAuthn error');
|
console.error('Unknown WebAuthn error');
|
||||||
this.msg.error('Passkey 登录失败:' + (error.message || '请重试或使用其他登录方式'));
|
this.msg.error(`Passkey 登录失败:${error.message || '请重试或使用其他登录方式'}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.msg.error('Passkey 登录失败:' + (error.message || '请重试或使用其他登录方式'));
|
this.msg.error(`Passkey 登录失败:${error.message || '请重试或使用其他登录方式'}`);
|
||||||
}
|
}
|
||||||
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -107,6 +107,8 @@ import {
|
|||||||
FileProtectOutline,
|
FileProtectOutline,
|
||||||
HistoryOutline,
|
HistoryOutline,
|
||||||
UserAddOutline,
|
UserAddOutline,
|
||||||
|
SafetyCertificateOutline,
|
||||||
|
PlusCircleOutline,
|
||||||
AuditOutline
|
AuditOutline
|
||||||
} from '@ant-design/icons-angular/icons';
|
} from '@ant-design/icons-angular/icons';
|
||||||
import { QR_DEFULAT_CONFIG } from '@delon/abc/qr';
|
import { QR_DEFULAT_CONFIG } from '@delon/abc/qr';
|
||||||
@ -199,5 +201,7 @@ export const ICONS_AUTO = [
|
|||||||
FileProtectOutline,
|
FileProtectOutline,
|
||||||
HistoryOutline,
|
HistoryOutline,
|
||||||
UserAddOutline,
|
UserAddOutline,
|
||||||
|
SafetyCertificateOutline,
|
||||||
|
PlusCircleOutline,
|
||||||
AuditOutline
|
AuditOutline
|
||||||
];
|
];
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import org.dromara.maxkey.configuration.ApplicationConfig;
|
|||||||
import org.dromara.maxkey.constants.ConstsLoginType;
|
import org.dromara.maxkey.constants.ConstsLoginType;
|
||||||
import org.dromara.maxkey.entity.*;
|
import org.dromara.maxkey.entity.*;
|
||||||
import org.dromara.maxkey.entity.idm.UserInfo;
|
import org.dromara.maxkey.entity.idm.UserInfo;
|
||||||
|
import org.dromara.maxkey.passkey.config.PasskeyProperties;
|
||||||
import org.dromara.maxkey.password.onetimepwd.AbstractOtpAuthn;
|
import org.dromara.maxkey.password.onetimepwd.AbstractOtpAuthn;
|
||||||
import org.dromara.maxkey.password.sms.SmsOtpAuthnService;
|
import org.dromara.maxkey.password.sms.SmsOtpAuthnService;
|
||||||
import org.dromara.maxkey.persistence.service.SocialsAssociatesService;
|
import org.dromara.maxkey.persistence.service.SocialsAssociatesService;
|
||||||
@ -73,6 +74,9 @@ public class LoginEntryPoint {
|
|||||||
@Autowired
|
@Autowired
|
||||||
ApplicationConfig applicationConfig;
|
ApplicationConfig applicationConfig;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
PasskeyProperties passkeyProperties;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
AbstractAuthenticationProvider authenticationProvider ;
|
AbstractAuthenticationProvider authenticationProvider ;
|
||||||
|
|
||||||
@ -134,6 +138,8 @@ public class LoginEntryPoint {
|
|||||||
model.put("otpType", tfaOtpAuthn.getOtpType());
|
model.put("otpType", tfaOtpAuthn.getOtpType());
|
||||||
model.put("otpInterval", tfaOtpAuthn.getInterval());
|
model.put("otpInterval", tfaOtpAuthn.getInterval());
|
||||||
}
|
}
|
||||||
|
model.put("passkeyEnabled", passkeyProperties.isEnabled());
|
||||||
|
model.put("passkeyAllowedOrigins", passkeyProperties.getRelyingParty().getAllowedOrigins());
|
||||||
|
|
||||||
if( applicationConfig.getLoginConfig().isKerberos()){
|
if( applicationConfig.getLoginConfig().isKerberos()){
|
||||||
model.put("userDomainUrlJson", kerberosService.buildKerberosProxys());
|
model.put("userDomainUrlJson", kerberosService.buildKerberosProxys());
|
||||||
|
|||||||
@ -90,10 +90,10 @@ maxkey.notices.visible =false
|
|||||||
############################################################################
|
############################################################################
|
||||||
# Passkey Configuration #
|
# Passkey Configuration #
|
||||||
############################################################################
|
############################################################################
|
||||||
maxkey.passkey.enabled=true
|
maxkey.login.passkey.enabled=true
|
||||||
maxkey.passkey.relying-party.name=MaxKey
|
maxkey.login.passkey.relying-party.name=MaxKey
|
||||||
maxkey.passkey.relying-party.id=localhost
|
maxkey.login.passkey.relying-party.id=localhost
|
||||||
maxkey.passkey.relying-party.allowed-origins=http://localhost:8527,http://localhost:8080,http://localhost
|
maxkey.login.passkey.relying-party.allowed-origins=http://localhost
|
||||||
############################################################################
|
############################################################################
|
||||||
#ssl configuration #
|
#ssl configuration #
|
||||||
############################################################################
|
############################################################################
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user