当前位置: 首页 > ai >正文

Python Flask: Windows 2022 server SMB账户(共享盘账户)密码修改

适用于小型组织企业内部 smb共享盘账户密码修改。


环境:

        python3.11

        pip install flask pywin32 ip address

        可选 pip install pyinstaller


效果:


代码:

from flask import Flask, render_template_string, request, flash, abort
import win32net
import win32security
import win32api
import os
import ctypes
import sys
import re
import logging
from logging.handlers import RotatingFileHandler
import ipaddress  # 用于处理IP地址和CIDR段app = Flask(__name__)
app.secret_key = os.urandom(24)  # 用于flash消息# 配置日志
if not os.path.exists('logs'):os.mkdir('logs')
file_handler = RotatingFileHandler('logs/password_changer.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('SMB密码修改工具启动')# 允许访问的IP地址或CIDR段列表
# 支持单个IP(如"127.0.0.1")和CIDR段(如"172.16.0.0/24")
ALLOWED_IPS = [# 本地回环地址# 可以添加更多IP或网段# "192.168.1.0/24",# "10.0.0.100","172.16.0.0/24",  # 示例:允许172.16.0.0-172.16.0.255整个网段"192.168.88.0/24","10.0.202.0/24",
]# 检查是否以管理员身份运行
def is_admin():try:return ctypes.windll.shell32.IsUserAnAdmin()except:return False# 获取客户端真实IP
def get_real_ip():# 检查代理头,获取真实IPif 'X-Forwarded-For' in request.headers:# X-Forwarded-For格式: client_ip, proxy1_ip, proxy2_ip...return request.headers['X-Forwarded-For'].split(',')[0].strip()elif 'X-Real-IP' in request.headers:return request.headers['X-Real-IP']else:return request.remote_addr# 检查IP是否在允许的列表或网段中
def is_ip_allowed(ip):try:client_ip = ipaddress.IPv4Address(ip)for allowed in ALLOWED_IPS:try:# 尝试解析为IP网段network = ipaddress.IPv4Network(allowed, strict=False)if client_ip in network:return Trueexcept ValueError:# 如果不是网段,则尝试解析为单个IPtry:allowed_ip = ipaddress.IPv4Address(allowed)if client_ip == allowed_ip:return Trueexcept ValueError:# 无效的IP格式,跳过app.logger.warning(f"无效的允许IP配置: {allowed}")continuereturn Falseexcept ValueError:app.logger.warning(f"无效的客户端IP地址: {ip}")return False# IP访问限制中间件
@app.before_request
def restrict_ip():# 获取客户端真实IP地址client_ip = get_real_ip()# 记录访问尝试app.logger.info(f"访问尝试 from {client_ip} to {request.path}")# 检查IP是否在允许列表中if not is_ip_allowed(client_ip):# 记录未授权访问尝试app.logger.warning(f"未授权的访问尝试 from {client_ip}")abort(403)  # 拒绝访问# 验证用户密码
def verify_password(username, password):try:# 使用Windows API验证凭据hToken = win32security.LogonUser(username,None,password,win32security.LOGON32_LOGON_INTERACTIVE,win32security.LOGON32_PROVIDER_DEFAULT)hToken.Close()return Trueexcept win32security.error  as e:error_code = e.winerrorerror_msg = e.strerrorapp.logger.warning(f"账户验证发生错误 ==》 {error_code}; {error_msg}")return False# 获取用户全名(用于密码验证)
def get_user_fullname(username):try:server = win32api.GetComputerName()user_info = win32net.NetUserGetInfo(server, username, 1)return user_info.get('comment', '').strip()  # 通常全名存储在comment字段except:return ''# 密码复杂度验证
def validate_password(password, username, fullname):errors = []# 1. 密码长度必须为8至16位if len(password) < 8 or len(password) > 16:errors.append("密码长度必须为8至16位")# 2. 必须包含:数字、大写字母、小写字母、特殊字符中的4种字符has_digit = re.search(r'\d', password) is not Nonehas_upper = re.search(r'[A-Z]', password) is not Nonehas_lower = re.search(r'[a-z]', password) is not Nonehas_special = re.search(r'[^A-Za-z0-9]', password) is not Noneif sum([has_digit, has_upper, has_lower, has_special]) < 4:errors.append("密码必须包含数字、大写字母、小写字母和特殊字符")# 3. 连续3位及以上数字不能连号(例如123、654)digit_sequences = re.findall(r'\d{3,}', password)for seq in digit_sequences:for i in range(len(seq) - 2):triplet = seq[i:i+3]# 检查升序连号 (123)if int(triplet[0]) + 1 == int(triplet[1]) and int(triplet[1]) + 1 == int(triplet[2]):errors.append(f"密码包含连续数字序列: {triplet}")break# 检查降序连号 (321)if int(triplet[0]) - 1 == int(triplet[1]) and int(triplet[1]) - 1 == int(triplet[2]):errors.append(f"密码包含连续数字序列: {triplet}")break# 4. 连续3位及以上字母不能连续(例如abc、cba)alpha_sequences = re.findall(r'[A-Za-z]{3,}', password)for seq in alpha_sequences:for i in range(len(seq) - 2):triplet = seq[i:i+3].lower()# 检查升序连续 (abc)if ord(triplet[0]) + 1 == ord(triplet[1]) and ord(triplet[1]) + 1 == ord(triplet[2]):errors.append(f"密码包含连续字母序列: {seq[i:i+3]}")break# 检查降序连续 (cba)if ord(triplet[0]) - 1 == ord(triplet[1]) and ord(triplet[1]) - 1 == ord(triplet[2]):errors.append(f"密码包含连续字母序列: {seq[i:i+3]}")break# 5. 密码不能包含连续3个及以上相同字符(例如aaa、rrr)same_chars = re.findall(r'(.)\1{2,}', password)if same_chars:errors.append(f"密码包含连续相同字符: {', '.join(set(same_chars))}")# 6. 密码不能包含账号if username.lower() in password.lower():errors.append("密码不能包含用户名")# 7. 密码不能包含用户姓名大小写全拼if fullname:# 检查全名的各种形式name_variations = [fullname.lower(),fullname.replace(' ', '').lower(),fullname.replace(' ', '').upper(),fullname.upper()]for variation in name_variations:if variation and variation in password:errors.append("密码不能包含用户姓名")breakreturn errors# 修改用户密码
def change_password(username, old_password, new_password):try:if not verify_password(username, old_password):return False, "原密码不正确"# 获取用户全名用于密码验证fullname = get_user_fullname(username)# 验证新密码是否符合规则password_errors = validate_password(new_password, username, fullname)if password_errors:return False, ";".join(password_errors)server = win32api.GetComputerName()# 设置密码需要管理员权限win32net.NetUserChangePassword(server, username, old_password, new_password)return True, "密码修改成功"except win32net.error as e:# 处理不同的错误情况error_code = e.args[0]if error_code == 2221:return False, "用户名不存在"elif error_code == 86:return False, "原密码不正确"elif error_code == 2245:return False, "新密码不符合系统密码策略"else:return False, f"修改失败: {str(e.args)}"# 主页面模板 - 直接输入用户名和旧密码
index_template = '''
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>SMB账户密码修改</title><link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
</head>
<body class="bg-gray-100 min-h-screen"><div class="container mx-auto px-4 py-8 max-w-3xl"><h1 class="text-3xl font-bold text-center mb-8 text-gray-800">SMB账户密码修改</h1>{% with messages = get_flashed_messages() %}{% if messages %}<div class="mb-6">{% for message in messages %}<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">{{ message }}</div>{% endfor %}</div>{% endif %}{% endwith %}<div class="bg-white rounded-lg shadow-md p-6"><form method="POST" action="/verify-password"><div class="mb-4"><label for="username" class="block text-gray-700 text-sm font-bold mb-2">用户名</label><input type="text" id="username" name="username" requiredclass="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"></div><div class="mb-4"><label for="old_password" class="block text-gray-700 text-sm font-bold mb-2">输入旧密码</label><input type="password" id="old_password" name="old_password" requiredclass="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"></div><div class="flex items-center justify-between"><button type="submit" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">验证并修改密码</button></div></form></div></div>
</body>
</html>
'''# 修改密码页面模板 - 包含密码规则说明
change_password_template = '''
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>修改密码 - SMB账户密码修改</title><link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
</head>
<body class="bg-gray-100 min-h-screen"><div class="container mx-auto px-4 py-8 max-w-2xl"><h1 class="text-2xl font-bold text-center mb-6 text-gray-800">修改密码</h1><h2 class="text-center text-gray-600 mb-8">用户: {{ username }}</h2>{% with messages = get_flashed_messages() %}{% if messages %}<div class="mb-6">{% for message in messages %}<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">{{ message }}</div>{% endfor %}</div>{% endif %}{% endwith %}<div class="grid grid-cols-1 md:grid-cols-12 gap-6"><div class="md:col-span-7 bg-white rounded-lg shadow-md p-6"><form method="POST" action="/do-change-password"><input type="hidden" name="username" value="{{ username }}"><input type="hidden" name="old_password" value="{{ old_password }}"><div class="mb-4"><label for="new_password" class="block text-gray-700 text-sm font-bold mb-2">新密码</label><input type="password" id="new_password" name="new_password" requiredclass="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"></div><div class="mb-6"><label for="confirm_password" class="block text-gray-700 text-sm font-bold mb-2">确认新密码</label><input type="password" id="confirm_password" name="confirm_password" requiredclass="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"></div><div class="flex items-center justify-between"><button type="submit" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">确认修改</button><a href="/" class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800">返回主页</a></div></form></div><div class="md:col-span-5 bg-blue-50 rounded-lg p-6"><h3 class="text-lg font-semibold mb-3 text-gray-800"><i class="fa fa-info-circle text-blue-500 mr-2"></i>密码规则</h3><ul class="text-sm text-gray-700 space-y-1"><li><i class="fa fa-check-circle text-blue-500 mr-1"></i>必须包含数字、大写字母、小写字母和特殊字符</li><li><i class="fa fa-check-circle text-blue-500 mr-1"></i>长度必须为8至16位</li><li><i class="fa fa-check-circle text-blue-500 mr-1"></i>不能包含连续3位及以上连号数字(如123、654)</li><li><i class="fa fa-check-circle text-blue-500 mr-1"></i>不能包含连续3位及以上连续字母(如abc、cba)</li><li><i class="fa fa-check-circle text-blue-500 mr-1"></i>不能包含连续3个及以上相同字符(如aaa、111)</li><li><i class="fa fa-check-circle text-blue-500 mr-1"></i>不能包含用户名</li><li><i class="fa fa-check-circle text-blue-500 mr-1"></i>不能包含用户姓名的全拼</li></ul></div></div></div>
</body>
</html>
'''# 成功页面模板
success_template = '''
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>操作成功 - SMB账户密码修改</title><link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center"><div class="bg-white rounded-lg shadow-md p-8 max-w-md w-full text-center"><div class="text-green-500 text-5xl mb-4"><i class="fa fa-check-circle"></i></div><h1 class="text-2xl font-bold text-gray-800 mb-2">操作成功</h1><p class="text-gray-600 mb-6">{{ message }}</p><a href="/" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline inline-block">返回主页</a></div>
</body>
</html>
'''# 403错误页面
@app.errorhandler(403)
def forbidden(error):client_ip = get_real_ip()return '''<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><title>访问被拒绝</title><link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css"></head><body class="bg-gray-100 min-h-screen flex items-center justify-center"><div class="bg-white rounded-lg shadow-md p-8 max-w-md w-full text-center"><div class="text-red-500 text-5xl mb-4"><i class="fa fa-exclamation-triangle"></i></div><h1 class="text-2xl font-bold text-gray-800 mb-2">访问被拒绝</h1><p class="text-gray-600 mb-6">您的IP地址(''' + client_ip + ''')没有权限访问此页面。</p></div></body></html>''', 403@app.route('/')
def index():return render_template_string(index_template)@app.route('/verify-password', methods=['POST'])
def verify_password_route():username = request.form.get('username')old_password = request.form.get('old_password')client_ip = get_real_ip()if not username or not old_password:flash("请输入用户名和密码")return render_template_string(index_template)app.logger.info(f"用户 {username} 尝试验证密码 from {client_ip}")if verify_password(username, old_password):app.logger.info(f"用户 {username} 密码验证成功 from {client_ip}")return render_template_string(change_password_template, username=username, old_password=old_password)else:app.logger.warning(f"用户 {username} 密码验证失败 from {client_ip}")# 不明确提示是用户名不存在还是密码错误,提高安全性flash("用户名或密码不正确")return render_template_string(index_template)@app.route('/do-change-password', methods=['POST'])
def do_change_password():username = request.form.get('username')old_password = request.form.get('old_password')new_password = request.form.get('new_password')confirm_password = request.form.get('confirm_password')client_ip = get_real_ip()app.logger.info(f"用户 {username} 尝试修改密码 from {client_ip}")if new_password != confirm_password:flash("两次输入的新密码不一致")return render_template_string(change_password_template, username=username, old_password=old_password)success, message = change_password(username, old_password, new_password)if success:app.logger.info(f"用户 {username} 密码修改成功 from {client_ip}")return render_template_string(success_template, message=message)else:app.logger.warning(f"用户 {username} 密码修改失败: {message} from {client_ip}")flash(message)return render_template_string(change_password_template, username=username, old_password=old_password)if __name__ == '__main__':# 检查是否以管理员身份运行if not is_admin():print("错误:此应用程序需要以管理员权限运行才能修改用户密码。")print("请右键点击命令提示符或PyCharm,选择'以管理员身份运行',然后重新启动应用。")sys.exit(1)# 安装必要的依赖提示try:import ipaddressexcept ImportError:print("检测到缺少ipaddress模块,正在尝试安装...")import subprocesssubprocess.check_call([sys.executable, "-m", "pip", "install", "ipaddress"])# 启动应用app.run(host='172.16.0.253', port=8080, debug=True) 

注意:

        需要管理员权限运行

http://www.xdnf.cn/news/16492.html

相关文章:

  • Java注解全面解析与应用实战
  • 在Word和WPS文字中把全角数字全部改为半角
  • 微信小程序无法构建npm,可能是如下几个原因
  • uniapp 微信小程序 列表点击分享 不同的信息
  • 计算机视觉-图像基础处理
  • 一步步详解使用 Flask 连接数据库进行增删改查操作
  • 【PHP】几种免费的通过IP获取IP所在地理位置的接口(免费API接口)
  • 硬件学习笔记--73 电能表新旧精度等级对应关系
  • Android 解决键盘遮挡输入框
  • Javaweb————HTTP请求头属性讲解
  • 前端css 的固定布局,流式布局,弹性布局,自适应布局,响应式布局
  • VNC和RPC加固措施
  • win10 环境删除文件提示文件被使用无法删除怎么办?
  • 海外短剧系统架构设计:从0到1搭建高并发微服务平台
  • 白玩 一 记录retrofit+okhttp+flow 及 kts的全局配置
  • 墨者:SQL过滤字符后手工注入漏洞测试(第3题)
  • npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
  • 什么是ios企业签名?
  • VTK开发笔记(一):VTK介绍,Qt5.9.3+VS2017x64+VTK8.2编译
  • 使用 Django REST Framework 构建强大的 API
  • vue请求golang后端CORS跨域问题深度踩坑
  • 分布式链路追踪详解
  • 图论:Bellman_ford算法
  • 预过滤环境光贴图制作教程:第三阶段 - GGX 分布预过滤
  • Unity 编辑器开发 之 Excel导表工具
  • git使用lfs解决大文件上传限制
  • 监控场景视频质量异常修复:陌讯动态增强算法实战解析
  • 使用JavaScript实现轮播图的自动切换和左右箭头切换效果
  • BERT 的 NSP慢慢转换为SOP
  • Linux -- 文件【中】