mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 04:18:17 +08:00
fix(sso): inline FQN→import + harden auto-create orphan rollback
- Replace inline fully-qualified names with top-of-file imports across SsoService / SsoStateService / FeishuSsoProvider (ObjectMapper, Autowired, Map.of, Date, DuplicateKeyException, Mac, URLEncoder) per code style. - createSsoUser: roll back the freshly inserted user on any non-duplicate identity-insert failure, preventing passwordless orphan accounts. The two inserts share no transaction — the method is self-invoked and the enclosing callback performs a network call, so a method-level @Transactional would not apply; an explicit rollback in the catch is the correct guard here.
This commit is contained in:
parent
03a6d61131
commit
b048718298
@ -2,8 +2,10 @@ package vip.mate.auth.sso;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -46,9 +48,9 @@ public class SsoService {
|
|||||||
private final AuthService authService;
|
private final AuthService authService;
|
||||||
private final SsoProperties ssoProperties;
|
private final SsoProperties ssoProperties;
|
||||||
private final BCryptPasswordEncoder passwordEncoder;
|
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. */
|
/** Optional — audit may be null in narrow test contexts. */
|
||||||
@org.springframework.beans.factory.annotation.Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private AuditEventService auditService;
|
private AuditEventService auditService;
|
||||||
|
|
||||||
// ==================== authorize ====================
|
// ==================== authorize ====================
|
||||||
@ -228,6 +230,16 @@ public class SsoService {
|
|||||||
503, "SSO 登录遇到并发冲突, 请重试");
|
503, "SSO 登录遇到并发冲突, 请重试");
|
||||||
}
|
}
|
||||||
return createSsoUser(providerId, info, true);
|
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) {
|
private void audit(String action, String provider, String externalId, Long userId) {
|
||||||
if (auditService != null) {
|
if (auditService != null) {
|
||||||
try {
|
try {
|
||||||
String detail = objectMapper.writeValueAsString(java.util.Map.of(
|
String detail = objectMapper.writeValueAsString(Map.of(
|
||||||
"provider", provider != null ? provider : "",
|
"provider", provider != null ? provider : "",
|
||||||
"userId", userId != null ? userId : "null"));
|
"userId", userId != null ? userId : "null"));
|
||||||
auditService.record(action, "sso",
|
auditService.record(action, "sso",
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import io.jsonwebtoken.security.Keys;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import vip.mate.auth.sso.model.SsoStateEntity;
|
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.auth.sso.repository.SsoStateMapper;
|
||||||
import vip.mate.exception.MateClawException;
|
import vip.mate.exception.MateClawException;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -126,8 +129,8 @@ public class SsoStateService {
|
|||||||
.claim("externalId", info.externalId())
|
.claim("externalId", info.externalId())
|
||||||
.claim("unionId", info.unionId())
|
.claim("unionId", info.unionId())
|
||||||
.claim("externalName", info.displayName())
|
.claim("externalName", info.displayName())
|
||||||
.issuedAt(new java.util.Date(now))
|
.issuedAt(new Date(now))
|
||||||
.expiration(new java.util.Date(now + BIND_TOKEN_TTL_SECONDS * 1000L))
|
.expiration(new Date(now + BIND_TOKEN_TTL_SECONDS * 1000L))
|
||||||
.signWith(getSignKey())
|
.signWith(getSignKey())
|
||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
@ -166,7 +169,7 @@ public class SsoStateService {
|
|||||||
consumed.setCreatedAt(LocalDateTime.now());
|
consumed.setCreatedAt(LocalDateTime.now());
|
||||||
try {
|
try {
|
||||||
stateMapper.insert(consumed);
|
stateMapper.insert(consumed);
|
||||||
} catch (org.springframework.dao.DuplicateKeyException e) {
|
} catch (DuplicateKeyException e) {
|
||||||
throw new MateClawException("err.sso.bind_token_used",
|
throw new MateClawException("err.sso.bind_token_used",
|
||||||
400, "bind_token 已被使用, 请重新登录");
|
400, "bind_token 已被使用, 请重新登录");
|
||||||
}
|
}
|
||||||
@ -213,7 +216,7 @@ public class SsoStateService {
|
|||||||
|
|
||||||
private String hmacSha256Hex(String input) {
|
private String hmacSha256Hex(String input) {
|
||||||
try {
|
try {
|
||||||
javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
|
Mac mac = Mac.getInstance("HmacSHA256");
|
||||||
mac.init(getSignKey());
|
mac.init(getSignKey());
|
||||||
byte[] hash = mac.doFinal(input.getBytes(StandardCharsets.UTF_8));
|
byte[] hash = mac.doFinal(input.getBytes(StandardCharsets.UTF_8));
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import vip.mate.auth.sso.SsoProperties;
|
|||||||
import vip.mate.exception.MateClawException;
|
import vip.mate.exception.MateClawException;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
@ -221,7 +222,7 @@ public class FeishuSsoProvider implements SsoProvider {
|
|||||||
|
|
||||||
private static String encode(String s) {
|
private static String encode(String s) {
|
||||||
try {
|
try {
|
||||||
return java.net.URLEncoder.encode(s, "UTF-8");
|
return URLEncoder.encode(s, "UTF-8");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user