feat(auth): support username or phone login
This commit is contained in:
@@ -85,6 +85,20 @@ public class InternalAccountController {
|
||||
@PathVariable Long id, @Valid @RequestBody UpdateAccountProfileRequest request) {
|
||||
verify(token); service.updateProfile(id, request); return ApiResponse.success(null);
|
||||
}
|
||||
@PutMapping("/{id}/login-identifiers")
|
||||
public ApiResponse<AccountSummary> loginIdentifiers(
|
||||
@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
|
||||
@PathVariable Long id, @Valid @RequestBody ChangeLoginIdentifiersRequest request) {
|
||||
verify(token);
|
||||
return ApiResponse.success(service.changeLoginIdentifiers(id, request));
|
||||
}
|
||||
@PutMapping("/{id}/phone")
|
||||
public ApiResponse<Void> phone(@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
|
||||
@PathVariable Long id, @Valid @RequestBody UpdateAccountPhoneRequest request) {
|
||||
verify(token);
|
||||
service.updatePhone(id, request);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
@PutMapping("/{id}/password")
|
||||
public ApiResponse<Void> reset(@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
|
||||
@PathVariable Long id, @Valid @RequestBody ResetPasswordRequest request) {
|
||||
|
||||
@@ -21,7 +21,8 @@ public final class AccountManagementDtos {
|
||||
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$", message = "密码必须同时包含字母和数字")
|
||||
String newPassword) { }
|
||||
public record CreateAccountRequest(@NotNull Long accountId,
|
||||
@NotBlank @Pattern(regexp = "^[A-Za-z0-9_.@-]{4,64}$") String username,
|
||||
@NotBlank @Pattern(regexp = "^[A-Za-z][A-Za-z0-9_.@-]{3,63}$") String username,
|
||||
@Pattern(regexp = "^1\\d{10}$") String phone,
|
||||
@NotBlank @Size(min = 8, max = 64)
|
||||
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$", message = "密码必须同时包含字母和数字") String password,
|
||||
@NotBlank String displayName, @NotNull Long tenantId,
|
||||
@@ -32,9 +33,19 @@ public final class AccountManagementDtos {
|
||||
String accountStatus) {
|
||||
public CreateAccountRequest(Long accountId, String username, String password, String displayName,
|
||||
Long tenantId, Long membershipId, String appCode) {
|
||||
this(accountId, username, password, displayName, tenantId, membershipId, appCode, "ACTIVE");
|
||||
this(accountId, username, null, password, displayName, tenantId, membershipId, appCode, "ACTIVE");
|
||||
}
|
||||
public CreateAccountRequest(Long accountId, String username, String password, String displayName,
|
||||
Long tenantId, Long membershipId, String appCode, String accountStatus) {
|
||||
this(accountId, username, null, password, displayName, tenantId, membershipId, appCode, accountStatus);
|
||||
}
|
||||
}
|
||||
public record ChangeLoginIdentifiersRequest(
|
||||
@NotBlank @Pattern(regexp = "^[A-Za-z][A-Za-z0-9_.@-]{3,63}$") String username,
|
||||
@NotBlank @Pattern(regexp = "^1\\d{10}$") String phone,
|
||||
@NotBlank String currentPassword) { }
|
||||
public record UpdateAccountPhoneRequest(
|
||||
@NotBlank @Pattern(regexp = "^1\\d{10}$") String phone) { }
|
||||
public record UpdateAccountProfileRequest(@NotBlank @Size(max = 64) String displayName) { }
|
||||
public record UpdateAccountStatusRequest(@NotBlank String status) { }
|
||||
public record RevokeAccountSessionsRequest(
|
||||
@@ -56,10 +67,10 @@ public final class AccountManagementDtos {
|
||||
public record ImpersonationExchangeRequest(@NotBlank String code) { }
|
||||
public record UpdateAccountScopeRequest(@NotNull Long tenantId, @NotNull Long membershipId,
|
||||
@NotBlank @Pattern(regexp = "MERCHANT|PLATFORM") String appCode) { }
|
||||
public record AccountSummary(Long accountId, String username, String displayName, String status,
|
||||
public record AccountSummary(Long accountId, String username, String phone, String displayName, String status,
|
||||
LocalDateTime lastLoginAt, Long tenantId, Long membershipId, String appCode) {
|
||||
public AccountSummary(Long accountId, String username, String displayName, String status) {
|
||||
this(accountId, username, displayName, status, null, null, null, null);
|
||||
this(accountId, username, null, displayName, status, null, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@ package com.baiyue.auth.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 登录请求契约,接收账号、密码和目标应用域等统一登录参数。
|
||||
*/
|
||||
@Schema(name = "LoginRequest", description = "Unified login credentials / 统一登录凭据")
|
||||
public record LoginRequest(
|
||||
@Schema(description = "Login username / 登录用户名", example = "merchant_admin") @NotBlank String username,
|
||||
@Schema(description = "Username or mainland China mobile number / 用户名或中国大陆手机号", example = "merchant_admin")
|
||||
@NotBlank @Size(max = 64) String loginId,
|
||||
@Schema(description = "Login password / 登录密码", example = "ChangeMe123!", format = "password") @NotBlank String password
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import jakarta.validation.constraints.Size;
|
||||
|
||||
@Schema(name = "MerchantRegisterRequest", description = "Phase-one merchant account registration")
|
||||
public record MerchantRegisterRequest(
|
||||
@NotBlank @Pattern(regexp = "[A-Za-z0-9_]{4,64}") String username,
|
||||
@NotBlank @Pattern(regexp = "^[A-Za-z][A-Za-z0-9_.@-]{3,63}$") String username,
|
||||
@NotBlank @Size(min = 8, max = 72) String password,
|
||||
@NotBlank @Size(max = 64) String displayName,
|
||||
@NotBlank @Size(max = 128) String companyName,
|
||||
@NotBlank @Size(max = 64) String legalPerson,
|
||||
@NotBlank @Size(max = 32) String legalPhone,
|
||||
@NotBlank @Pattern(regexp = "^1\\d{10}$") String legalPhone,
|
||||
@Email @Size(max = 128) String contactEmail,
|
||||
@Size(min = 2, max = 2) String countryCode,
|
||||
@Size(max = 255) String address
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.time.LocalDateTime;
|
||||
public class AuthAccount {
|
||||
@TableId private Long id;
|
||||
private String username;
|
||||
private String phone;
|
||||
private String passwordHash;
|
||||
private String displayName;
|
||||
private String accountStatus;
|
||||
|
||||
@@ -22,11 +22,34 @@ public class AuthAccountRepository {
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
public AuthAccount findByPhone(String phone) {
|
||||
return mapper.selectOne(new LambdaQueryWrapper<AuthAccount>()
|
||||
.eq(AuthAccount::getPhone, phone)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
public boolean existsByUsername(String username) {
|
||||
return mapper.selectCount(new LambdaQueryWrapper<AuthAccount>()
|
||||
.eq(AuthAccount::getUsername, username)) > 0;
|
||||
}
|
||||
|
||||
public boolean existsByPhone(String phone) {
|
||||
return mapper.selectCount(new LambdaQueryWrapper<AuthAccount>()
|
||||
.eq(AuthAccount::getPhone, phone)) > 0;
|
||||
}
|
||||
|
||||
public boolean existsByUsernameForOtherAccount(String username, Long accountId) {
|
||||
return mapper.selectCount(new LambdaQueryWrapper<AuthAccount>()
|
||||
.eq(AuthAccount::getUsername, username)
|
||||
.ne(AuthAccount::getId, accountId)) > 0;
|
||||
}
|
||||
|
||||
public boolean existsByPhoneForOtherAccount(String phone, Long accountId) {
|
||||
return mapper.selectCount(new LambdaQueryWrapper<AuthAccount>()
|
||||
.eq(AuthAccount::getPhone, phone)
|
||||
.ne(AuthAccount::getId, accountId)) > 0;
|
||||
}
|
||||
|
||||
public void insert(AuthAccount account) { mapper.insert(account); }
|
||||
|
||||
public AuthAccount findById(Long id) {
|
||||
|
||||
@@ -71,9 +71,13 @@ public class AccountManagementService {
|
||||
if (repository.existsByUsername(request.username())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "登录账号已存在");
|
||||
}
|
||||
if (request.phone() != null && repository.existsByPhone(request.phone())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "手机号已存在");
|
||||
}
|
||||
AuthAccount account = new AuthAccount();
|
||||
account.setId(request.accountId());
|
||||
account.setUsername(request.username());
|
||||
account.setPhone(request.phone());
|
||||
account.setPasswordHash(passwordEncoder.encode(request.password()));
|
||||
account.setDisplayName(request.displayName());
|
||||
account.setAccountStatus(request.accountStatus() == null ? "ACTIVE" : request.accountStatus());
|
||||
@@ -117,6 +121,45 @@ public class AccountManagementService {
|
||||
bumpVersion(account);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "ACCOUNT_LOGIN_IDENTIFIERS_CHANGE", resourceType = "ACCOUNT", resourceId = "#accountId",
|
||||
description = "修改本人用户名和手机号", captureAfter = false)
|
||||
public AccountSummary changeLoginIdentifiers(Long accountId, ChangeLoginIdentifiersRequest request) {
|
||||
AuthAccount account = required(accountId);
|
||||
if (!passwordEncoder.matches(request.currentPassword(), account.getPasswordHash())) {
|
||||
throw new BizException(ResultCode.UNAUTHORIZED, "当前密码错误");
|
||||
}
|
||||
String username = request.username().trim();
|
||||
String phone = request.phone().trim();
|
||||
if (repository.existsByUsernameForOtherAccount(username, accountId)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "用户名已存在");
|
||||
}
|
||||
if (repository.existsByPhoneForOtherAccount(phone, accountId)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "手机号已存在");
|
||||
}
|
||||
if (Objects.equals(account.getUsername(), username) && Objects.equals(account.getPhone(), phone)) {
|
||||
return summary(account);
|
||||
}
|
||||
account.setUsername(username);
|
||||
account.setPhone(phone);
|
||||
bumpVersion(account);
|
||||
return summary(account);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "ACCOUNT_PHONE_SYNC", resourceType = "ACCOUNT", resourceId = "#accountId",
|
||||
description = "同步受管账号手机号", captureAfter = false)
|
||||
public void updatePhone(Long accountId, UpdateAccountPhoneRequest request) {
|
||||
AuthAccount account = required(accountId);
|
||||
String phone = request.phone().trim();
|
||||
if (Objects.equals(account.getPhone(), phone)) return;
|
||||
if (repository.existsByPhoneForOtherAccount(phone, accountId)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "手机号已存在");
|
||||
}
|
||||
account.setPhone(phone);
|
||||
bumpVersion(account);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "PASSWORD_RESET", resourceType = "ACCOUNT", resourceId = "#accountId", description = "管理员重置密码", captureAfter = false)
|
||||
public void resetPassword(Long accountId, ResetPasswordRequest request, boolean forceChangePassword) {
|
||||
@@ -185,7 +228,7 @@ public class AccountManagementService {
|
||||
}
|
||||
|
||||
private AccountSummary summary(AuthAccount a) {
|
||||
return new AccountSummary(a.getId(), a.getUsername(), a.getDisplayName(), a.getAccountStatus(),
|
||||
return new AccountSummary(a.getId(), a.getUsername(), a.getPhone(), a.getDisplayName(), a.getAccountStatus(),
|
||||
a.getLastLoginAt(), a.getDefaultTenantId(), a.getDefaultMembershipId(), a.getDefaultAppCode());
|
||||
}
|
||||
|
||||
@@ -201,6 +244,7 @@ public class AccountManagementService {
|
||||
|
||||
private boolean sameIdentity(AuthAccount account, CreateAccountRequest request, String appCode) {
|
||||
return Objects.equals(account.getUsername(), request.username())
|
||||
&& Objects.equals(account.getPhone(), request.phone())
|
||||
&& Objects.equals(account.getDefaultTenantId(), request.tenantId())
|
||||
&& Objects.equals(account.getOperatorTenantId(), request.tenantId())
|
||||
&& Objects.equals(account.getDefaultMembershipId(), request.membershipId())
|
||||
|
||||
@@ -57,10 +57,13 @@ public class AuthService {
|
||||
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "AUTH_LOGIN", resourceType = "ACCOUNT", resourceId = "#request.username",
|
||||
@BusinessAudit(event = "AUTH_LOGIN", resourceType = "ACCOUNT", resourceId = "#request.loginId",
|
||||
description = "User login / 用户登录", captureAfter = false)
|
||||
public TokenResponse login(LoginRequest request) {
|
||||
AuthAccount account = accountRepository.findByUsername(request.username());
|
||||
String loginId = request.loginId().trim();
|
||||
AuthAccount account = loginId.matches("^1\\d{10}$")
|
||||
? accountRepository.findByPhone(loginId)
|
||||
: accountRepository.findByUsername(loginId);
|
||||
if (account == null) throw invalidCredentials();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (!"ACTIVE".equals(account.getAccountStatus()))
|
||||
@@ -211,7 +214,7 @@ public class AuthService {
|
||||
}
|
||||
|
||||
private static BizException invalidCredentials() {
|
||||
return new BizException(ResultCode.UNAUTHORIZED, "用户名或密码错误");
|
||||
return new BizException(ResultCode.UNAUTHORIZED, "登录标识或密码错误");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@ public class MerchantRegistrationService {
|
||||
resourceId = "#request.username", description = "商户注册", captureAfter = false)
|
||||
public MerchantRegisterResponse register(MerchantRegisterRequest request) {
|
||||
if (accountRepository.existsByUsername(request.username())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Username already exists");
|
||||
throw new BizException(ResultCode.CONFLICT, "用户名已存在");
|
||||
}
|
||||
if (accountRepository.existsByPhone(request.legalPhone())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "手机号已存在");
|
||||
}
|
||||
long accountId = IdWorker.getId();
|
||||
String passwordHash = passwordEncoder.encode(request.password());
|
||||
@@ -81,6 +84,7 @@ public class MerchantRegistrationService {
|
||||
AuthAccount account = new AuthAccount();
|
||||
account.setId(accountId);
|
||||
account.setUsername(request.username());
|
||||
account.setPhone(request.legalPhone());
|
||||
account.setPasswordHash(passwordHash);
|
||||
account.setDisplayName(request.displayName());
|
||||
account.setAccountStatus("ACTIVE");
|
||||
@@ -94,7 +98,7 @@ public class MerchantRegistrationService {
|
||||
try {
|
||||
accountRepository.insert(account);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
BizException failure = new BizException(ResultCode.CONFLICT, "Username already exists");
|
||||
BizException failure = new BizException(ResultCode.CONFLICT, "用户名或手机号已存在");
|
||||
failure.addSuppressed(exception);
|
||||
compensateOnboarding(accountId, failure, compensationCompleted);
|
||||
throw failure;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
-- Auth 单一登录标识增量:用户名或中国大陆手机号。
|
||||
-- 可重复执行;手机号仅从 IAM 当前用户档案回填,无法确认的账号保持 NULL。
|
||||
USE baiyue_auth;
|
||||
|
||||
DROP PROCEDURE IF EXISTS auth_add_login_phone;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE auth_add_login_phone()
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'baiyue_auth' AND table_name = 'auth_account' AND column_name = 'phone') THEN
|
||||
ALTER TABLE auth_account ADD COLUMN phone VARCHAR(32) NULL COMMENT '全局唯一中国大陆登录手机号' AFTER username;
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL auth_add_login_phone();
|
||||
DROP PROCEDURE auth_add_login_phone;
|
||||
|
||||
DROP PROCEDURE IF EXISTS auth_backfill_login_phone;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE auth_backfill_login_phone()
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT user.phone
|
||||
FROM baiyue_iam.iam_user user
|
||||
WHERE user.deleted = 0 AND user.phone REGEXP '^1[0-9]{10}$'
|
||||
GROUP BY user.phone HAVING COUNT(DISTINCT user.auth_user_id) > 1) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'IAM 存在重复手机号,禁止建立 Auth 唯一登录索引';
|
||||
END IF;
|
||||
UPDATE auth_account account
|
||||
JOIN baiyue_iam.iam_user user ON user.auth_user_id = account.id AND user.deleted = 0
|
||||
SET account.phone = user.phone
|
||||
WHERE account.phone IS NULL AND user.phone REGEXP '^1[0-9]{10}$';
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.statistics
|
||||
WHERE table_schema = 'baiyue_auth' AND table_name = 'auth_account'
|
||||
AND index_name = 'uk_auth_account_phone') THEN
|
||||
ALTER TABLE auth_account ADD UNIQUE KEY uk_auth_account_phone (phone);
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL auth_backfill_login_phone();
|
||||
DROP PROCEDURE auth_backfill_login_phone;
|
||||
@@ -6,6 +6,7 @@ USE baiyue_auth;
|
||||
CREATE TABLE IF NOT EXISTS auth_account (
|
||||
id BIGINT NOT NULL COMMENT '统一登录账号ID(雪花ID)',
|
||||
username VARCHAR(64) NOT NULL COMMENT '全局唯一登录名',
|
||||
phone VARCHAR(32) DEFAULT NULL COMMENT '全局唯一中国大陆登录手机号',
|
||||
password_hash VARCHAR(255) NOT NULL COMMENT 'BCrypt密码哈希,禁止保存明文密码',
|
||||
display_name VARCHAR(64) NOT NULL COMMENT '登录后展示名称',
|
||||
account_status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '账号状态:ACTIVE正常、DISABLED停用、LOCKED锁定',
|
||||
@@ -23,6 +24,7 @@ CREATE TABLE IF NOT EXISTS auth_account (
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_auth_account_username (username),
|
||||
UNIQUE KEY uk_auth_account_phone (phone),
|
||||
KEY idx_auth_account_status (account_status, locked_until)
|
||||
) ENGINE=InnoDB COMMENT='统一登录账号表:保存平台端和商户端共用的认证凭据及账号安全状态';
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.baiyue.auth.dto.AccountManagementDtos.CreateAccountRequest;
|
||||
import com.baiyue.auth.dto.AccountManagementDtos.UpdateAccountProfileRequest;
|
||||
import com.baiyue.auth.dto.AccountManagementDtos.UpdateAccountStatusRequest;
|
||||
import com.baiyue.auth.dto.AccountManagementDtos.UpdateAccountScopeRequest;
|
||||
import com.baiyue.auth.dto.AccountManagementDtos.ChangeLoginIdentifiersRequest;
|
||||
import com.baiyue.auth.entity.AuthAccount;
|
||||
import com.baiyue.auth.repository.AuthAccountRepository;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
@@ -157,6 +158,33 @@ class AccountManagementServiceTest {
|
||||
verify(sessions).revokeAll(10L);
|
||||
}
|
||||
|
||||
@Test void changeLoginIdentifiersRequiresCurrentPasswordAndRevokesSessions() {
|
||||
account.setUsername("old.user");
|
||||
account.setPhone("13800138000");
|
||||
when(repository.existsByUsernameForOtherAccount("new.user", 10L)).thenReturn(false);
|
||||
when(repository.existsByPhoneForOtherAccount("13900139000", 10L)).thenReturn(false);
|
||||
|
||||
var result = service.changeLoginIdentifiers(10L,
|
||||
new ChangeLoginIdentifiersRequest("new.user", "13900139000", "OldPass1"));
|
||||
|
||||
assertEquals("new.user", result.username());
|
||||
assertEquals("13900139000", result.phone());
|
||||
assertEquals(3, account.getTokenVersion());
|
||||
verify(sessions).revokeAll(10L);
|
||||
}
|
||||
|
||||
@Test void duplicatePhoneDoesNotChangeLoginIdentifiers() {
|
||||
account.setUsername("old.user");
|
||||
account.setPhone("13800138000");
|
||||
when(repository.existsByUsernameForOtherAccount("new.user", 10L)).thenReturn(false);
|
||||
when(repository.existsByPhoneForOtherAccount("13900139000", 10L)).thenReturn(true);
|
||||
|
||||
assertThrows(BizException.class, () -> service.changeLoginIdentifiers(10L,
|
||||
new ChangeLoginIdentifiersRequest("new.user", "13900139000", "OldPass1")));
|
||||
verify(repository, never()).save(any());
|
||||
verify(sessions, never()).revokeAll(any());
|
||||
}
|
||||
|
||||
@Test void rbacChangeRevokesAccessAndRefreshSessionsAndBumpsTokenVersions() {
|
||||
AuthAccount second = new AuthAccount();
|
||||
second.setId(11L); second.setTokenVersion(7); second.setAccountStatus("ACTIVE");
|
||||
|
||||
@@ -100,6 +100,34 @@ class AuthServiceSmallJwtTest {
|
||||
assertThat(response.overseasExpiresIn()).isEqualTo(900);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mainlandPhoneIsResolvedAsTheSinglePhoneLoginIdentifier() {
|
||||
AuthAccount account = new AuthAccount();
|
||||
account.setId(42L);
|
||||
account.setUsername("operator");
|
||||
account.setPhone("13800138000");
|
||||
account.setPasswordHash("hash");
|
||||
account.setAccountStatus("ACTIVE");
|
||||
account.setDefaultTenantId(0L);
|
||||
account.setOperatorTenantId(0L);
|
||||
account.setDefaultMembershipId(84L);
|
||||
account.setDefaultAppCode("PLATFORM");
|
||||
account.setTokenVersion(1);
|
||||
AuthorizationSnapshot snapshot = new AuthorizationSnapshot(42L, 0L, 84L, "PLATFORM",
|
||||
"WEB", Set.of(), Set.of(), 1L);
|
||||
Jwt encoded = org.mockito.Mockito.mock(Jwt.class);
|
||||
when(repository.findByPhone("13800138000")).thenReturn(account);
|
||||
when(passwordEncoder.matches("secret", "hash")).thenReturn(true);
|
||||
when(authorizationLoader.load(account)).thenReturn(snapshot);
|
||||
when(jwtEncoder.encode(any())).thenReturn(encoded);
|
||||
when(encoded.getTokenValue()).thenReturn("small-token");
|
||||
|
||||
service.login(new LoginRequest("13800138000", "secret"));
|
||||
|
||||
verify(repository).findByPhone("13800138000");
|
||||
verify(repository, never()).findByUsername("13800138000");
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshRotatesDomesticAndOverseasTokensFromOneAuthorizationSnapshot() {
|
||||
AuthAccount account = new AuthAccount();
|
||||
|
||||
Reference in New Issue
Block a user