fix: 修复PasskeyRegistrationEndpoint中@PathVariable注解缺少value属性的问题

This commit is contained in:
Spock12138 2025-09-12 12:23:50 +08:00
parent 33734f1387
commit 0733ce2d17
17 changed files with 23 additions and 156 deletions

View File

@ -54,7 +54,6 @@ allprojects {
targetCompatibility = 17 targetCompatibility = 17
compileJava.options.encoding = 'UTF-8' compileJava.options.encoding = 'UTF-8'
compileJava.options.compilerArgs += ['-parameters']
eclipse { eclipse {
/*设置工程字符集*/ /*设置工程字符集*/
jdt { jdt {

View File

@ -17,6 +17,7 @@
package org.dromara.maxkey.persistence.mapper; package org.dromara.maxkey.persistence.mapper;
import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
@ -70,7 +71,7 @@ public interface PasskeyChallengeMapper extends IJpaMapper<PasskeyChallenge> {
@Result(column = "status", property = "status"), @Result(column = "status", property = "status"),
@Result(column = "inst_id", property = "instId") @Result(column = "inst_id", property = "instId")
}) })
PasskeyChallenge findLatestByUserIdAndType(String userId, String challengeType); PasskeyChallenge findLatestByUserIdAndType(@Param("userId") String userId, @Param("challengeType") String challengeType);
/** /**
* 删除指定挑战ID的记录 * 删除指定挑战ID的记录
@ -106,7 +107,7 @@ public interface PasskeyChallengeMapper extends IJpaMapper<PasskeyChallenge> {
* @return 清理的记录数 * @return 清理的记录数
*/ */
@Delete("DELETE FROM mxk_passkey_challenges WHERE user_id = #{userId} AND challenge_type = #{challengeType}") @Delete("DELETE FROM mxk_passkey_challenges WHERE user_id = #{userId} AND challenge_type = #{challengeType}")
int deleteByUserIdAndType(String userId, String challengeType); int deleteByUserIdAndType(@Param("userId") String userId, @Param("challengeType") String challengeType);
/** /**
* 统计指定用户的挑战数量 * 统计指定用户的挑战数量

View File

@ -19,6 +19,7 @@ package org.dromara.maxkey.persistence.mapper;
import java.util.List; import java.util.List;
import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
@ -102,7 +103,7 @@ public interface UserPasskeyMapper extends IJpaMapper<UserPasskey> {
@Result(column = "status", property = "status"), @Result(column = "status", property = "status"),
@Result(column = "inst_id", property = "instId") @Result(column = "inst_id", property = "instId")
}) })
UserPasskey findByUserIdAndCredentialId(String userId, String credentialId); UserPasskey findByUserIdAndCredentialId(@Param("userId") String userId, @Param("credentialId") String credentialId);
/** /**
* 更新签名计数器 * 更新签名计数器
@ -112,7 +113,7 @@ public interface UserPasskeyMapper extends IJpaMapper<UserPasskey> {
* @return 更新的记录数 * @return 更新的记录数
*/ */
@Update("UPDATE mxk_user_passkeys SET signature_count = #{signatureCount}, last_used_date = NOW() WHERE credential_id = #{credentialId}") @Update("UPDATE mxk_user_passkeys SET signature_count = #{signatureCount}, last_used_date = NOW() WHERE credential_id = #{credentialId}")
int updateSignatureCount(String credentialId, Long signatureCount); int updateSignatureCount(@Param("credentialId") String credentialId, @Param("signatureCount") Long signatureCount);
/** /**
* 物理删除Passkey * 物理删除Passkey
@ -122,7 +123,7 @@ public interface UserPasskeyMapper extends IJpaMapper<UserPasskey> {
* @return 删除的记录数 * @return 删除的记录数
*/ */
@Delete("DELETE FROM mxk_user_passkeys WHERE user_id = #{userId} AND credential_id = #{credentialId}") @Delete("DELETE FROM mxk_user_passkeys WHERE user_id = #{userId} AND credential_id = #{credentialId}")
int deleteByUserIdAndCredentialId(String userId, String credentialId); int deleteByUserIdAndCredentialId(@Param("userId") String userId, @Param("credentialId") String credentialId);
/** /**
* 物理删除过期的Passkey记录 * 物理删除过期的Passkey记录

View File

@ -1,5 +1,5 @@
/* /*
* Copyright [2024] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright [2024] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,106 +0,0 @@
# PasskeyServiceImpl 重构总结
## 重构目标
本次重构旨在提高 `PasskeyServiceImpl` 类的代码可读性、可维护性和可测试性,通过方法拆分和工具类提取来优化代码结构。
## 主要改进
### 1. 方法拆分
#### 原有的大方法被拆分为更小、职责单一的方法:
**`generateRegistrationOptions` 方法拆分:**
- `generateAndSaveChallenge()` - 生成并保存挑战
- `buildRegistrationOptions()` - 构建注册选项
- `buildRelyingPartyInfo()` - 构建RP信息
- `buildUserInfo()` - 构建用户信息
- `buildPublicKeyCredentialParams()` - 构建公钥凭据参数
- `buildAuthenticatorSelection()` - 构建认证器选择标准
- `buildExcludeCredentials()` - 构建排除凭据列表
**`verifyRegistrationResponse` 方法拆分:**
- `validateChallenge()` - 验证挑战
- `parseRegistrationResponse()` - 解析注册响应
- `createServerProperty()` - 创建服务器属性
- `performRegistrationVerification()` - 执行注册验证
- `createUserPasskey()` - 创建UserPasskey对象
**`verifyAuthenticationResponse` 方法拆分:**
- `validateChallenge()` - 验证挑战(复用)
- `parseAuthenticationResponse()` - 解析认证响应
- `validateChallengeUserMatch()` - 验证挑战与用户匹配
- `performAuthenticationVerification()` - 执行认证验证
- `buildCredentialRecord()` - 构建凭据记录
### 2. 工具类提取
创建了 `PasskeyUtils` 工具类,提取了通用的验证和构建逻辑:
- `generateChallenge()` - 生成挑战
- `buildRelyingPartyInfo()` - 构建RP信息
- `buildUserInfo()` - 构建用户信息
- `buildPublicKeyCredentialParams()` - 构建公钥凭据参数
- `buildAuthenticatorSelection()` - 构建认证器选择
- `buildCredentialList()` - 构建凭据列表
- `createServerProperty()` - 创建服务器属性
- `parseAndValidateOrigin()` - 解析和验证origin
- `base64Decode()` / `base64Encode()` - Base64编解码
- `validateNotEmpty()` / `validateNotNull()` - 参数验证
### 3. 常量定义
添加了常量定义以提高代码可读性:
```java
private static final String CHALLENGE_TYPE_REGISTRATION = "registration";
private static final String CHALLENGE_TYPE_AUTHENTICATION = "authentication";
private static final String CREDENTIAL_TYPE_PUBLIC_KEY = "public-key";
private static final String DEFAULT_INST_ID = "1";
private static final String DEFAULT_DEVICE_NAME = "Unknown Device";
```
### 4. 内部类优化
添加了 `AuthenticationResponseData` 内部类来封装认证响应数据,提高代码的组织性。
### 5. 日志优化
- 简化了冗长的调试日志
- 将详细的调试信息改为debug级别
- 保留了关键的错误和警告日志
## 重构效果
### 代码质量提升:
1. **可读性**:方法职责更加单一,代码逻辑更清晰
2. **可维护性**:功能模块化,便于修改和扩展
3. **可测试性**:小方法更容易进行单元测试
4. **复用性**:工具类方法可在其他地方复用
### 代码行数优化:
- 原文件约900行
- 重构后主文件约774行
- 新增工具类约238行
- 总体代码更加模块化和组织化
## 文件结构
```
passkey/
├── service/impl/
│ └── PasskeyServiceImpl.java (重构后的主实现类)
└── util/
└── PasskeyUtils.java (新增的工具类)
```
## 向后兼容性
本次重构保持了所有公共接口的兼容性,不会影响现有的调用代码。所有的重构都是内部实现的优化,对外部调用者透明。
## 后续建议
1. 为新的私有方法添加单元测试
2. 考虑将一些配置参数提取到配置文件中
3. 可以进一步优化异常处理机制
4. 考虑添加更多的参数验证逻辑

View File

@ -1,5 +1,5 @@
/* /*
* Copyright [2024] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright [2024] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright [2024] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -156,7 +156,7 @@ public class PasskeyRegistrationEndpoint {
*/ */
@GetMapping("/list/{userId}") @GetMapping("/list/{userId}")
public ResponseEntity<?> getUserPasskeys( public ResponseEntity<?> getUserPasskeys(
@PathVariable String userId, @PathVariable("userId") String userId,
HttpServletRequest httpRequest, HttpServletRequest httpRequest,
HttpServletResponse httpResponse) { HttpServletResponse httpResponse) {
@ -195,8 +195,8 @@ public class PasskeyRegistrationEndpoint {
*/ */
@DeleteMapping("/delete/{userId}/{credentialId}") @DeleteMapping("/delete/{userId}/{credentialId}")
public ResponseEntity<?> deletePasskey( public ResponseEntity<?> deletePasskey(
@PathVariable String userId, @PathVariable("userId") String userId,
@PathVariable String credentialId, @PathVariable("credentialId") String credentialId,
HttpServletRequest httpRequest, HttpServletRequest httpRequest,
HttpServletResponse httpResponse) { HttpServletResponse httpResponse) {
@ -240,7 +240,7 @@ public class PasskeyRegistrationEndpoint {
*/ */
@GetMapping("/stats/{userId}") @GetMapping("/stats/{userId}")
public ResponseEntity<?> getPasskeyStats( public ResponseEntity<?> getPasskeyStats(
@PathVariable String userId, @PathVariable("userId") String userId,
HttpServletRequest httpRequest, HttpServletRequest httpRequest,
HttpServletResponse httpResponse) { HttpServletResponse httpResponse) {
@ -273,7 +273,7 @@ public class PasskeyRegistrationEndpoint {
*/ */
@GetMapping("/support/{userId}") @GetMapping("/support/{userId}")
public ResponseEntity<?> checkPasskeySupport( public ResponseEntity<?> checkPasskeySupport(
@PathVariable String userId, @PathVariable("userId") String userId,
HttpServletRequest httpRequest, HttpServletRequest httpRequest,
HttpServletResponse httpResponse) { HttpServletResponse httpResponse) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright [2024] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright [2024] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright [2024] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/** /**
* Copyright [2024] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,6 +1,6 @@
{ {
"name": "maxkey", "name": "maxkey",
"version": "4.1.0", "version": "4.1.x",
"description": "Leading-Edge IAM Identity and Access Management", "description": "Leading-Edge IAM Identity and Access Management",
"author": "MaxKey <support@maxsso.net>", "author": "MaxKey <support@maxsso.net>",
"repository": { "repository": {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright [2022] [MaxKey of copyright http://www.maxkey.top] * Copyright [2025] [MaxKey of copyright http://www.maxkey.top]
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -32,26 +32,6 @@
</div> </div>
<div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md"> <div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
<div nz-row nzType="flex" nzAlign="middle" class="bg-success rounded-md"> <div nz-row nzType="flex" nzAlign="middle" class="bg-success rounded-md">
<div nz-col nzSpan="12" class="p-md text-white">
<div class="h2 mt0">{{ dayCount }}</div>
<p class="text-nowrap mb0">{{ 'mxk.home.dayCount' | i18n }}</p>
</div>
<div nz-col nzSpan="12">
<g2-mini-bar
*ngIf="simulateData"
height="35"
color="#fff"
borderWidth="3"
[padding]="[5, 30]"
[data]="simulateData"
tooltipType="mini"
(ready)="fixDark($event)"
></g2-mini-bar>
</div>
</div>
</div>
<div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
<div nz-row nzType="flex" nzAlign="middle" class="bg-orange rounded-md">
<div nz-col nzSpan="12" class="p-md text-white"> <div nz-col nzSpan="12" class="p-md text-white">
<div class="h2 mt0">{{ newUsers }}</div> <div class="h2 mt0">{{ newUsers }}</div>
<p class="text-nowrap mb0">{{ 'mxk.home.newUsers' | i18n }}</p> <p class="text-nowrap mb0">{{ 'mxk.home.newUsers' | i18n }}</p>

View File

@ -52,7 +52,6 @@ dependencies {
implementation project(":maxkey-starter:maxkey-starter-sms") implementation project(":maxkey-starter:maxkey-starter-sms")
implementation project(":maxkey-starter:maxkey-starter-social") implementation project(":maxkey-starter:maxkey-starter-social")
implementation project(":maxkey-starter:maxkey-starter-web") implementation project(":maxkey-starter:maxkey-starter-web")
implementation project(":maxkey-starter:maxkey-starter-passkey")
implementation project(":maxkey-authentications:maxkey-authentication-core") implementation project(":maxkey-authentications:maxkey-authentication-core")
implementation project(":maxkey-authentications:maxkey-authentication-provider") implementation project(":maxkey-authentications:maxkey-authentication-provider")
@ -65,11 +64,4 @@ dependencies {
implementation project(":maxkey-protocols:maxkey-protocol-oauth-2.0") implementation project(":maxkey-protocols:maxkey-protocol-oauth-2.0")
implementation project(":maxkey-protocols:maxkey-protocol-saml-2.0") implementation project(":maxkey-protocols:maxkey-protocol-saml-2.0")
implementation project(":maxkey-protocols:maxkey-protocol-jwt") implementation project(":maxkey-protocols:maxkey-protocol-jwt")
// WebAuthn4J
implementation "com.webauthn4j:webauthn4j-core:${webauthn4jVersion}"
implementation "com.webauthn4j:webauthn4j-util:${webauthn4jVersion}"
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor'
implementation 'commons-codec:commons-codec'
} }