feat: initialize local passwords from platform login

master
chenyuan 4 weeks ago
parent 83c6fed1f9
commit 6edd870a32

@ -0,0 +1,36 @@
package com.yau.digitalrmb.identity.application;
import com.yau.digitalrmb.platformintegration.application.PlatformCredentialRepository;
import com.yau.digitalrmb.platformintegration.application.PlatformIdentityRepository;
import com.yau.digitalrmb.platformintegration.config.PlatformIntegrationProperties;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Component
@Profile("local")
public class PlatformCredentialInitializer implements ApplicationRunner {
private final PlatformIntegrationProperties.LocalLogin properties;
private final PlatformIdentityRepository identityRepository;
private final PlatformCredentialRepository credentialRepository;
private final PlatformIdentityProjectionService projectionService;
public PlatformCredentialInitializer(PlatformIntegrationProperties properties,
PlatformIdentityRepository identityRepository,
PlatformCredentialRepository credentialRepository,
PlatformIdentityProjectionService projectionService) {
this.properties = properties.getLocalLogin();
this.identityRepository = identityRepository;
this.credentialRepository = credentialRepository;
this.projectionService = projectionService;
}
@Override
public void run(ApplicationArguments args) {
properties.getInitialAccounts().stream().filter(account -> account != null && !account.isBlank())
.forEach(account -> identityRepository.findBySchoolAccount(account)
.flatMap(actor -> credentialRepository.findCredential(actor.platformUserId()))
.ifPresent(projectionService::project));
}
}

@ -10,6 +10,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import java.time.Duration;
import java.util.List;
@Getter
@Setter
@ -26,6 +27,8 @@ public class PlatformIntegrationProperties {
private Frontend frontend = new Frontend();
@Valid
private Sync sync = new Sync();
@Valid
private LocalLogin localLogin = new LocalLogin();
@Getter
@Setter
@ -79,4 +82,10 @@ public class PlatformIntegrationProperties {
@NotNull
private Duration fixedDelay = Duration.ofMinutes(15);
}
@Getter
@Setter
public static class LocalLogin {
private List<String> initialAccounts = List.of("tzs001");
}
}

@ -3,6 +3,7 @@ package com.yau.digitalrmb.platformintegration.interfaces;
import com.yau.digitalrmb.identity.application.PlatformIdentityProjectionService;
import com.yau.digitalrmb.platformintegration.application.CasTicketValidator;
import com.yau.digitalrmb.platformintegration.application.PlatformIdentityRepository;
import com.yau.digitalrmb.platformintegration.application.PlatformCredentialRepository;
import com.yau.digitalrmb.platformintegration.config.PlatformIntegrationProperties;
import com.yau.digitalrmb.platformintegration.domain.PlatformActor;
import com.yau.digitalrmb.security.application.LoginExchangeCodeService;
@ -22,17 +23,20 @@ public class CasAuthenticationController {
private final CasTicketValidator ticketValidator;
private final PlatformIdentityRepository identityRepository;
private final PlatformIdentityProjectionService projectionService;
private final PlatformCredentialRepository credentialRepository;
private final LoginExchangeCodeService exchangeCodeService;
private final PlatformIntegrationProperties.Cas cas;
private final PlatformIntegrationProperties.Frontend frontend;
public CasAuthenticationController(CasTicketValidator ticketValidator,
PlatformIdentityRepository identityRepository,
PlatformCredentialRepository credentialRepository,
PlatformIdentityProjectionService projectionService,
LoginExchangeCodeService exchangeCodeService,
PlatformIntegrationProperties properties) {
this.ticketValidator = ticketValidator;
this.identityRepository = identityRepository;
this.credentialRepository = credentialRepository;
this.projectionService = projectionService;
this.exchangeCodeService = exchangeCodeService;
this.cas = properties.getCas();
@ -52,7 +56,7 @@ public class CasAuthenticationController {
String account = ticketValidator.validate(ticket);
PlatformActor actor = identityRepository.findBySchoolAccount(account)
.orElseThrow(() -> new BusinessException(ErrorCode.UNAUTHORIZED, "用户无权访问本系统"));
projectionService.project(actor);
credentialRepository.findCredential(actor.platformUserId()).ifPresent(projectionService::project);
String exchangeCode = exchangeCodeService.issue(actor.platformUserId());
String location = UriComponentsBuilder.fromUriString(frontend.getCallbackUrl())
.queryParam("code", exchangeCode).build().encode().toUriString();

@ -2,6 +2,7 @@ package com.yau.digitalrmb.platformintegration.interfaces;
import com.yau.digitalrmb.identity.application.PlatformIdentityProjectionService;
import com.yau.digitalrmb.platformintegration.application.PlatformTokenVerifier;
import com.yau.digitalrmb.platformintegration.application.PlatformCredentialRepository;
import com.yau.digitalrmb.platformintegration.application.VerifiedPlatformToken;
import com.yau.digitalrmb.platformintegration.config.PlatformIntegrationProperties;
import com.yau.digitalrmb.security.application.LoginExchangeCodeService;
@ -18,14 +19,17 @@ import org.springframework.web.util.UriComponentsBuilder;
public class PlatformSsoController {
private final PlatformTokenVerifier tokenVerifier;
private final PlatformIdentityProjectionService projectionService;
private final PlatformCredentialRepository credentialRepository;
private final LoginExchangeCodeService exchangeCodeService;
private final PlatformIntegrationProperties.Frontend frontend;
public PlatformSsoController(PlatformTokenVerifier tokenVerifier,
PlatformCredentialRepository credentialRepository,
PlatformIdentityProjectionService projectionService,
LoginExchangeCodeService exchangeCodeService,
PlatformIntegrationProperties properties) {
this.tokenVerifier = tokenVerifier;
this.credentialRepository = credentialRepository;
this.projectionService = projectionService;
this.exchangeCodeService = exchangeCodeService;
this.frontend = properties.getFrontend();
@ -34,7 +38,7 @@ public class PlatformSsoController {
@GetMapping("/sso")
public ResponseEntity<Void> loginFromPlatform(@RequestParam("token") String token) {
VerifiedPlatformToken verified = tokenVerifier.verify(token);
projectionService.project(verified.actor());
credentialRepository.findCredential(verified.actor().platformUserId()).ifPresent(projectionService::project);
String exchangeCode = exchangeCodeService.issue(verified.actor().platformUserId());
String location = UriComponentsBuilder.fromUriString(frontend.getCallbackUrl())
.queryParam("code", exchangeCode).build().encode().toUriString();

@ -0,0 +1,41 @@
package com.yau.digitalrmb.identity;
import com.yau.digitalrmb.identity.application.PlatformCredentialInitializer;
import com.yau.digitalrmb.identity.application.PlatformIdentityProjectionService;
import com.yau.digitalrmb.platformintegration.application.PlatformCredentialRepository;
import com.yau.digitalrmb.platformintegration.application.PlatformIdentityRepository;
import com.yau.digitalrmb.platformintegration.config.PlatformIntegrationProperties;
import com.yau.digitalrmb.platformintegration.domain.PlatformActor;
import com.yau.digitalrmb.platformintegration.domain.PlatformCredential;
import com.yau.digitalrmb.platformintegration.domain.PlatformRole;
import org.junit.jupiter.api.Test;
import org.springframework.boot.DefaultApplicationArguments;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class PlatformCredentialInitializerTest {
@Test
void initializesOnlyConfiguredPlatformAccount() throws Exception {
PlatformIdentityRepository identities = mock(PlatformIdentityRepository.class);
PlatformCredentialRepository credentials = mock(PlatformCredentialRepository.class);
PlatformIdentityProjectionService projection = mock(PlatformIdentityProjectionService.class);
PlatformIntegrationProperties properties = new PlatformIntegrationProperties();
properties.getLocalLogin().setInitialAccounts(List.of("tzs001"));
PlatformActor actor = new PlatformActor(301L, 3L, "tzs001", "教师", PlatformRole.TEACHER,
Instant.parse("2026-01-01T00:00:00Z"));
PlatformCredential credential = new PlatformCredential(actor, "123qwe");
when(identities.findBySchoolAccount("tzs001")).thenReturn(Optional.of(actor));
when(credentials.findCredential(301L)).thenReturn(Optional.of(credential));
new PlatformCredentialInitializer(properties, identities, credentials, projection)
.run(new DefaultApplicationArguments());
verify(projection).project(credential);
}
}

@ -2,9 +2,11 @@ package com.yau.digitalrmb.platformintegration.interfaces;
import com.yau.digitalrmb.identity.application.PlatformIdentityProjectionService;
import com.yau.digitalrmb.platformintegration.application.PlatformTokenVerifier;
import com.yau.digitalrmb.platformintegration.application.PlatformCredentialRepository;
import com.yau.digitalrmb.platformintegration.application.VerifiedPlatformToken;
import com.yau.digitalrmb.platformintegration.config.PlatformIntegrationProperties;
import com.yau.digitalrmb.platformintegration.domain.PlatformActor;
import com.yau.digitalrmb.platformintegration.domain.PlatformCredential;
import com.yau.digitalrmb.platformintegration.domain.PlatformRole;
import com.yau.digitalrmb.security.application.LoginExchangeCodeService;
import org.junit.jupiter.api.Test;
@ -16,6 +18,7 @@ import java.time.Instant;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -24,6 +27,7 @@ class PlatformSsoControllerTest {
@Test
void ssoRedirectDoesNotLeakIncomingToken() throws Exception {
PlatformTokenVerifier verifier = mock(PlatformTokenVerifier.class);
PlatformCredentialRepository credentials = mock(PlatformCredentialRepository.class);
PlatformIdentityProjectionService projection = mock(PlatformIdentityProjectionService.class);
LoginExchangeCodeService exchangeCodes = mock(LoginExchangeCodeService.class);
PlatformIntegrationProperties properties = new PlatformIntegrationProperties();
@ -31,13 +35,15 @@ class PlatformSsoControllerTest {
PlatformActor actor = new PlatformActor(101L, 1L, "t001", "教师甲", PlatformRole.TEACHER,
Instant.parse("2026-01-01T00:00:00Z"));
when(verifier.verify(anyString())).thenReturn(new VerifiedPlatformToken(actor, "fingerprint"));
when(credentials.findCredential(101L)).thenReturn(java.util.Optional.of(new PlatformCredential(actor, "123qwe")));
when(exchangeCodes.issue(101L)).thenReturn("one-time-code");
MockMvc mvc = MockMvcBuilders.standaloneSetup(new PlatformSsoController(verifier, projection, exchangeCodes, properties)).build();
MockMvc mvc = MockMvcBuilders.standaloneSetup(new PlatformSsoController(verifier, credentials, projection, exchangeCodes, properties)).build();
mvc.perform(get("/api/v1/auth/sso").param("token", "incoming-platform-token"))
.andExpect(status().isFound())
.andExpect(header().string("Location", "https://rmb.example.edu/sso-callback?code=one-time-code"))
.andExpect(header().string("Cache-Control", "no-store"))
.andExpect(header().string("Referrer-Policy", "no-referrer"));
verify(projection).project(new PlatformCredential(actor, "123qwe"));
}
}

Loading…
Cancel
Save