From bd6b518aae61608ddc2d82b43ccc283dc95b9c54 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 11 三月 2026 13:59:33 +0800
Subject: [PATCH] #
---
src/main/java/com/zy/common/auth/PasskeyChallengeManager.java | 138 ++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 138 insertions(+), 0 deletions(-)
diff --git a/src/main/java/com/zy/common/auth/PasskeyChallengeManager.java b/src/main/java/com/zy/common/auth/PasskeyChallengeManager.java
new file mode 100644
index 0000000..cbd5c82
--- /dev/null
+++ b/src/main/java/com/zy/common/auth/PasskeyChallengeManager.java
@@ -0,0 +1,138 @@
+package com.zy.common.auth;
+
+import com.core.common.Cools;
+import org.springframework.stereotype.Component;
+
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class PasskeyChallengeManager {
+
+ private static final long EXPIRE_MILLIS = 5 * 60 * 1000L;
+
+ private final SecureRandom secureRandom = new SecureRandom();
+ private final ConcurrentHashMap<String, ChallengeState> holders = new ConcurrentHashMap<>();
+
+ public ChallengeState createRegistration(Long userId, String origin, String rpId) {
+ return create(Purpose.REGISTRATION, userId, origin, rpId);
+ }
+
+ public ChallengeState createAuthentication(Long userId, String origin, String rpId) {
+ return create(Purpose.AUTHENTICATION, userId, origin, rpId);
+ }
+
+ public ChallengeState get(String ticket, Purpose purpose) {
+ if (Cools.isEmpty(ticket) || purpose == null) {
+ return null;
+ }
+ ChallengeState state = holders.get(ticket);
+ if (state == null) {
+ return null;
+ }
+ if (state.expireAt < System.currentTimeMillis() || state.purpose != purpose) {
+ holders.remove(ticket);
+ return null;
+ }
+ return state;
+ }
+
+ public void remove(String ticket) {
+ if (!Cools.isEmpty(ticket)) {
+ holders.remove(ticket);
+ }
+ }
+
+ private ChallengeState create(Purpose purpose, Long userId, String origin, String rpId) {
+ cleanup();
+ ChallengeState state;
+ do {
+ state = new ChallengeState(
+ randomTicket(),
+ randomChallenge(),
+ purpose,
+ userId,
+ origin,
+ rpId,
+ System.currentTimeMillis() + EXPIRE_MILLIS
+ );
+ } while (holders.putIfAbsent(state.ticket, state) != null);
+ return state;
+ }
+
+ private void cleanup() {
+ long now = System.currentTimeMillis();
+ for (Map.Entry<String, ChallengeState> entry : holders.entrySet()) {
+ if (entry.getValue().expireAt < now) {
+ holders.remove(entry.getKey());
+ }
+ }
+ }
+
+ private String randomTicket() {
+ byte[] bytes = new byte[24];
+ secureRandom.nextBytes(bytes);
+ return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
+ }
+
+ private String randomChallenge() {
+ byte[] bytes = new byte[32];
+ secureRandom.nextBytes(bytes);
+ return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
+ }
+
+ public enum Purpose {
+ REGISTRATION,
+ AUTHENTICATION
+ }
+
+ public static final class ChallengeState {
+ private final String ticket;
+ private final String challenge;
+ private final Purpose purpose;
+ private final Long userId;
+ private final String origin;
+ private final String rpId;
+ private final long expireAt;
+
+ private ChallengeState(String ticket, String challenge, Purpose purpose, Long userId, String origin, String rpId, long expireAt) {
+ this.ticket = ticket;
+ this.challenge = challenge;
+ this.purpose = purpose;
+ this.userId = userId;
+ this.origin = origin;
+ this.rpId = rpId;
+ this.expireAt = expireAt;
+ }
+
+ public String getTicket() {
+ return ticket;
+ }
+
+ public String getChallenge() {
+ return challenge;
+ }
+
+ public Purpose getPurpose() {
+ return purpose;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public String getOrigin() {
+ return origin;
+ }
+
+ public String getRpId() {
+ return rpId;
+ }
+
+ public long getExpireAt() {
+ return expireAt;
+ }
+ }
+}
--
Gitblit v1.9.1