格式修改
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user