traffic-audit-server/src/main/java/com/trafficaudit/security/controller/LoginController.java
@@ -9,6 +9,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import org.springframework.beans.factory.annotation.Value; import javax.annotation.Resource; import java.util.Collections; import java.util.HashMap; @@ -31,6 +32,9 @@ @Resource private PasswordEncoder passwordEncoder; @Value("${app.captcha.required:true}") private boolean captchaRequired; @PostMapping("/login") public Result<Map<String, Object>> login(@RequestBody Map<String, Object> params) { String username = (String) params.get("username"); @@ -38,15 +42,17 @@ String captchaId = (String) params.get("captchaId"); List<String> clickIds = strList(params.get("captchaClickIds")); CaptchaService.ConsumeResult cr = captchaService.consume(captchaId, clickIds); if (cr == CaptchaService.ConsumeResult.NOT_FOUND) { return Result.error(4002, "验证码不存在或已过期,请刷新重试"); } if (cr == CaptchaService.ConsumeResult.NOT_VERIFIED) { return Result.error(4003, "请先完成行为验证"); } if (cr == CaptchaService.ConsumeResult.WRONG) { return Result.error(4001, "行为验证未通过,请重新验证"); if (captchaRequired) { CaptchaService.ConsumeResult cr = captchaService.consume(captchaId, clickIds); if (cr == CaptchaService.ConsumeResult.NOT_FOUND) { return Result.error(4002, "验证码不存在或已过期,请刷新重试"); } if (cr == CaptchaService.ConsumeResult.NOT_VERIFIED) { return Result.error(4003, "请先完成行为验证"); } if (cr == CaptchaService.ConsumeResult.WRONG) { return Result.error(4001, "行为验证未通过,请重新验证"); } } if (username == null || username.trim().isEmpty() || password == null || password.isEmpty()) { @@ -77,6 +83,13 @@ return Result.ok(data); } @GetMapping("/captcha-config") public Result<Map<String, Object>> captchaConfig() { Map<String, Object> data = new HashMap<>(); data.put("required", captchaRequired); return Result.ok(data); } @SuppressWarnings("unchecked") private List<String> strList(Object o) { if (o instanceof List) { traffic-audit-server/src/main/resources/application.yml.example
@@ -55,3 +55,8 @@ api-key: ${DEEPSEEK_API_KEY:your-deepseek-api-key} api-url: https://api.deepseek.com/v1/chat/completions model: deepseek-chat # Behavior captcha on login. Set to false to skip verification during test phase; enable before production release. app: captcha: required: false traffic-audit-web/src/views/Login.vue
@@ -9,8 +9,11 @@ <el-form-item prop="password"> <el-input v-model="form.password" type="password" placeholder="密码" @keyup.enter.native="login"></el-input> </el-form-item> <el-form-item> <el-form-item v-if="captchaRequired"> <captcha3-d ref="captcha" @verified="onCaptchaVerified" @reset="onCaptchaReset"></captcha3-d> </el-form-item> <el-form-item v-else> <div class="captcha-skip-tip">测试模式:行为验证码已跳过,可直接点击登录(正式发布后将启用)</div> </el-form-item> <el-form-item> <el-button type="primary" :loading="loggingIn" @click="login" style="width:100%">登 录</el-button> @@ -36,8 +39,17 @@ captchaVerified: false, captchaId: '', clickIds: [], loggingIn: false loggingIn: false, captchaRequired: true } }, created() { axios.get('/api/auth/captcha-config') .then(res => { const d = res.data if (d && d.code === 200) this.captchaRequired = d.data.required !== false }) .catch(() => {}) }, methods: { onCaptchaVerified(payload) { @@ -53,7 +65,7 @@ login() { this.$refs.form.validate(valid => { if (!valid) return if (!this.captchaVerified) { if (this.captchaRequired && !this.captchaVerified) { this.$message.warning('请先完成行为验证') return } @@ -75,13 +87,13 @@ this.$router.push('/dashboard') } else if (d.code === 401) { this.$message.error(d.message || '用户名或密码错误') this.$refs.captcha.load() if (this.$refs.captcha) this.$refs.captcha.load() } else if (d.code === 4001 || d.code === 4002 || d.code === 4003) { this.$message.error(d.message || '验证未通过,请重新验证') this.$refs.captcha.load() if (this.$refs.captcha) this.$refs.captcha.load() } else { this.$message.error(d.message || '登录失败,请重试') this.$refs.captcha.load() if (this.$refs.captcha) this.$refs.captcha.load() } }).catch(() => { this.loggingIn = false @@ -94,6 +106,7 @@ </script> <style scoped> .login-container { display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 20px 0; box-sizing: border-box; background: #f0f2f5; } .captcha-skip-tip { font-size: 12px; color: #909399; line-height: 1.6; padding: 8px 10px; background: #F5F7FA; border-radius: 4px; } .login-card { width: 400px; } .login-card h2 { text-align: center; margin-bottom: 18px; } .login-card .el-form-item { margin-bottom: 16px; }