模擬使用者登入、限制嘗試次數是 C 語言基礎 I/O、字串比較、迴圈控制的綜合練習。本文提供標準實作、安全性強化版(隱藏輸入、緩衝區保護)、以及擴展方向(帳號鎖定、日誌記錄、多使用者支援)。
#include <stdio.h>
#include <string.h>
int main() {
const char *password = "123456"; // 預設密碼
char input[20];
int attempts = 0;
const int MAX_ATTEMPTS = 3;
for (attempts = 0; attempts < MAX_ATTEMPTS; attempts++) {
printf("請輸入密碼: ");
if (scanf("%19s", input) != 1) { // 限制長度防溢位
printf("輸入錯誤\n");
continue;
}
if (strcmp(password, input) == 0) {
printf("登入成功!\n");
break;
} else {
printf("密碼錯誤,剩餘嘗試 %d 次\n", MAX_ATTEMPTS - attempts - 1);
}
}
if (attempts == MAX_ATTEMPTS) {
printf("錯誤次數過多,帳號暫時鎖定\n");
}
return 0;
}
#include <stdio.h>
#include <string.h>
#include <termios.h> // Linux/Unix 隱藏輸入
#include <unistd.h> // STDIN_FILENO
// 關閉回顯
void disableEcho() {
struct termios tty;
tcgetattr(STDIN_FILENO, &tty);
tty.c_lflag &= ~ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &tty);
}
// 開啟回顯
void enableEcho() {
struct termios tty;
tcgetattr(STDIN_FILENO, &tty);
tty.c_lflag |= ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &tty);
}
// 安全讀取一行(含長度限制、去換行)
bool readLineSafe(char *buf, size_t bufSize) {
if (fgets(buf, bufSize, stdin) == NULL) return false;
size_t len = strlen(buf);
if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
return true;
}
int main() {
const char *password = "123456";
char input[20];
int attempts = 0;
const int MAX_ATTEMPTS = 3;
printf("=== 登入系統 ===\n");
for (attempts = 0; attempts < MAX_ATTEMPTS; attempts++) {
printf("密碼: ");
disableEcho(); // 隱藏輸入
bool ok = readLineSafe(input, sizeof(input));
enableEcho(); // 恢復回顯
printf("\n");
if (!ok) {
printf("讀取失敗\n");
continue;
}
if (strcmp(password, input) == 0) {
printf("✅ 登入成功!歡迎回來。\n");
return 0;
} else {
int remain = MAX_ATTEMPTS - attempts - 1;
if (remain > 0)
printf("❌ 密碼錯誤,還有 %d 次機會。\n", remain);
}
}
printf("🔒 錯誤次數達上限,帳號已鎖定。請稍後再試或聯繫管理員。\n");
return 1;
}
#include <windows.h>
#include <conio.h> // _getch
void readPasswordWin(char *buf, int maxLen) {
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode;
GetConsoleMode(hStdin, &mode);
SetConsoleMode(hStdin, mode & ~ENABLE_ECHO_INPUT); // 關閉回顯
int i = 0;
while (i < maxLen - 1) {
int ch = _getch();
if (ch == '\r' || ch == '\n') break;
if (ch == 8 || ch == 127) { // Backspace
if (i > 0) {
i--;
printf("\b \b"); // 視覺回退
}
} else {
buf[i++] = ch;
putchar('*'); // 顯示星號
}
}
buf[i] = '\0';
SetConsoleMode(hStdin, mode); // 恢復模式
printf("\n");
}
// 使用者結構
typedef struct {
char username[32];
char passwordHash[64]; // 存儲雜湊而非明文
int failedAttempts;
time_t lockUntil; // 鎖定解除時間
bool isLocked;
} User;
// 驗證流程
bool authenticate(User *user, const char *inputPass) {
if (user->isLocked && time(NULL) < user->lockUntil) {
printf("帳號鎖定中,%ld 秒後解鎖\n", user->lockUntil - time(NULL));
return false;
}
if (verifyHash(inputPass, user->passwordHash)) { // bcrypt/argon2 驗證
user->failedAttempts = 0;
user->isLocked = false;
return true;
} else {
user->failedAttempts++;
if (user->failedAttempts >= 5) {
user->isLocked = true;
user->lockUntil = time(NULL) + 300; // 鎖定 5 分鐘
}
return false;
}
}
scanf("%19s")、fgets(buf, sizeof(buf)) 防止緩衝區溢位。sleep(1) 增加暴力破解成本。timingsafe_bcmp 防止時序攻擊。從簡單的三次嘗試限制出發,逐步建立完整的認證安全體系,是每位後端開發者必經的成長路徑。建議實作上述強化版,並嘗試整合 SQLite 儲存使用者資料、使用 OpenSSL 進行密碼雜湊,打造可用於實際專案的登入模組。