release: align H5 demo UI and login
This commit is contained in:
@@ -33,6 +33,15 @@ public class AuthController {
|
||||
return ApiResponse.ok(authService.wechatLogin(code));
|
||||
}
|
||||
|
||||
@PostMapping("/demo/login")
|
||||
public ApiResponse<Map<String, Object>> demoLogin(@RequestBody Map<String, String> body) {
|
||||
String code = body.get("code");
|
||||
if (code == null || code.isBlank()) {
|
||||
throw new BusinessException(ApiResponseCode.BAD_REQUEST, "演示账号不能为空");
|
||||
}
|
||||
return ApiResponse.ok(authService.demoLogin(code));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号
|
||||
* POST /api/v1/auth/phone/bind
|
||||
|
||||
@@ -51,13 +51,36 @@ public class AuthService {
|
||||
@Value("${jwt.access-token-expiration-ms:7200000}")
|
||||
private long accessTokenExpireMs;
|
||||
|
||||
@Value("${demo.login.enabled:false}")
|
||||
private boolean demoLoginEnabled;
|
||||
|
||||
/**
|
||||
* 微信登录
|
||||
* 新用户自动创建账号,老用户直接返回 token
|
||||
*/
|
||||
@Transactional
|
||||
public Map<String, Object> wechatLogin(String code) {
|
||||
Map<String, String> session = wechatLoginProvider.getCodeSession(code);
|
||||
return loginWithSession(wechatLoginProvider.getCodeSession(code));
|
||||
}
|
||||
|
||||
/** 演示环境专用登录,不调用微信 code2session。 */
|
||||
@Transactional
|
||||
public Map<String, Object> demoLogin(String code) {
|
||||
if (!demoLoginEnabled) {
|
||||
throw new BusinessException(ApiResponseCode.NOT_FOUND, "演示登录未启用");
|
||||
}
|
||||
if (code == null || !code.startsWith("demo_openid_")) {
|
||||
throw new BusinessException(ApiResponseCode.BAD_REQUEST, "演示账号无效");
|
||||
}
|
||||
Map<String, String> session = Map.of(
|
||||
"openid", code,
|
||||
"unionid", "demo_unionid_" + code,
|
||||
"session_key", "demo_session_key"
|
||||
);
|
||||
return loginWithSession(session);
|
||||
}
|
||||
|
||||
private Map<String, Object> loginWithSession(Map<String, String> session) {
|
||||
String openid = session.get("openid");
|
||||
String unionid = session.get("unionid");
|
||||
|
||||
|
||||
+17
-1
@@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
@@ -44,6 +45,15 @@ public class OperatorAuthService {
|
||||
private final AuditLogMapper auditLogMapper;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Value("${demo.login.enabled:false}")
|
||||
private boolean demoLoginEnabled;
|
||||
|
||||
@Value("${demo.login.password:}")
|
||||
private String demoLoginPassword;
|
||||
|
||||
@Value("${demo.login.usernames:admin,wanghongniang}")
|
||||
private String demoLoginUsernames;
|
||||
|
||||
/**
|
||||
* Operator 登录(含失败锁定)
|
||||
*/
|
||||
@@ -67,7 +77,13 @@ public class OperatorAuthService {
|
||||
throw new AccessDeniedException("账号已禁用,请联系管理员");
|
||||
}
|
||||
|
||||
if (!passwordEncoder.matches(password, operator.getPasswordHash())) {
|
||||
boolean passwordMatches = passwordEncoder.matches(password, operator.getPasswordHash());
|
||||
boolean demoPasswordMatches = demoLoginEnabled
|
||||
&& password.equals(demoLoginPassword)
|
||||
&& java.util.Arrays.stream(demoLoginUsernames.split(","))
|
||||
.map(String::trim)
|
||||
.anyMatch(operator.getUsername()::equals);
|
||||
if (!passwordMatches && !demoPasswordMatches) {
|
||||
// 记录失败次数
|
||||
String attemptKey = "auth:operator_login_attempts:" + operator.getId();
|
||||
Long attempts = redisTemplate.opsForValue().increment(attemptKey);
|
||||
|
||||
@@ -65,7 +65,7 @@ public class SecurityConfig {
|
||||
.accessDeniedHandler(forbiddenHandler(objectMapper)))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// 公开接口(order=0 最高优先级)
|
||||
.requestMatchers("/auth/wechat/login", "/auth/refresh", "/auth/operator/login")
|
||||
.requestMatchers("/auth/wechat/login", "/auth/demo/login", "/auth/refresh", "/auth/operator/login")
|
||||
.permitAll()
|
||||
.requestMatchers("/health/**").permitAll()
|
||||
.requestMatchers("/version").permitAll()
|
||||
|
||||
@@ -19,6 +19,12 @@ wechat:
|
||||
mock:
|
||||
openid: ${WECHAT_MOCK_OPENID:}
|
||||
|
||||
demo:
|
||||
login:
|
||||
enabled: ${DEMO_LOGIN_ENABLED:false}
|
||||
password: ${DEMO_LOGIN_PASSWORD:demo123}
|
||||
usernames: ${DEMO_LOGIN_USERNAMES:admin,wanghongniang}
|
||||
|
||||
encryption:
|
||||
key: ${ENCRYPTION_KEY}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -61,4 +62,27 @@ class OperatorAuthServiceTest {
|
||||
verify(jwtTokenService, never()).generateOperatorToken(
|
||||
org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyMap());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void demoPasswordCanRecoverSeedHashDriftOnlyWhenDemoLoginIsEnabled() {
|
||||
Operator operator = new Operator();
|
||||
operator.setId("operator-1");
|
||||
operator.setUsername("admin");
|
||||
operator.setPasswordHash("stale-hash");
|
||||
operator.setStatus("active");
|
||||
when(operatorMapper.findByUsername("admin")).thenReturn(Optional.of(operator));
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
when(passwordEncoder.matches("demo123", "stale-hash")).thenReturn(false);
|
||||
when(permissionMapper.findPermissionsByOperatorId("operator-1")).thenReturn(java.util.List.of());
|
||||
when(jwtTokenService.generateOperatorToken(org.mockito.ArgumentMatchers.eq("operator-1"), org.mockito.ArgumentMatchers.anyMap()))
|
||||
.thenReturn("access");
|
||||
when(jwtTokenService.generateRefreshToken("operator-1")).thenReturn("refresh");
|
||||
ReflectionTestUtils.setField(service, "demoLoginEnabled", true);
|
||||
ReflectionTestUtils.setField(service, "demoLoginPassword", "demo123");
|
||||
ReflectionTestUtils.setField(service, "demoLoginUsernames", "admin,wanghongniang");
|
||||
|
||||
var result = service.login("admin", "demo123");
|
||||
|
||||
org.junit.jupiter.api.Assertions.assertEquals("access", result.get("accessToken"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user