格式修改

This commit is contained in:
2026-08-05 10:35:07 +08:00
parent 81c9c92f5d
commit 496fc58a90
241 changed files with 5600 additions and 2088 deletions
@@ -1,5 +1,8 @@
package com.baiyue.auth.client;
import jakarta.annotation.Resource;
import jakarta.annotation.PostConstruct;
import com.baiyue.auth.entity.AuthAccount;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
@@ -12,16 +15,16 @@ import org.springframework.stereotype.Component;
*/
@Component
public class AuthorizationLoader {
private final IamAuthorizationClient client;
private final String internalToken;
@Resource
private IamAuthorizationClient client;
@Value("${baiyue.security.internal-service-token}")
private String internalToken;
public AuthorizationLoader(IamAuthorizationClient client,
@Value("${baiyue.security.internal-service-token}") String internalToken) {
@PostConstruct
void validateConfiguration() {
if (internalToken == null || internalToken.getBytes(java.nio.charset.StandardCharsets.UTF_8).length < 32) {
throw new IllegalStateException("INTERNAL_SERVICE_TOKEN 至少需要 32 字节");
}
this.client = client;
this.internalToken = internalToken;
}
public AuthorizationSnapshot load(AuthAccount account) {
@@ -21,14 +21,10 @@ public class InternalAccountController {
@Resource
private AccountManagementService service;
private final String configuredToken;
@Value("${baiyue.security.internal-service-token}")
private String configuredToken;
private byte[] tokenBytes;
public InternalAccountController(
@Value("${baiyue.security.internal-service-token}") String configuredToken) {
this.configuredToken = configuredToken;
}
@PostConstruct void init() { tokenBytes = configuredToken.getBytes(StandardCharsets.UTF_8); }
@PostMapping
@@ -1,5 +1,7 @@
package com.baiyue.auth.repository;
import jakarta.annotation.Resource;
import com.baiyue.auth.entity.AuthAccount;
import com.baiyue.auth.mapper.AuthAccountMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -10,11 +12,8 @@ import org.springframework.stereotype.Repository;
*/
@Repository
public class AuthAccountRepository {
private final AuthAccountMapper mapper;
public AuthAccountRepository(AuthAccountMapper mapper) {
this.mapper = mapper;
}
@Resource
private AuthAccountMapper mapper;
public AuthAccount findByUsername(String username) {
return mapper.selectOne(new LambdaQueryWrapper<AuthAccount>()
@@ -1,5 +1,7 @@
package com.baiyue.auth.repository;
import jakarta.annotation.Resource;
import com.baiyue.auth.entity.AuthRefreshToken;
import com.baiyue.auth.mapper.AuthRefreshTokenMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -10,11 +12,8 @@ import org.springframework.stereotype.Repository;
*/
@Repository
public class AuthRefreshTokenRepository {
private final AuthRefreshTokenMapper mapper;
public AuthRefreshTokenRepository(AuthRefreshTokenMapper mapper) {
this.mapper = mapper;
}
@Resource
private AuthRefreshTokenMapper mapper;
public AuthRefreshToken findByHash(String tokenHash, boolean forUpdate) {
String limit = forUpdate ? "LIMIT 1 FOR UPDATE" : "LIMIT 1";
@@ -1,5 +1,8 @@
package com.baiyue.auth.service;
import jakarta.annotation.Resource;
import jakarta.annotation.PostConstruct;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.RedisTokenKeys;
@@ -25,15 +28,16 @@ import java.util.List;
*/
@Component
public class TokenSessionStore {
private final StringRedisTemplate redisTemplate;
private final ObjectMapper objectMapper;
private final String keyPrefix;
@Resource
private StringRedisTemplate redisTemplate;
@Resource
private ObjectMapper objectMapper;
@Value("${baiyue.security.token-key-prefix:baiyue:auth}")
private String keyPrefix;
public TokenSessionStore(StringRedisTemplate redisTemplate, ObjectMapper objectMapper,
@Value("${baiyue.security.token-key-prefix:baiyue:auth}") String keyPrefix) {
this.redisTemplate = redisTemplate;
this.objectMapper = objectMapper;
this.keyPrefix = RedisTokenKeys.normalize(keyPrefix);
@PostConstruct
void normalizeKeyPrefix() {
keyPrefix = RedisTokenKeys.normalize(keyPrefix);
}
public void store(String jwtId, Long userId, String rawRefreshToken,
+1 -1
View File
@@ -52,6 +52,6 @@ logging:
rollingpolicy:
file-name-pattern: "${LOGGING_ROLLING_FILE_NAME_PATTERN:${LOG_PATH:./logs}/${spring.application.name}.%d{yyyy-MM-dd}.%i.log.gz}"
max-file-size: "${LOGGING_ROLLING_MAX_FILE_SIZE:100MB}"
max-history: "${LOGGING_ROLLING_MAX_HISTORY:30}"
max-history: "${LOGGING_ROLLING_MAX_HISTORY:7}"
total-size-cap: "${LOGGING_ROLLING_TOTAL_SIZE_CAP:5GB}"
clean-history-on-start: "${LOGGING_ROLLING_CLEAN_HISTORY_ON_START:false}"
@@ -11,25 +11,29 @@ import com.baiyue.common.core.exception.BizException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import jakarta.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.time.LocalDateTime;
@DisplayName("账号管理服务")
@SpringJUnitConfig(classes = {AccountManagementService.class, AccountManagementServiceTest.TestConfig.class})
class AccountManagementServiceTest {
private final AuthAccountRepository repository = mock(AuthAccountRepository.class);
private final TokenSessionStore sessions = mock(TokenSessionStore.class);
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(4);
private final AccountManagementService service = new AccountManagementService();
@MockBean private AuthAccountRepository repository;
@MockBean private TokenSessionStore sessions;
@Resource private PasswordEncoder encoder;
@Resource private AccountManagementService service;
private AuthAccount account;
@BeforeEach void setUp() {
ReflectionTestUtils.setField(service, "repository", repository);
ReflectionTestUtils.setField(service, "passwordEncoder", encoder);
ReflectionTestUtils.setField(service, "tokenSessionStore", sessions);
account = new AuthAccount(); account.setId(10L); account.setPasswordHash(encoder.encode("OldPass1"));
account.setTokenVersion(2); account.setAccountStatus("ACTIVE");
when(repository.findById(10L)).thenReturn(account);
@@ -123,4 +127,11 @@ class AccountManagementServiceTest {
verify(repository).save(account);
verify(sessions).revokeAll(10L);
}
@Configuration
static class TestConfig {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(4);
}
}
}
@@ -7,31 +7,32 @@ import com.baiyue.auth.repository.AuthAccountRepository;
import com.baiyue.common.core.api.ApiResponse;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import jakarta.annotation.Resource;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SpringJUnitConfig(MerchantRegistrationService.class)
@TestPropertySource(properties = "baiyue.security.internal-service-token=internal-token")
class MerchantRegistrationServiceTest {
@MockBean private AuthAccountRepository accounts;
@MockBean private PasswordEncoder encoder;
@MockBean private IamOnboardingClient onboarding;
@Resource private MerchantRegistrationService service;
@Test
void registeredAuthAccountUsesTheOwnerIdentityReturnedByIamOnboarding() {
AuthAccountRepository accounts = mock(AuthAccountRepository.class);
PasswordEncoder encoder = mock(PasswordEncoder.class);
IamOnboardingClient onboarding = mock(IamOnboardingClient.class);
MerchantRegistrationService service = new MerchantRegistrationService();
ReflectionTestUtils.setField(service, "accountRepository", accounts);
ReflectionTestUtils.setField(service, "passwordEncoder", encoder);
ReflectionTestUtils.setField(service, "onboardingClient", onboarding);
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
when(encoder.encode("Password123")).thenReturn("encoded-password");
when(onboarding.register(eq("internal-token"), any())).thenReturn(ApiResponse.success(
new IamOnboardingClient.OnboardingResponse(10001L, 30001L, "PROFILE_REQUIRED")));
@@ -59,14 +60,6 @@ class MerchantRegistrationServiceTest {
@Test
void failedAuthInsertCompensatesTheIamRegistration() {
AuthAccountRepository accounts = mock(AuthAccountRepository.class);
PasswordEncoder encoder = mock(PasswordEncoder.class);
IamOnboardingClient onboarding = mock(IamOnboardingClient.class);
MerchantRegistrationService service = new MerchantRegistrationService();
ReflectionTestUtils.setField(service, "accountRepository", accounts);
ReflectionTestUtils.setField(service, "passwordEncoder", encoder);
ReflectionTestUtils.setField(service, "onboardingClient", onboarding);
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
when(encoder.encode("Password123")).thenReturn("encoded-password");
when(onboarding.register(eq("internal-token"), any())).thenReturn(ApiResponse.success(
new IamOnboardingClient.OnboardingResponse(10001L, 30001L, "PROFILE_REQUIRED")));
@@ -87,14 +80,6 @@ class MerchantRegistrationServiceTest {
@Test
void rolledBackAuthTransactionCompensatesAfterTheLocalTransactionCompletes() {
AuthAccountRepository accounts = mock(AuthAccountRepository.class);
PasswordEncoder encoder = mock(PasswordEncoder.class);
IamOnboardingClient onboarding = mock(IamOnboardingClient.class);
MerchantRegistrationService service = new MerchantRegistrationService();
ReflectionTestUtils.setField(service, "accountRepository", accounts);
ReflectionTestUtils.setField(service, "passwordEncoder", encoder);
ReflectionTestUtils.setField(service, "onboardingClient", onboarding);
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
when(encoder.encode("Password123")).thenReturn("encoded-password");
when(onboarding.register(eq("internal-token"), any())).thenReturn(ApiResponse.success(
new IamOnboardingClient.OnboardingResponse(10001L, 30001L, "PROFILE_REQUIRED")));
@@ -3,9 +3,15 @@ package com.baiyue.auth.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import jakarta.annotation.Resource;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.time.Duration;
import java.util.Set;
@@ -18,14 +24,27 @@ import static org.mockito.Mockito.when;
import static org.mockito.Mockito.eq;
@DisplayName("令牌会话存储")
@SpringJUnitConfig(classes = {TokenSessionStore.class, TokenSessionStoreTest.TestConfig.class})
@TestPropertySource(properties = "baiyue.security.token-key-prefix=baiyue:auth")
class TokenSessionStoreTest {
@MockBean
private StringRedisTemplate redis;
@Resource
private TokenSessionStore store;
@Configuration(proxyBeanMethods = false)
static class TestConfig {
@Bean
ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
@Test
void storesAccessAndHashedRefreshWithIndependentTtls() {
StringRedisTemplate redis = mock(StringRedisTemplate.class);
@SuppressWarnings("unchecked")
ValueOperations<String, String> values = mock(ValueOperations.class);
when(redis.opsForValue()).thenReturn(values);
TokenSessionStore store = new TokenSessionStore(redis, new ObjectMapper(), "baiyue:auth:");
store.store("jti-1", 42L, "raw-refresh", Duration.ofMinutes(30), Duration.ofDays(7));
@@ -36,14 +55,12 @@ class TokenSessionStoreTest {
@Test
void atomicallyConsumesRefreshAndRevokesItsAccessSession() throws Exception {
StringRedisTemplate redis = mock(StringRedisTemplate.class);
@SuppressWarnings("unchecked")
ValueOperations<String, String> values = mock(ValueOperations.class);
when(redis.opsForValue()).thenReturn(values);
String json = new ObjectMapper().writeValueAsString(
new TokenSessionStore.RefreshSession(42L, "jti-1"));
when(values.getAndDelete(anyString())).thenReturn(json);
TokenSessionStore store = new TokenSessionStore(redis, new ObjectMapper(), "baiyue:auth");
TokenSessionStore.RefreshSession session = store.consume("raw-refresh");
@@ -55,26 +72,23 @@ class TokenSessionStoreTest {
@Test
void treatsAlreadyConsumedRefreshAsInvalid() {
StringRedisTemplate redis = mock(StringRedisTemplate.class);
@SuppressWarnings("unchecked")
ValueOperations<String, String> values = mock(ValueOperations.class);
when(redis.opsForValue()).thenReturn(values);
when(values.getAndDelete(anyString())).thenReturn(null);
assertThat(new TokenSessionStore(redis, new ObjectMapper(), "baiyue:auth")
.consume("raw-refresh")).isNull();
assertThat(store.consume("raw-refresh")).isNull();
}
@Test
void revokesAllIndexedSessionsForUser() {
StringRedisTemplate redis = mock(StringRedisTemplate.class);
@SuppressWarnings("unchecked")
SetOperations<String, String> sets = mock(SetOperations.class);
when(redis.opsForSet()).thenReturn(sets);
when(sets.members("baiyue:auth:user-sessions:42"))
.thenReturn(Set.of("baiyue:auth:access:a", "baiyue:auth:refresh:b"));
new TokenSessionStore(redis, new ObjectMapper(), "baiyue:auth").revokeAll(42L);
store.revokeAll(42L);
verify(redis).delete(eq(Set.of("baiyue:auth:access:a", "baiyue:auth:refresh:b")));
verify(redis).delete("baiyue:auth:user-sessions:42");