diff --git a/src/main/java/com/kane/animo/auth/controller/PasskeyAuthorizationController.java b/src/main/java/com/kane/animo/auth/controller/PasskeyAuthorizationController.java index 29589b0..c30a93e 100644 --- a/src/main/java/com/kane/animo/auth/controller/PasskeyAuthorizationController.java +++ b/src/main/java/com/kane/animo/auth/controller/PasskeyAuthorizationController.java @@ -3,6 +3,7 @@ package com.kane.animo.auth.controller; import com.fasterxml.jackson.core.JsonProcessingException; import com.kane.animo.auth.service.PasskeyAuthorizationService; import com.kane.animo.model.R; +import com.yubico.webauthn.exception.AssertionFailedException; import com.yubico.webauthn.exception.RegistrationFailedException; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; @@ -47,7 +48,7 @@ public class PasskeyAuthorizationController { * @return 验证参数 */ @GetMapping("/assertion/options") - public R getPasskeyAssertionOptions(HttpServletRequest httpServletRequest) { + public R getPasskeyAssertionOptions(HttpServletRequest httpServletRequest) throws JsonProcessingException { return R.success(service.startPasskeyAssertion(httpServletRequest.getSession().getId())); } @@ -57,9 +58,9 @@ public class PasskeyAuthorizationController { * @param credential 凭证 */ @PostMapping("/assertion") - public R verifyPasskeyAssertion(HttpServletRequest httpServletRequest, @RequestBody String credential) { - service.finishPasskeyAssertion(httpServletRequest.getSession().getId(), credential); - return R.success(); + public R verifyPasskeyAssertion(HttpServletRequest httpServletRequest, @RequestBody String credential) throws IOException, AssertionFailedException { + String s = service.finishPasskeyAssertion(httpServletRequest.getSession().getId(), credential); + return R.success(s); } } diff --git a/src/main/java/com/kane/animo/auth/domain/UserCredential.java b/src/main/java/com/kane/animo/auth/domain/UserCredential.java index b75d37f..2debb11 100644 --- a/src/main/java/com/kane/animo/auth/domain/UserCredential.java +++ b/src/main/java/com/kane/animo/auth/domain/UserCredential.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; +import lombok.experimental.Accessors; /** * 用户凭证 @@ -11,6 +12,7 @@ import lombok.Data; * @since 2025/11/7 16:57 */ @Data +@Accessors(chain = true) @TableName("user_credential") public class UserCredential { /** @@ -30,6 +32,10 @@ public class UserCredential { * 身份标识 */ private String identity; + /** + * 传输方式 + */ + private String transports; /** * 删除标识 */ diff --git a/src/main/java/com/kane/animo/auth/service/PasskeyAuthorizationService.java b/src/main/java/com/kane/animo/auth/service/PasskeyAuthorizationService.java index d543e86..26a419a 100644 --- a/src/main/java/com/kane/animo/auth/service/PasskeyAuthorizationService.java +++ b/src/main/java/com/kane/animo/auth/service/PasskeyAuthorizationService.java @@ -1,6 +1,7 @@ package com.kane.animo.auth.service; import com.fasterxml.jackson.core.JsonProcessingException; +import com.yubico.webauthn.exception.AssertionFailedException; import com.yubico.webauthn.exception.RegistrationFailedException; import java.io.IOException; @@ -32,7 +33,7 @@ public interface PasskeyAuthorizationService { * @param id 登录id * @return 验证参数 */ - String startPasskeyAssertion(String id); + String startPasskeyAssertion(String id) throws JsonProcessingException; /** * 验证通行密钥 @@ -40,5 +41,5 @@ public interface PasskeyAuthorizationService { * @param id 登录id * @param credential 凭证 */ - void finishPasskeyAssertion(String id, String credential); + String finishPasskeyAssertion(String id, String credential) throws IOException, AssertionFailedException; } diff --git a/src/main/java/com/kane/animo/auth/service/impl/CredentialRepositoryImpl.java b/src/main/java/com/kane/animo/auth/service/impl/CredentialRepositoryImpl.java index a9dda56..437c81e 100644 --- a/src/main/java/com/kane/animo/auth/service/impl/CredentialRepositoryImpl.java +++ b/src/main/java/com/kane/animo/auth/service/impl/CredentialRepositoryImpl.java @@ -9,12 +9,14 @@ import com.kane.animo.auth.mapper.UserMapper; import com.yubico.webauthn.CredentialRepository; import com.yubico.webauthn.RegisteredCredential; import com.yubico.webauthn.RegistrationResult; +import com.yubico.webauthn.data.AuthenticatorTransport; import com.yubico.webauthn.data.ByteArray; import com.yubico.webauthn.data.PublicKeyCredentialDescriptor; import com.yubico.webauthn.data.UserIdentity; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -53,11 +55,12 @@ public class CredentialRepositoryImpl implements CredentialRepository { .eq(UserCredential::getUserId, one.getId()) .list() .stream() - .map(UserCredential::getCredential) +// .map(UserCredential::getCredential) .map(x -> { - RegisteredCredential credential = JSON.parseObject(x, RegisteredCredential.class); + RegisteredCredential credential = JSON.parseObject(x.getCredential(), RegisteredCredential.class); return PublicKeyCredentialDescriptor.builder() .id(credential.getCredentialId()) + .transports(new HashSet<>(JSON.parseArray(x.getTransports(), AuthenticatorTransport.class))) .build(); }).collect(Collectors.toSet()); } diff --git a/src/main/java/com/kane/animo/auth/service/impl/PasskeyAuthorizationServiceImpl.java b/src/main/java/com/kane/animo/auth/service/impl/PasskeyAuthorizationServiceImpl.java index 53f631f..fbd48cb 100644 --- a/src/main/java/com/kane/animo/auth/service/impl/PasskeyAuthorizationServiceImpl.java +++ b/src/main/java/com/kane/animo/auth/service/impl/PasskeyAuthorizationServiceImpl.java @@ -1,21 +1,28 @@ package com.kane.animo.auth.service.impl; import cn.dev33.satoken.stp.StpUtil; +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; +import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.kane.animo.auth.domain.User; +import com.kane.animo.auth.domain.UserCredential; +import com.kane.animo.auth.mapper.UserCredentialMapper; import com.kane.animo.auth.mapper.UserMapper; import com.kane.animo.auth.service.PasskeyAuthorizationService; +import com.kane.animo.exception.ServiceException; import com.kane.animo.util.CacheService; -import com.yubico.webauthn.FinishRegistrationOptions; -import com.yubico.webauthn.RegistrationResult; -import com.yubico.webauthn.RelyingParty; -import com.yubico.webauthn.StartRegistrationOptions; +import com.yubico.webauthn.*; import com.yubico.webauthn.data.*; +import com.yubico.webauthn.exception.AssertionFailedException; import com.yubico.webauthn.exception.RegistrationFailedException; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.TreeSet; /** * 通行密钥授权服务实现 @@ -31,6 +38,9 @@ public class PasskeyAuthorizationServiceImpl implements PasskeyAuthorizationServ @Resource private UserMapper userMapper; + @Resource + private UserCredentialMapper credentialMapper; + @Resource private CacheService cacheService; @@ -77,6 +87,17 @@ public class PasskeyAuthorizationServiceImpl implements PasskeyAuthorizationServ .request(request) .response(pkc) .build()); + UserCredential userCredential = new UserCredential() + .setUserId(user.getId()) + .setIdentity(JSON.toJSONString(request.getUser())) + .setTransports(JSON.toJSONString(result.getKeyId().getTransports().orElse(new TreeSet<>()))) + .setCredential(JSON.toJSONString(RegisteredCredential.builder() + .credentialId(result.getKeyId().getId()) + .userHandle(request.getUser().getId()) + .publicKeyCose(result.getPublicKeyCose()) + .signatureCount(result.getSignatureCount()) + .build())); + credentialMapper.insert(userCredential); cacheService.removeCache(PASSKEY_REGISTRATION_KEY + user.getId()); } @@ -87,8 +108,10 @@ public class PasskeyAuthorizationServiceImpl implements PasskeyAuthorizationServ * @return 验证参数 */ @Override - public String startPasskeyAssertion(String id) { - return ""; + public String startPasskeyAssertion(String id) throws JsonProcessingException { + AssertionRequest request = relyingParty.startAssertion(StartAssertionOptions.builder().build()); + cacheService.setCache(PASSKEY_ASSERTION_KEY + id, request.toJson()); + return request.toCredentialsGetJson(); } /** @@ -98,7 +121,35 @@ public class PasskeyAuthorizationServiceImpl implements PasskeyAuthorizationServ * @param credential 凭证 */ @Override - public void finishPasskeyAssertion(String id, String credential) { - + public String finishPasskeyAssertion(String id, String credential) throws IOException, AssertionFailedException { + String cache = cacheService.getCache(PASSKEY_ASSERTION_KEY + id, String.class); + AssertionRequest request = AssertionRequest.fromJson(cache); + PublicKeyCredential keyCredential = PublicKeyCredential.parseAssertionResponseJson(credential); + AssertionResult result = relyingParty.finishAssertion(FinishAssertionOptions.builder() + .request(request) + .response(keyCredential) + .build()); + cacheService.removeCache(PASSKEY_ASSERTION_KEY + id); + if (!result.isSuccess()){ + throw new ServiceException("验证失败"); + } + User one = new LambdaQueryChainWrapper<>(userMapper) + .eq(User::getUser, result.getUsername()) + .one(); + List list = new LambdaQueryChainWrapper<>(credentialMapper) + .eq(UserCredential::getUserId, one.getId()) + .list(); + for (UserCredential userCredential : list) { + RegisteredCredential parsed = JSON.parseObject(userCredential.getCredential(), RegisteredCredential.class); + if (parsed.getCredentialId().equals(result.getCredential().getCredentialId())){ + RegisteredCredential build = parsed.toBuilder().signatureCount(result.getSignatureCount()).build(); + new LambdaUpdateChainWrapper<>(credentialMapper) + .set(UserCredential::getCredential, JSON.toJSONString(build)) + .eq(UserCredential::getId, userCredential.getId()) + .update(); + } + } + StpUtil.login(one.getId()); + return result.getUsername(); } } diff --git a/src/main/java/com/kane/animo/config/SaTokenConfigurer.java b/src/main/java/com/kane/animo/config/SaTokenConfigurer.java index 7bd315a..6f06ae1 100644 --- a/src/main/java/com/kane/animo/config/SaTokenConfigurer.java +++ b/src/main/java/com/kane/animo/config/SaTokenConfigurer.java @@ -17,7 +17,9 @@ public class SaTokenConfigurer implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor(handler -> StpUtil.checkLogin())) .addPathPatterns("/**") - .excludePathPatterns("/auth/login"); -// .excludePathPatterns("/auth/register"); + .excludePathPatterns("/auth/login") + .excludePathPatterns("/auth/register") + .excludePathPatterns("/passkey/assertion/options") + .excludePathPatterns("/passkey/assertion"); } }