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;
|
}
|
}
|
}
|