用户登录和注册:不能在C中注册多个用户

时间:2018-11-21 15:56:21

标签: c authentication login registration

我正在从事这个c项目,该项目处理用户注册和登录程序。问题是该程序成功,但仅对一个用户有效。你们能解释什么地方错了,我应该改变什么?预先感谢。

P.S 这是我在stackoverflow中的第一个问题

英语不是我的主要语言

无论如何,下面是我在C语言中的代码:

#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static int i=0;
struct w
{
char nama[30],pass[30];
}
w[100];
int n;
void login(void);
void reg(void);
int main(void)
{
menu:
system("cls");
printf("\n\n\n\n\n\t\t\t\tWELCOME!");
printf("\n\n\n\n\t\t\t\t\t\t[ENTER]");
if(getch()==13)
{
    system("cls");
}
    else
{
    goto menu;
}
menu_main:
printf("\n\n\n\t\t\t[1] LOGIN\t\t[2] REGISTRATION\t\t[3] EXIT APP");
printf("\n\n\n\t\t\t\t  INPUT YOUR SELECTION THEN PRESS [ENTER]: ");
scanf("%d",&n);
switch(n)
  {
    case 1: system("cls");
        login();
        break;
    case 2: system("cls");
        reg();
        break;
    case 3:system("cls");
        printf("\n\n\t\t\t\tTHANK YOU FOR USING THIS APP\n");
        break;
    default: system("cls"); printf("\n\n\t\t\t\tNOT AVAILABLE");
        printf("\n\n\t\t\tPRESS ANY KEY TO GO BACK");
        getch();
        system("cls"); goto menu_main;
    }
}
void reg()
  {
    FILE *fp;
    char c,username[30]; int z=0;
    fp=fopen("file.txt","ab+");
    system("cls");
    printf("\n\n\t\tPLEASE INPUT USERNAME & PASSWORD");
    for(i=0;i<99;i++)
    {
      printf("\n\n\t\t\t\t  USERNAME: ");
      scanf("%s",username);
        while(!feof(fp))
        {
          fread(&w[i],sizeof(w[i]),1,fp);
          if(strcmp(username,w[i].nama)==0)
            {
            system("cls");
            printf("\n\n\t\t\t  USERNAME IS NOT AVAILABLE");
            printf("\n\n\t\t\t  PRESS ANY KEY TO GO BACK");
            getch();
            system("cls"); reg();
            }
          else
          {
            strcpy(w[i].nama,username);
            break;
          }
        }
      z=0;
      printf("\n\n\t\t\t\t  PASSWORD: ");
      while((c=getch())!=13)
        {
          w[i].pass[z++]=c;
          printf("%c",'*');
        }
      fwrite(&w[i],sizeof(w[i]),1,fp);
      fclose(fp);
      printf("\n\n\tPRESS [ENTER] IF YOU AGREE");
      if((c=getch())==13)
        {
        system("cls");
        printf("\n\n\t\tYOU ARE REGISTERED!");
        printf("\n\n\t\tWOULD YOU LIKE TO LOGIN?\n\n\t\t  ");
        printf(" PRESS [1] FOR YES\n\n\t\t   PRESS [2] FOR NO\n\n\t\t\t\t");
        scanf("%d",&n);
        if(n==1)
          { 
            system("cls");
            login();
          }
            else if(n==2)
          {
            system("cls");
            printf("\n\n\n\t\t\t\tTHANK YOU FOR REGISTERING IN THIS APP\n");
          } 
        }
        break;
      }
  }
  void login()
    {
      FILE *fp;
      char c,nama[10],pass[10]; int z=0;
      int cekun,cekpw;
      fp=fopen("file.txt","r");
      for(i=0;i<=10;i++)
      {
        printf("\n\n\t\t\t\t  USERNAME: ");
        scanf("%s",nama);

        system("cls");
        printf("\n\n\t\t\t\t  PASSWORD: ");
        while((c=getch())!=13)
        {
          pass[z++]=c;
          printf("%c",'*');
        }
        pass[z]='\0';
        while(!feof(fp))
        {
        fread(&w[i],sizeof(w[i]),1,fp);
          cekun=strcmp(nama,w[i].nama);
          cekpw=strcmp(pass,w[i].pass);
          if(cekun==0&&cekpw==0)
          {
            system("cls");
            printf("\n\n\n\t\t\tLOGIN SUCCESSFUL!");
            break;
          }
        else if(cekun==0)
          {
            printf("\n\n\n\t\t\tWRONG PASSWORD!");
            printf("\n\n\t\t\t\t  (PRESS [Y] TO RE-LOGIN)");
            if(getch()=='y'||getch()=='Y')
              system("cls"); login();
          }
        else if(cekun!=0&&cekpw!=0)
          {
            h:
            printf("\n\n\n\t\t\tYOU ARE NOT REGISTERED\n \t\t\tPRESS [ENTER] TO REGISTER");
            if(getch()==13)
            system("cls"); reg();
          }
          else if(cekun!=0&&cekpw==0)
          {
            goto h;
          }
        }
        break;
      }
      getch();
    }

2 个答案:

答案 0 :(得分:0)

不适用于多个用户的一个原因是myFormData.append('myJSObject', JSON.stringify(myObject)); var myFile = document.getElementById('myinput').files[0]; myFormData.append('myfile', myFile); 函数的逻辑结构。

                    $.ajax({
                    type: "POST",
                    url: "/SomeController",
                    async:false,
                    data: myFormData,
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",

该代码将拒绝任何登录尝试,除非用户名和密码与文件中的第一个条目 匹配。

相反,您应该实现以下逻辑:

login

答案 1 :(得分:0)

与其编写提示和检查功能,不如在单独的功能中进行检查。

例如,假设您使用

#define  MAX_NAME_LEN  31
#define  MAX_PASS_LEN  31

typedef struct {
    char  name[MAX_NAME_LEN + 1];
    char  pass[MAX_PASS_LEN + 1];
} userinfo;

存储有关已知用户的信息。您的用户数据库可能就是

#define  MAX_USERS  100

static userinfo  known_user[MAX_USERS];
static size_t    known_users = 0;

要注册新用户,请编写一个以用户名和密码作为参数的函数:

int register_user(const char *user, const char *pass)
{
    const size_t  userlen = (user) ? strlen(user) : 0;
    const size_t  passlen = (pass) ? strlen(pass) : 0;
    size_t        i;

    if (userlen < 1)
        return -1; /* Empty username */
    if (userlen > MAX_NAME_LEN)
        return -2; /* Username is too long */
    if (passlen < 1)
        return -3; /* Empty password */
    if (passlen > MAX_PASS_LEN)
        return -4; /* Password is too long */

    /* Check if this user is already known. */
    for (i = 0; i < known_users; i++)
        if (!strcmp(user, known_user[i].name))
            return -5; /* Username is already registered. */

    if (known_users >= MAX_USERS)
        return -6; /* User database is full. */

    /* Add this user to the user database. */
    strncpy(known_user[known_users].name, user, MAX_NAME_LEN + 1);
    strncpy(known_user[known_users].pass, pass, MAX_PASS_LEN + 1);

    known_users++;

    return 0; /* Success */
}

请注意,(user) ? strlen(user) : 0是三元表达式。这意味着如果user不为NULL,则表达式的计算结果为strlen(user),否则它的计算结果为0。(是的,它类似于if子句的紧凑形式。)经常看到strlen()像这样包装,因为strlen(NULL)不安全,并且可能使程序崩溃:根据C标准,它被可怕地 Undefined Behaviour 。 (他们说 UB 可能会导致守护程序从您的鼻子中飞出,但是我认为这是一个白胡子使我们认真对待 UB 的恐怖故事。)

(!strcmp(...))(strcmp(...) == 0)等效,如果两个字符串完全匹配,则为true。

类似地,要检查用户名和密码对是否与已知用户匹配,

int verify_user(const char *user, const char *pass)
{
    const size_t  userlen = (user) ? strlen(user) : 0;
    const size_t  passlen = (pass) ? strlen(pass) : 0;
    size_t        i;

    if (userlen < 1)
        return -1; /* Empty username */
    if (userlen > MAX_NAME_LEN)
        return -2; /* Username is too long */
    if (passlen < 1)
        return -3; /* Empty password */
    if (passlen > MAX_PASS_LEN)
        return -4; /* Password is too long */

    for (i = 0; i < known_users; i++)
        if (!strcmp(user, known_user[i].name)) {
            if (!strcmp(pass, known_user[i].pass))
                return 0;  /* Valid user */
            else
                return -6; /* Wrong password */
        }

    return -7; /* No such user. */
}

这时,您可以编写一个小的测试程序,并测试上述功能是否有效。这称为unit testing

int main(void)
{
    int result;

    result = register_user("foo", "bar");
    if (result) {
        fprintf(stderr, "register_user(\"foo\", \"bar\") failed: %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "register_user(\"foo\", \"bar\") worked as expected (0).\n");

    result = register_user("user", "pass");
    if (result) {
        fprintf(stderr, "register_user(\"user\", \"pass\") failed: %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "register_user(\"user\", \"pass\") worked as expected (0).\n");

    result = register_user("foo", "irrelevant");
    if (result) {
        fprintf(stderr, "register_user(\"foo\", \"irrelevant\") failed. Expected -5, received %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "register_user(\"foo\", \"irrelevant\") worked as expected (-5).\n");

    result = verify_user("no such", "user or password");
    if (result != -7) {
        fprintf(stderr, "verify_user(\"no such\", \"user or password\") failed. Expected -7, received %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "verify_user(\"no such\", \"user or password\") worked as expected (-7).\n");

    result = verify_user("foo", "wrong password");
    if (result != -6) {
        fprintf(stderr, "verify_user(\"foo\", \"wrong password\") failed. Expected -6, received %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "verify_user(\"foo\", \"wrong password\") worked as expected (-6).\n");

    result = verify_user("foo", "bar");
    if (result != 0) {
        fprintf(stderr, "verify_user(\"foo\", \"bar\") failed. Expected 0, received %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "verify_user(\"foo\", \"bar\") worked as expected (0).\n");

    printf("All tests successful.\n");
    return EXIT_SUCCESS;
}

当您精通编程时,可以使用多种方法“自动”进行此类测试。

仅当您的测试程序可以正确进行测试(并同时测试正确和不正确的输入及其结果!)时,才继续解决原始问题。

这样,您知道这两个功能都可以工作,并且知道任何问题或错误都必须在其他地方。 (如果不确定,可以将该输入添加到单元测试程序中,然后进行验证!)


以纯文本存储密码不是一个好主意。曾经您也不需要这样做。

您应该做的是使用hash function将密码转换为大量密码;哈希函数应该是密码学上安全的。密码也应为salted:随机数或字符串,以纯文本格式保存,并在密码之前(或附加或同时附加),并提供给哈希函数。为了使任何字典攻击变慢,通常会大量应用散列函数。这个数字可以变化,但是固定的回合数通常同样安全。

在这种情况下,用户信息类似于

#define  MAX_NAME_LEN  31
#define  HASH_SIZE     64    /* for 512-bit hashes like SHA512 */
#define  SALT_SIZE     16    /* for 128-bit salt */
#define  HASH_ROUNDS   5000

typedef struct {
    char           name[MAX_NAME_LEN + 1];
    unsigned char  salt[SALT_SIZE];
    unsigned char  hash[HASH_SIZE];
} userinfo;

假设我们可以使用的加密安全哈希函数声明为

void crypto_hash(unsigned char *hash, const void *data, const size_t size);

,我们有一个加密安全的随机数生成器,当需要使用时会生成一个随机盐

void crypto_salt(unsigned char *salt);

然后我们的用户注册功能变为

int register_user(const char *user, const char *pass)
{
    const size_t   userlen = (user) ? strlen(user) : 0;
    const size_t   passlen = (pass) ? strlen(pass) : 0;
    unsigned char *temp;
    unsigned char  hash[2][HASH_SIZE];
    size_t         i;

    if (userlen < 1)
        return -1; /* Empty username */
    if (userlen > MAX_NAME_LEN)
        return -2; /* Username is too long */
    if (passlen < 1)
        return -3; /* Empty password */

    /* TODO: Password strength checking! */

    /* Check if this user is already known. */
    for (i = 0; i < known_users; i++)
        if (!strcmp(user, known_user[i].name))
            return -5; /* Username is already registered. */

    if (known_users >= MAX_USERS)
        return -6; /* User database is full. */

    /* Generate a random salt for this new user. */
    crypto_salt(known_user[i].salt);

    /* Allocate a temporary buffer for the salt and the password.
       Here, for simplicity, we just prepend the salt to the password,
       and include the string-terminating nul byte, '\0'.
       This is to protect against a certain type of tail attacks. */
    temp = malloc(SALT_SIZE + pass_len + 1);
    if (!temp)
        return -8; /* Not enough memory. */

    /* Combine the salt and the password, including '\0' at end. */
    memcpy(temp, known_user[known_users].salt, SALT_SIZE);
    memcpy(temp + SALT_SIZE, pass, passlen + 1);

    /* First hashing round. */
    crypto_hash(hash[0], temp, SALT_SIZE + passlen + 1);

    /* The temporary buffer is no longer needed.
       We wipe, then free it. */
    memset(temp, 0, SALT_SIZE + passlen + 1);
    free(temp);

    /* Repeated rounds. */
    for (i = 0; i < HASH_ROUNDS - 2; i++)
        crypto_hash(hash[(i+1) & 1], hash[i & 1], HASH_SIZE);

    /* Final round. Since i was incremented after the last
       crypto_hash() call, the last hash is in hash[i & 1]. */
    crypto_hash(known_user[known_users].hash, hash[i & 1], HASH_SIZE);

    /* Wipe the temporary hash table. */
    memset(hash[0], 0, HASH_SIZE);
    memset(hash[1], 0, HASH_SIZE);

    /* Add this user to the user database. */
    strncpy(known_user[known_users].name, user, MAX_NAME_LEN + 1);
    known_users++;
    return 0; /* Success */
}

使用了两个临时哈希槽,因为通常crypto_hash()函数期望结果位于源之外的其他地方。

用户名和密码检查变为

int verify_user(const char *user, const char *pass)
{
    const size_t   userlen = (user) ? strlen(user) : 0;
    const size_t   passlen = (pass) ? strlen(pass) : 0;
    unsigned char *temp;
    unsigned char  hash[2][HASH_SIZE];
    size_t         i, u;
    int            result;

    if (userlen < 1)
        return -1; /* Empty username */
    if (userlen > MAX_NAME_LEN)
        return -2; /* Username is too long */
    if (passlen < 1)
        return -3; /* Empty password */

    for (u = 0; i < known_users; i++)
        if (!strcmp(user, known_user[u].name))
            break; /* u will be less than known_users */
    if (u >= known_users)
        return -7; /* No such user */

    /* known_user[u] matches the name. */

    /* Allocate a temporary area for combining this users' salt
       and the supplied password. */
    temp = malloc(HASH_SIZE + passlen + 1);
    if (!temp)
        return -8; /* Out of memory. */

    /* Combine the salt and the password the same way as when
       the user and password was registered. */
    memcpy(temp, known_user[u].salt, SALT_SIZE);
    memcpy(temp + SALT_SIZE, pass, passlen + 1);

    /* Calculate the hash the same way. First pass: */
    crypto_hash(hash[0], temp, SALT_SIZE + passlen + 1);

    /* Temporary buffer is no longer needed, so clear and free it. */
    memset(temp, 0, SALT_SIZE + passlen + 1);
    free(temp);

    /* Repeated rounds, including the final round. */
    for (i = 0; i < HASH_ROUNDS - 1; i++)
        crypto_hash(hash[(i+1) & 1], hash[i & 1], HASH_SIZE);

    /* Because i was incremented after the last crypto_hash() call,
       hash[i & 1] is where the final hash is. Compare. */
    if (!memcmp(hash[i & 1], known_user[u].hash))
        result =  0; /* Valid password for the user! */
    else
        result = -6; /* Wrong password. */

    /* Wipe the temporary hash buffer. */
    memset(hash[0], 0, HASH_SIZE);
    memset(hash[1], 0, HASH_SIZE);

    return result;
}

在实际应用中,最好不要区分“未知用户” “错误密码” ,因为知道哪些用户名是有效的,可以使某些攻击更容易。