首页 > 技术文章 > 登录验证程序-[sys][datetime][json][hashlib]模块的运用

a72hongjie 2018-05-16 01:44 原文

程序目录结构

├── account
│   └── 1234.json
└── bin
      └── user_login.py

1234.json

{
  "expire_date": "2020-01-01",
  "id": 1234,
  "status": 0,
  "pay_day": 22,
  "password": "950b80f9ec4a84bdb828400a404e55d5",
  "salt": "eq8nXoV9Ndh6"
}

user_login.py

# -*- coding: utf-8 -*-
# @Author  : 'hhjie'
# @Time    : 2018-05-14 13:18
# @File    : 综合练习-01---用户登录-json.py

"""
{"expire_date": "2021-01-01", "id": 1234, "status": 0, "pay_day": 22, "password": "abc"}

①:
1、用户名为json文件名,密码为 password。
2、判断是否过期,与expire_date进行对比。
3、登陆成功后,打印“登陆成功”,三次登陆失败,status值改为1,并且锁定账号。

分析:
1、用户名为json文件名,意思是每个用户对应一个json文件。如果用户输入用户名,则在用户文件夹中寻找是否包含该用户的用户文件。要有读取json文件的函数。
2、判断是否过期,意思为通过时间模块获取到年月日。和文件中的"expire_date"日期进行比对。由于是字符串类型。需要转换为时间戳。
    用当前时间减去"expire_date"的时间。如果结果为负数或者0.则未过期,反之。如果大于0,则过期。
3、三次登录失败后,要修改状态。要写入文件。需要单独写一个写入文件的函数。锁定账号,就是在判断中,如果被锁定了,直接退出。

②:
三次验证的密码进行hashlib加密处理。即:json文件保存为md5的值,然后用md5的值进行验证。
独立的函数用hashlib处理密码加密

分析:
由于hashlib可通过反推表反推出字符串。所有我这处理的方式是将密码进行加盐操作,得到唯一的md5值。
这个'盐'利用随机字符生成,并保存在用户列表中。用变量 user_salt 表示。当然这个salt和用户之间在真实数据库中需要另外一张表对应。

"""

import os
import json
import string
import random
import datetime
import hashlib


def load_user_info(user_file):
    user_info = json.load(open(user_file, mode='r', encoding='utf-8'))
    return user_info


def dump_user_info(user_info, user_file):
    temp_file = user_file + ".bak"
    json.dump(user_info, open(temp_file, mode='w', encoding='utf-8'))

    os.remove(user_file)
    os.rename(temp_file, user_file)


def get_files_of_dir(dir_path):
    all_files_dirs = os.listdir(dir_path)
    return all_files_dirs


def is_expired(expire_date):
    today = datetime.datetime.today()
    expire_day = datetime.datetime.strptime(expire_date, "%Y-%m-%d")
    # 注意:传入的参数时间字符串为:【"2021-01-01"】但是经过转换为元组时间时,变为【"2021-01-01 00:00:00"】
    delta = today - expire_day  # 结果为3 days, 0:00:00 这种形式的时间格式。其中有很多方法。days拿到天数。
    if delta.days > 0:  # 如果比对日期为正数的话,则过期了。返回True,意为过期为真。
        print("您的账号已过期,请联系管理员")
        return True
    else:
        print("账号离过期还剩【{0}】天".format(abs(delta.days)))
        return False


def encrypt_password(password, salt=None):
    # 两个功能,一是新用户创建时,未给定salt,则随机生成salt并加密密码,返回salt和hash值。二是非新用户登录时,从文件信息中获取到salt和密码加密

    if not salt:  # 如果salt没有值,就将随机字符串赋值给salt。
        salt = ''.join(
            random.sample(string.digits + string.ascii_letters, 12))  # 生成16位的随机字符串作为加密的salt。
    password_hash = hashlib.md5()
    password_hash.update((password + salt).encode('utf-8'))  # 将密码和salt进行合并字符串进行加密
    return salt, password_hash.hexdigest()


def start():
    try_count = 0
    exit_flag = False
    while not exit_flag:
        username = input("请输入用户名:")
        username_file_name = username + ".json"
        username_file_path = os.path.join(account_dir, username_file_name)
        if username_file_name in get_files_of_dir(account_dir):
            username_info = load_user_info(username_file_path)  # 从用户json文件中获取用户信息
            expire_date = username_info["expire_date"]
            is_expire = is_expired(expire_date)
            if is_expire:
                exit_flag = True
            while not exit_flag:
                if username_info["status"] == 1:
                    print("您的账号已被锁定,请联系系统管理员。")
                    exit_flag = True
                    break
                if try_count == 3:  # 先判断尝试次数是否等于3次。
                    username_info["status"] = 1
                    dump_user_info(username_info, username_file_path)  # 这里的文件一定要是绝对路径
                    print("尝试登录系统次数超限,账号已被锁定。")
                    exit_flag = True
                    break  # 这里要有break,因为虽然这里为True,整个系统退出了,但是还会执行下面的代码,因此要用break。下面代码不继续执行了。
                user_password = input("请输入密码: ")
                user_encrypt_password = encrypt_password(user_password, username_info["salt"])[1]  # 获取加密密码的hash值。
                if user_encrypt_password == username_info["password"]:
                    print("欢迎您【{0}】,登录系统成功".format(username))
                    exit_flag = True
                else:
                    print("密码错误,请重试...")
                    try_count += 1
                    left_try_count = 3 - try_count
                    if left_try_count == 0:  # 如果剩余尝试次数为0的话,就跳过下面代码。
                        continue
                    print("还有{0}次尝试登录系统的机会!".format(left_try_count))
        else:
            print("用户名不存在,请重试")


if __name__ == '__main__':
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # 获取当前绝对路径的目录名
    account_dir = os.path.join(BASE_DIR, "account")  # 用户账户目录
    start()

推荐阅读