package com.mydreamjob.auth; import com.mydreamjob.config.AppJwtProperties; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.RedisTemplate; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import static org.assertj.core.api.Assertions.assertThat; /** * S02 复核项:只配置三个新密钥、**不配置旧 JWT_SECRET** 时应用仍可启动(任务书 §五)。 * *

旧 {@code app.jwt.secret} 对 S02 的 Token 已无任何作用,但仍被 * {@code talentdiscovery.CandidateKeyGenerator} 使用(该组件在密钥为空时自行失败关闭), * 因此保留属性但改为非强制解析。本测试证明: * 1) 旧密钥为空时全量上下文仍能启动;2) 三项 S02 密钥正确绑定; * 3) {@code application.yml} 中的旧密钥占位符带默认值,不再阻塞启动。

*/ @SpringBootTest(properties = { "app.jwt.secret=", "app.jwt.access-secret=test-access-secret-key-for-integration-tests-only-not-for-production", "app.jwt.refresh-secret=test-refresh-secret-key-for-integration-tests-only-not-for-production", "app.jwt.token-hash-secret=test-token-hash-secret-key-for-integration-tests-only-not-for-production", }) @org.springframework.test.context.ActiveProfiles("integration-test") class JwtSecretOptionalStartupIntegrationTest { @MockBean private RedisTemplate redisTemplate; @Autowired private ApplicationContext applicationContext; @Autowired private AppJwtProperties jwtProperties; @Autowired private JwtTokenProvider jwtTokenProvider; @Autowired private AuthSessionService authSessionService; @Test void contextStartsWithOnlyTheThreeNewSecrets() { assertThat(applicationContext).isNotNull(); assertThat(jwtTokenProvider).isNotNull(); assertThat(authSessionService).isNotNull(); assertThat(jwtProperties.getSecret()) .as("旧密钥为空也必须能启动").isNullOrEmpty(); assertThat(jwtProperties.getAccessSecret()).isNotBlank(); assertThat(jwtProperties.getRefreshSecret()).isNotBlank(); assertThat(jwtProperties.getTokenHashSecret()).isNotBlank(); assertThat(jwtProperties.getAccessSecret()) .isNotEqualTo(jwtProperties.getRefreshSecret()); } @Test void accessAndRefreshNeverFallBackToLegacySecret() throws Exception { // 旧密钥为空时,S02 的两类 Token 仍必须能正常签发与校验 —— 证明没有回退到它。 Instant now = Instant.now(); String sid = java.util.UUID.randomUUID().toString(); IssuedToken access = jwtTokenProvider.generateAccessToken( 1L, "STUDENT", sid, java.util.UUID.randomUUID().toString(), now); IssuedToken refresh = jwtTokenProvider.generateRefreshToken( 1L, sid, java.util.UUID.randomUUID().toString(), now); assertThat(jwtTokenProvider.parseAndValidateAccessToken(access.token(), now).userId()).isEqualTo(1L); assertThat(jwtTokenProvider.parseAndValidateRefreshToken(refresh.token(), now).userId()).isEqualTo(1L); } @Test void applicationYmlNoLongerForcesLegacySecret() throws Exception { Path yml = Path.of("src/main/resources/application.yml"); String content = Files.readString(yml); assertThat(content) .as("旧密钥占位符必须带默认值,否则未配置 JWT_SECRET 的环境无法启动") .contains("secret: ${JWT_SECRET:}"); assertThat(content).doesNotContain("secret: ${JWT_SECRET}\n"); } }