在设计一个要求用户每90天更改一次密码的系统时,后端的设计需要包括以下几个关键点:如何判断密码是否需要更改、如何告知前端、如何处理密码的更新请求。
1. 数据存储设计
后端需要在数据库中存储用户密码以及密码的最后修改时间。这可以通过在用户表中添加两个字段来实现:
password
: 存储用户的哈希密码。password_last_changed
: 记录密码上次修改的时间,使用时间戳或日期格式。
例如,数据库中的用户表可能如下:
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(255),
password_last_changed TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 其他用户信息字段
);
2. 前端获取是否需要更改密码的判断
在用户登录时,后端会检查密码的最后修改时间,如果距离当前时间超过了 90 天,后端会返回一个标志,提示前端要求用户更改密码。后端还可以在每次用户登录时进行检查。
2.1接口设计:/api/login
或 /api/check-password-status
2.1.1 请求:
POST /api/login
{
"username": "user123",
"password": "password123"
}
2.1.2 响应:
如果用户成功登录,但密码已经过期,后端返回如下信息:
{
"status": "success",
"password_change_required": true,
"message": "Your password has expired and needs to be changed."
}
如果密码未过期:
{
"status": "success",
"password_change_required": false,
"message": "Login successful."
}
2.1.3 逻辑:
- 后端根据登录请求进行身份验证。
- 检查
password_last_changed
字段,计算距离当前时间的天数。 - 如果超过 90 天,返回
password_change_required: true
,并提示用户修改密码。
3. 密码更改接口设计
如果用户的密码已过期,前端会引导用户到密码更改页面。后端需要一个 API 接口来处理密码更新请求。
3.1 接口设计:/api/change-password
3.1.1 请求:
POST /api/change-password
{
"userId": 123,
"old_password": "oldPassword123",
"new_password": "newPassword456"
}
3.1.2 响应:
{
"status": "success",
"message": "Password changed successfully."
}
3.1.3 逻辑:
- 验证用户的身份和旧密码是否正确。
- 检查新密码是否满足密码复杂性要求(如长度、特殊字符等)。
- 如果验证通过,更新用户的密码,将
password_last_changed
字段更新为当前时间。 - 如果密码更新成功,返回成功状态。
3.1.4 密码更新过程中的注意事项:
- 密码在传输过程中应进行加密(通过 HTTPS)。
- 后端应对新密码进行哈希处理(如使用
bcrypt
),然后存储。 - 防止弱密码、与旧密码重复等。
4. 额外的安全措施
- 邮件提醒:在密码即将过期时(如过期前7天),可以发送邮件提醒用户更新密码。
- 失败登录锁定:如果用户尝试用旧密码登录超过一定次数(例如5次),可以暂时锁定账户,防止暴力破解。
- 多因素认证 (MFA):为敏感操作(如更改密码)增加多因素验证(如短信或邮件验证码)。
5. 接口的整体流程
-
登录请求:
- 用户提交用户名和密码。
- 后端验证用户名和密码是否匹配,并检查密码的最后修改时间。
- 如果密码超过 90 天没有修改,返回
password_change_required
为true
。
-
修改密码请求:
- 用户在前端收到需要更改密码的提示后,输入旧密码和新密码。
- 后端验证旧密码,并将新密码保存到数据库,同时更新密码的修改时间。
通过这样的设计,后端能够有效管理密码的过期时间,并通过API接口与前端进行信息交换,确保用户在90天内及时更新密码,保障系统安全。
6.代码实战
下面是基于 Python(使用 Flask 框架)实现的后端代码示例,分别展示前后端如何处理“每 90 天更改一次密码”的逻辑。使用 Flask 框架作为后端,SQLAlchemy 作为 ORM。
6.1 后端:Flask + SQLAlchemy
6.1.1 数据库模型
我们首先定义用户模型 User
,包括存储用户的密码哈希和密码最后修改的时间字段。
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
db = SQLAlchemy()
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
password_last_changed = db.Column(db.DateTime, default=datetime.utcnow)
def set_password(self, password):
"""哈希处理并存储密码"""
self.password_hash = generate_password_hash(password)
self.password_last_changed = datetime.utcnow()
def check_password(self, password):
"""验证密码"""
return check_password_hash(self.password_hash, password)
6.1.2 登录逻辑:检查密码是否需要更新
后端检查用户登录时,判断密码是否需要更新,并返回相应的标志给前端。
from flask import Flask, request, jsonify
from datetime import datetime, timedelta
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
db.init_app(app)
PASSWORD_EXPIRY_DAYS = 90 # 密码过期时间为90天
@app.route('/api/login', methods=['POST'])
def login():
data = request.json
username = data.get('username')
password = data.get('password')
# 查找用户
user = User.query.filter_by(username=username).first()
if not user or not user.check_password(password):
return jsonify({"status": "error", "message": "Invalid credentials"}), 401
# 检查密码是否超过90天
password_age = (datetime.utcnow() - user.password_last_changed).days
password_change_required = password_age > PASSWORD_EXPIRY_DAYS
return jsonify({
"status": "success",
"password_change_required": password_change_required,
"message": "Login successful" if not password_change_required else "Password expired, please change your password."
})
6.1.3 密码更改接口
当前端收到密码过期的提示时,用户输入新密码,后端处理密码更新的逻辑。
@app.route('/api/change-password', methods=['POST'])
def change_password():
data = request.json
user_id = data.get('userId')
old_password = data.get('old_password')
new_password = data.get('new_password')
# 查找用户
user = User.query.get(user_id)
if not user or not user.check_password(old_password):
return jsonify({"status": "error", "message": "Old password is incorrect"}), 401
# 检查新密码是否符合规则,可以加入密码复杂性校验逻辑
if len(new_password) < 8:
return jsonify({"status": "error", "message": "New password must be at least 8 characters long"}), 400
# 更新密码和最后修改时间
user.set_password(new_password)
db.session.commit()
return jsonify({"status": "success", "message": "Password changed successfully"})
6.1.4 辅助函数
这些辅助函数用于密码的设置和校验。我们已经在模型中通过 set_password
和 check_password
进行了密码的哈希处理和校验。
from werkzeug.security import generate_password_hash, check_password_hash
# 生成哈希密码
def generate_hash(password):
return generate_password_hash(password)
# 校验哈希密码
def verify_password(stored_password_hash, password):
return check_password_hash(stored_password_hash, password)
6.2 前端(假设使用 HTML + JavaScript)
6.2.1 登录页面逻辑
当用户登录时,前端会检查返回的 password_change_required
字段,决定是否提示用户更改密码。
async function login() {
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.status === "success") {
if (data.password_change_required) {
alert("Your password has expired. Please change your password.");
// 跳转到更改密码页面
window.location.href = "/change-password.html";
} else {
alert("Login successful");
// 跳转到主页
window.location.href = "/home.html";
}
} else {
alert(data.message);
}
}
6.2.2 更改密码页面逻辑
用户进入更改密码页面后,前端会调用后端的 /api/change-password
接口来更新密码。
async function changePassword() {
const userId = localStorage.getItem('userId'); // 假设用户ID存储在 localStorage 中
const oldPassword = document.getElementById("old_password").value;
const newPassword = document.getElementById("new_password").value;
const response = await fetch('/api/change-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: userId,
old_password: oldPassword,
new_password: newPassword
})
});
const data = await response.json();
if (data.status === "success") {
alert("Password changed successfully");
// 跳转到登录页面
window.location.href = "/login.html";
} else {
alert(data.message);
}
}
6.3 逻辑总结
- 用户登录时,后端会检查密码是否过期(90 天),并通过
password_change_required
标志告知前端。 - 如果密码过期,前端会跳转到密码更改页面,用户输入旧密码和新密码,后端验证并更新密码及
password_last_changed
字段。 - 整个流程中确保密码哈希存储、前后端通讯的安全性(如使用 HTTPS)。