diff --git a/mateclaw-server/src/main/java/vip/mate/auth/sso/SsoService.java b/mateclaw-server/src/main/java/vip/mate/auth/sso/SsoService.java index d127570e..8d615715 100644 --- a/mateclaw-server/src/main/java/vip/mate/auth/sso/SsoService.java +++ b/mateclaw-server/src/main/java/vip/mate/auth/sso/SsoService.java @@ -2,8 +2,10 @@ package vip.mate.auth.sso; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; @@ -46,9 +48,9 @@ public class SsoService { private final AuthService authService; private final SsoProperties ssoProperties; private final BCryptPasswordEncoder passwordEncoder; - private final com.fasterxml.jackson.databind.ObjectMapper objectMapper; + private final ObjectMapper objectMapper; /** Optional — audit may be null in narrow test contexts. */ - @org.springframework.beans.factory.annotation.Autowired(required = false) + @Autowired(required = false) private AuditEventService auditService; // ==================== authorize ==================== @@ -228,6 +230,16 @@ public class SsoService { 503, "SSO 登录遇到并发冲突, 请重试"); } return createSsoUser(providerId, info, true); + } catch (RuntimeException e) { + // Identity insert failed for a non-duplicate reason (e.g. transient DB error). + // The two inserts are not in a shared transaction — this method is self-invoked + // and the enclosing callback performs a network call, so a method-level + // @Transactional would not apply. Roll back the freshly inserted user here so we + // never leave a passwordless orphan account behind. + if (newUser.getId() != null) { + userMapper.deleteById(newUser.getId()); + } + throw e; } } @@ -240,7 +252,7 @@ public class SsoService { private void audit(String action, String provider, String externalId, Long userId) { if (auditService != null) { try { - String detail = objectMapper.writeValueAsString(java.util.Map.of( + String detail = objectMapper.writeValueAsString(Map.of( "provider", provider != null ? provider : "", "userId", userId != null ? userId : "null")); auditService.record(action, "sso", diff --git a/mateclaw-server/src/main/java/vip/mate/auth/sso/SsoStateService.java b/mateclaw-server/src/main/java/vip/mate/auth/sso/SsoStateService.java index 27139fa7..00e1e8ea 100644 --- a/mateclaw-server/src/main/java/vip/mate/auth/sso/SsoStateService.java +++ b/mateclaw-server/src/main/java/vip/mate/auth/sso/SsoStateService.java @@ -8,6 +8,7 @@ import io.jsonwebtoken.security.Keys; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DuplicateKeyException; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import vip.mate.auth.sso.model.SsoStateEntity; @@ -15,10 +16,12 @@ import vip.mate.auth.sso.provider.SsoUserInfo; import vip.mate.auth.sso.repository.SsoStateMapper; import vip.mate.exception.MateClawException; +import javax.crypto.Mac; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.Date; import java.util.Map; import java.util.UUID; @@ -126,8 +129,8 @@ public class SsoStateService { .claim("externalId", info.externalId()) .claim("unionId", info.unionId()) .claim("externalName", info.displayName()) - .issuedAt(new java.util.Date(now)) - .expiration(new java.util.Date(now + BIND_TOKEN_TTL_SECONDS * 1000L)) + .issuedAt(new Date(now)) + .expiration(new Date(now + BIND_TOKEN_TTL_SECONDS * 1000L)) .signWith(getSignKey()) .compact(); } @@ -166,7 +169,7 @@ public class SsoStateService { consumed.setCreatedAt(LocalDateTime.now()); try { stateMapper.insert(consumed); - } catch (org.springframework.dao.DuplicateKeyException e) { + } catch (DuplicateKeyException e) { throw new MateClawException("err.sso.bind_token_used", 400, "bind_token 已被使用, 请重新登录"); } @@ -213,7 +216,7 @@ public class SsoStateService { private String hmacSha256Hex(String input) { try { - javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); mac.init(getSignKey()); byte[] hash = mac.doFinal(input.getBytes(StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); diff --git a/mateclaw-server/src/main/java/vip/mate/auth/sso/provider/FeishuSsoProvider.java b/mateclaw-server/src/main/java/vip/mate/auth/sso/provider/FeishuSsoProvider.java index 9f123043..8605e502 100644 --- a/mateclaw-server/src/main/java/vip/mate/auth/sso/provider/FeishuSsoProvider.java +++ b/mateclaw-server/src/main/java/vip/mate/auth/sso/provider/FeishuSsoProvider.java @@ -7,6 +7,7 @@ import vip.mate.auth.sso.SsoProperties; import vip.mate.exception.MateClawException; import java.net.URI; +import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -221,7 +222,7 @@ public class FeishuSsoProvider implements SsoProvider { private static String encode(String s) { try { - return java.net.URLEncoder.encode(s, "UTF-8"); + return URLEncoder.encode(s, "UTF-8"); } catch (Exception e) { return s; }