mirror of
https://gitee.com/dromara/MaxKey.git
synced 2025-12-06 17:08:29 +08:00
Compare commits
7 Commits
cfce5f7b1b
...
d43cc4b817
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d43cc4b817 | ||
|
|
fad7191ea7 | ||
|
|
b652781165 | ||
|
|
10c5b46810 | ||
|
|
a5f8d15b2d | ||
|
|
4df7f81dca | ||
|
|
1cb5ab7abd |
@ -132,7 +132,7 @@ App Management UI
|
||||
|
||||
| Version | Date | Download URL |
|
||||
| -------- | :----- | :---- |
|
||||
| v 4.1.8 | 2024/08/01 | <a href="https://www.maxkey.top/zh/about/download.html" target="_blank">Download</a> |
|
||||
| v 4.1.9 | 2025/10/10 | <a href="https://www.maxkey.top/zh/about/download.html" target="_blank">Download</a> |
|
||||
|
||||
|
||||
# Install
|
||||
|
||||
@ -133,7 +133,7 @@ App Management UI
|
||||
|
||||
| Version | Date | Download URL |
|
||||
| -------- | :----- | :---- |
|
||||
| v 4.1.8 | 2024/08/01 | <a href="https://www.maxkey.top/zh/about/download.html" target="_blank">Download</a> |
|
||||
| v 4.1.9 | 2025/10/10 | <a href="https://www.maxkey.top/zh/about/download.html" target="_blank">Download</a> |
|
||||
|
||||
|
||||
# Install
|
||||
|
||||
@ -136,7 +136,7 @@ MaxKey <b>遵循 Apache License, Version 2.0 开源免费</b>,开源、安全
|
||||
|
||||
| 版本 | 日期 | 下载 |
|
||||
| -------- | :----- | :---- |
|
||||
| v 4.1.8 | 2024/08/01 | <a href="https://www.maxkey.top/zh/about/download.html" target="_blank">下载</a>|
|
||||
| v 4.1.9 | 2025/10/10 | <a href="https://www.maxkey.top/zh/about/download.html" target="_blank">下载</a>|
|
||||
|
||||
|
||||
# 安装部署
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
MaxKey v 4.1.9 GA 2025/10/*
|
||||
MaxKey v 4.1.9 GA 2025/10/10
|
||||
*(MAXKEY-250501) #248 最新镜像 启动登陆异常
|
||||
*(MAXKEY-250502) 仪表盘显示和数据优化,增加totalUsers、totalDepts、totalApps统计
|
||||
*(MAXKEY-250503) 开源之夏-官方操作手册 by xiaoyvhuv
|
||||
@ -11,7 +11,10 @@
|
||||
*(MAXKEY-250510) 管理端增加官方网站链接菜单
|
||||
*(MAXKEY-250511) mybatis-jpa-extra升级优化
|
||||
*(MAXKEY-250512) release.bat构建脚本优化
|
||||
*(MAXKEY-250513) 依赖项引用、更新和升级
|
||||
*(MAXKEY-250513) 社交服务 ->社交登录
|
||||
*(MAXKEY-250514) 国际化切换图标修改,English abbr EN,zh-CN abbr CN
|
||||
*(MAXKEY-250515) sql文件数据结构和数据合并成一个问题maxkey.sql
|
||||
*(MAXKEY-250516) 依赖项引用、更新和升级
|
||||
springVersion 6.2.11
|
||||
springBootVersion 3.4.10
|
||||
springSecurityVersion 6.5.5
|
||||
|
||||
@ -3,5 +3,4 @@ create database if not exists `maxkey` /*!40100 DEFAULT CHARACTER SET utf8mb4 C
|
||||
use maxkey ;
|
||||
|
||||
source /docker-entrypoint-initdb.d/latest/maxkey.sql ;
|
||||
source /docker-entrypoint-initdb.d/latest/maxkey_data.sql ;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -15,7 +15,7 @@
|
||||
# */
|
||||
#maxkey properties
|
||||
group =org.dromara.maxkey
|
||||
version =4.1.8
|
||||
version =4.1.9
|
||||
vendor =https://www.maxkey.top
|
||||
author =MaxKeyTop
|
||||
githubUrl =https://github.com/dromara/MaxKey
|
||||
|
||||
@ -21,7 +21,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
/**
|
||||
* Passkey配置属性
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "maxkey.passkey")
|
||||
@ConfigurationProperties(prefix = "maxkey.login.passkey")
|
||||
public class PasskeyProperties {
|
||||
|
||||
/**
|
||||
|
||||
@ -488,7 +488,7 @@ public class PasskeyServiceImpl implements PasskeyService {
|
||||
|
||||
// 标记挑战为已使用
|
||||
challenge.setStatus(1);
|
||||
passkeyChallengeService.saveChallenge(challenge);
|
||||
passkeyChallengeService.validateAndConsumeChallenge(challenge.getId(),challenge.getChallengeType());
|
||||
|
||||
_logger.debug("Authentication verification completed successfully");
|
||||
return result;
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
{{ 'app.login.login' | i18n }}
|
||||
</button>
|
||||
</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>
|
||||
<i nz-icon nzType="safety-certificate" nzTheme="outline"></i>
|
||||
{{ 'mxk.login.passkey-login' | i18n }}
|
||||
|
||||
@ -56,6 +56,8 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
||||
loading = false;
|
||||
passwordVisible = false;
|
||||
qrexpire = false;
|
||||
passkeyEnabled = false;
|
||||
passkeyAllowedOrigins = [];
|
||||
imageCaptcha = '';
|
||||
captchaType = '';
|
||||
state = '';
|
||||
@ -136,6 +138,25 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
||||
this.socials = res.data.socials;
|
||||
this.state = res.data.state;
|
||||
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') {
|
||||
//清除校验规则
|
||||
this.form.get('captcha')?.clearValidators();
|
||||
@ -537,10 +558,12 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// 检查是否是没有注册 Passkey 的错误
|
||||
if (errorMessage.includes('没有注册任何 Passkey') ||
|
||||
if (
|
||||
errorMessage.includes('没有注册任何 Passkey') ||
|
||||
errorMessage.includes('No Passkeys registered') ||
|
||||
errorMessage.includes('还没有注册任何 Passkey') ||
|
||||
errorMessage.includes('系统中还没有注册任何 Passkey')) {
|
||||
errorMessage.includes('系统中还没有注册任何 Passkey')
|
||||
) {
|
||||
// 直接显示友好提示并返回,不抛出错误避免被全局拦截器捕获
|
||||
this.msg.warning('还未注册 Passkey,请注册 Passkey');
|
||||
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
||||
@ -555,10 +578,12 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
||||
console.error('Failed to get auth options:', authOptionsResponse);
|
||||
// 检查是否是没有注册 Passkey 的错误
|
||||
const errorMessage = authOptionsResponse?.message || '获取认证选项失败';
|
||||
if (errorMessage.includes('没有注册任何 Passkey') ||
|
||||
if (
|
||||
errorMessage.includes('没有注册任何 Passkey') ||
|
||||
errorMessage.includes('No Passkeys registered') ||
|
||||
errorMessage.includes('还没有注册任何 Passkey') ||
|
||||
errorMessage.includes('系统中还没有注册任何 Passkey')) {
|
||||
errorMessage.includes('系统中还没有注册任何 Passkey')
|
||||
) {
|
||||
// 直接显示友好提示并返回,不抛出错误避免被全局拦截器捕获
|
||||
this.msg.warning('还未注册 Passkey,请注册 Passkey');
|
||||
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('Available authenticators will be queried automatically');
|
||||
|
||||
const credential = await navigator.credentials.get({
|
||||
const credential = (await navigator.credentials.get({
|
||||
publicKey: convertedOptions
|
||||
}) as PublicKeyCredential;
|
||||
})) as PublicKeyCredential;
|
||||
|
||||
if (!credential) {
|
||||
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);
|
||||
throw new Error('认证成功但用户数据无效');
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('=== PASSKEY LOGIN ERROR ===');
|
||||
console.error('Error type:', error.constructor.name);
|
||||
@ -707,14 +731,17 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
||||
console.error('Full error object:', error);
|
||||
|
||||
// 检查是否是没有注册 Passkey 的错误
|
||||
if (error.message && (error.message.includes('PASSKEY_NOT_REGISTERED') ||
|
||||
if (
|
||||
error.message &&
|
||||
(error.message.includes('PASSKEY_NOT_REGISTERED') ||
|
||||
error.message.includes('没有找到可用的凭据') ||
|
||||
error.message.includes('No credentials available') ||
|
||||
error.message.includes('用户未注册') ||
|
||||
error.message.includes('credential not found') ||
|
||||
error.message.includes('没有注册任何 Passkey') ||
|
||||
error.message.includes('No Passkeys registered') ||
|
||||
error.message.includes('还没有注册任何 Passkey'))) {
|
||||
error.message.includes('还没有注册任何 Passkey'))
|
||||
) {
|
||||
this.msg.warning('还未注册 Passkey,请注册 Passkey');
|
||||
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
||||
return;
|
||||
@ -747,10 +774,10 @@ export class UserLoginComponent implements OnInit, OnDestroy {
|
||||
break;
|
||||
default:
|
||||
console.error('Unknown WebAuthn error');
|
||||
this.msg.error('Passkey 登录失败:' + (error.message || '请重试或使用其他登录方式'));
|
||||
this.msg.error(`Passkey 登录失败:${error.message || '请重试或使用其他登录方式'}`);
|
||||
}
|
||||
} else {
|
||||
this.msg.error('Passkey 登录失败:' + (error.message || '请重试或使用其他登录方式'));
|
||||
this.msg.error(`Passkey 登录失败:${error.message || '请重试或使用其他登录方式'}`);
|
||||
}
|
||||
console.log('=== PASSKEY LOGIN DEBUG END ===');
|
||||
} finally {
|
||||
|
||||
@ -27,5 +27,5 @@ export const CONSTS = {
|
||||
REDIRECT_URI: 'redirect_uri',
|
||||
REMEMBER: 'remember_me',
|
||||
TOKEN: '_token',
|
||||
VERSION: 'v4.1.8 GA'
|
||||
VERSION: 'v4.1.9 GA'
|
||||
};
|
||||
|
||||
@ -107,6 +107,8 @@ import {
|
||||
FileProtectOutline,
|
||||
HistoryOutline,
|
||||
UserAddOutline,
|
||||
SafetyCertificateOutline,
|
||||
PlusCircleOutline,
|
||||
AuditOutline
|
||||
} from '@ant-design/icons-angular/icons';
|
||||
import { QR_DEFULAT_CONFIG } from '@delon/abc/qr';
|
||||
@ -199,5 +201,7 @@ export const ICONS_AUTO = [
|
||||
FileProtectOutline,
|
||||
HistoryOutline,
|
||||
UserAddOutline,
|
||||
SafetyCertificateOutline,
|
||||
PlusCircleOutline,
|
||||
AuditOutline
|
||||
];
|
||||
|
||||
@ -19,5 +19,5 @@ export const CONSTS = {
|
||||
INST: 'inst_mgt',
|
||||
REDIRECT_URI: 'redirect_uri',
|
||||
REMEMBER: 'remember',
|
||||
VERSION: 'v4.1.8 GA'
|
||||
VERSION: 'v4.1.9 GA'
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#端口号
|
||||
application:
|
||||
name: maxkey-gateway-server
|
||||
formatted-version: v4.1.8 GA
|
||||
formatted-version: v4.1.9 GA
|
||||
server:
|
||||
port: 9000
|
||||
spring:
|
||||
|
||||
@ -33,6 +33,7 @@ import org.dromara.maxkey.configuration.ApplicationConfig;
|
||||
import org.dromara.maxkey.constants.ConstsLoginType;
|
||||
import org.dromara.maxkey.entity.*;
|
||||
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.sms.SmsOtpAuthnService;
|
||||
import org.dromara.maxkey.persistence.service.SocialsAssociatesService;
|
||||
@ -73,6 +74,9 @@ public class LoginEntryPoint {
|
||||
@Autowired
|
||||
ApplicationConfig applicationConfig;
|
||||
|
||||
@Autowired
|
||||
PasskeyProperties passkeyProperties;
|
||||
|
||||
@Autowired
|
||||
AbstractAuthenticationProvider authenticationProvider ;
|
||||
|
||||
@ -134,6 +138,8 @@ public class LoginEntryPoint {
|
||||
model.put("otpType", tfaOtpAuthn.getOtpType());
|
||||
model.put("otpInterval", tfaOtpAuthn.getInterval());
|
||||
}
|
||||
model.put("passkeyEnabled", passkeyProperties.isEnabled());
|
||||
model.put("passkeyAllowedOrigins", passkeyProperties.getRelyingParty().getAllowedOrigins());
|
||||
|
||||
if( applicationConfig.getLoginConfig().isKerberos()){
|
||||
model.put("userDomainUrlJson", kerberosService.buildKerberosProxys());
|
||||
|
||||
@ -88,6 +88,13 @@ maxkey.ipaddress.whitelist =false
|
||||
#notices show
|
||||
maxkey.notices.visible =false
|
||||
############################################################################
|
||||
# Passkey Configuration #
|
||||
############################################################################
|
||||
maxkey.login.passkey.enabled=true
|
||||
maxkey.login.passkey.relying-party.name=MaxKey
|
||||
maxkey.login.passkey.relying-party.id=localhost
|
||||
maxkey.login.passkey.relying-party.allowed-origins=http://localhost
|
||||
############################################################################
|
||||
#ssl configuration #
|
||||
############################################################################
|
||||
#server.ssl.key-store=maxkeyserver.keystore
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
#MaxKey Title and Version #
|
||||
############################################################################
|
||||
application.title =MaxKey
|
||||
application.formatted-version =v4.1.8 GA
|
||||
application.formatted-version =v4.1.9 GA
|
||||
#for dynamic service discovery
|
||||
spring.application.name =maxkey
|
||||
############################################################################
|
||||
@ -28,11 +28,3 @@ spring.main.banner-mode =log
|
||||
#spring.profiles.active maxkey #
|
||||
############################################################################
|
||||
spring.profiles.active =${SERVER_PROFILES:maxkey}
|
||||
|
||||
############################################################################
|
||||
# Passkey Configuration #
|
||||
############################################################################
|
||||
maxkey.passkey.enabled=true
|
||||
maxkey.passkey.relying-party.name=MaxKey
|
||||
maxkey.passkey.relying-party.id=localhost
|
||||
maxkey.passkey.relying-party.allowed-origins=http://localhost:8527,http://localhost:8080
|
||||
@ -16,7 +16,7 @@
|
||||
#MaxKey Title and Version #
|
||||
############################################################################
|
||||
application.title =MaxKey-Mgt
|
||||
application.formatted-version =v4.1.8 GA
|
||||
application.formatted-version =v4.1.9 GA
|
||||
#for dynamic service discovery
|
||||
spring.application.name =maxkey-mgt
|
||||
############################################################################
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
#MaxKey Title and Version #
|
||||
############################################################################
|
||||
application.title =MaxKey-OpenApi
|
||||
application.formatted-version =v4.1.8 GA
|
||||
application.formatted-version =v4.1.9 GA
|
||||
#for dynamic service discovery
|
||||
spring.application.name =maxkey-openapi
|
||||
############################################################################
|
||||
|
||||
@ -3,7 +3,7 @@ echo set env
|
||||
set JAVA_HOME=C:\IDE\jdk-17
|
||||
set GRADLE_HOME=C:\ide\gradle-8.8
|
||||
|
||||
set MXK_VERSION=4.1.8
|
||||
set MXK_VERSION=4.1.9
|
||||
set MXK_REPOSITORY=maxkeytop
|
||||
|
||||
call %JAVA_HOME%/bin/java -version
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,52 +0,0 @@
|
||||
-- MaxKey Passkey 模块数据库表创建脚本
|
||||
-- 创建用户 Passkey 表和挑战表
|
||||
|
||||
-- 用户 Passkey 表
|
||||
CREATE TABLE IF NOT EXISTS mxk_user_passkeys (
|
||||
id VARCHAR(40) NOT NULL COMMENT '主键ID',
|
||||
user_id VARCHAR(40) NOT NULL COMMENT '用户ID',
|
||||
credential_id VARCHAR(500) NOT NULL COMMENT '凭据ID(Base64编码)',
|
||||
public_key TEXT NOT NULL COMMENT '公钥数据(Base64编码)',
|
||||
display_name VARCHAR(100) COMMENT '显示名称',
|
||||
device_type VARCHAR(50) DEFAULT 'unknown' COMMENT '设备类型',
|
||||
signature_count BIGINT DEFAULT 0 COMMENT '签名计数器',
|
||||
created_date DATETIME NOT NULL COMMENT '创建时间',
|
||||
last_used_date DATETIME COMMENT '最后使用时间',
|
||||
aaguid VARCHAR(100) COMMENT 'AAGUID',
|
||||
inst_id VARCHAR(40) DEFAULT '1' COMMENT '机构ID',
|
||||
status INT DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_credential_id (credential_id),
|
||||
KEY idx_user_id (user_id),
|
||||
KEY idx_inst_id (inst_id),
|
||||
KEY idx_created_date (created_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户Passkey凭据表';
|
||||
|
||||
-- Passkey 挑战表
|
||||
CREATE TABLE IF NOT EXISTS mxk_passkey_challenges (
|
||||
id VARCHAR(40) NOT NULL COMMENT '挑战ID',
|
||||
user_id VARCHAR(40) COMMENT '用户ID(认证时可为空)',
|
||||
challenge TEXT NOT NULL COMMENT '挑战数据(Base64编码)',
|
||||
challenge_type VARCHAR(20) NOT NULL COMMENT '挑战类型:REGISTRATION-注册,AUTHENTICATION-认证',
|
||||
created_date DATETIME NOT NULL COMMENT '创建时间',
|
||||
expires_date DATETIME NOT NULL COMMENT '过期时间',
|
||||
status INT DEFAULT 0 COMMENT '状态:0-未使用,1-已使用',
|
||||
inst_id VARCHAR(40) DEFAULT '1' COMMENT '机构ID',
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_user_id (user_id),
|
||||
KEY idx_challenge_type (challenge_type),
|
||||
KEY idx_expires_date (expires_date),
|
||||
KEY idx_inst_id (inst_id),
|
||||
KEY idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Passkey挑战表';
|
||||
|
||||
-- 创建索引以优化查询性能
|
||||
CREATE INDEX idx_user_passkeys_user_status ON mxk_user_passkeys(user_id, status);
|
||||
CREATE INDEX idx_passkey_challenges_expires_status ON mxk_passkey_challenges(expires_date, status);
|
||||
CREATE INDEX idx_passkey_challenges_user_type ON mxk_passkey_challenges(user_id, challenge_type);
|
||||
|
||||
-- 插入示例数据(可选)
|
||||
-- INSERT INTO mxk_user_passkeys (id, user_id, credential_id, public_key, display_name, device_type, signature_count, created_date, inst_id)
|
||||
-- VALUES ('test_passkey_1', 'test_user_1', 'test_credential_id_1', 'test_public_key_1', 'Test Device', 'platform', 0, NOW(), '1');
|
||||
|
||||
COMMIT;
|
||||
Loading…
x
Reference in New Issue
Block a user