CREATE TABLE userInfo (
userId INT PRIMARY KEY AUTO_INCREMENT,
userName VARCHAR(50) NOT NULL UNIQUE,
userPwdz VARCHAR(255) NOT NULL, -- 建議儲存 BCrypt 雜湊而非明文
email VARCHAR(100)
);
<%@ page contentType="text/html;charset=UTF-8" %>
<form id="pwdForm" action="ChangePasswordServlet" method="post">
<input type="hidden" name="userId" value="${sessionScope.user.userId}" />
<div>
<label>舊密碼:</label>
<input type="password" name="oldPwd" required />
</div>
<div>
<label>新密碼:</label>
<input type="password" name="newPwd" required minlength="6" />
</div>
<div>
<label>確認新密碼:</label>
<input type="password" name="confirmPwd" required />
</div>
<button type="submit">確認修改</button>
<button type="button" onclick="closeModal()">取消</button>
</form>
<script>
// 簡易前端驗證
document.getElementById('pwdForm').addEventListener('submit', function(e) {
const np = this.newPwd.value, cp = this.confirmPwd.value;
if (np !== cp) { alert('兩次新密碼不一致'); e.preventDefault(); }
});
</script>
@WebServlet("/ChangePasswordServlet")
public class ChangePasswordServlet extends HttpServlet {
// 資料庫連線池建議使用 HikariCP / Druid,此處簡化為單一連線
private static final String DB_URL = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
private static final String DB_USER = "root";
private static final String DB_PASS = "password";
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String userId = req.getParameter("userId");
String oldPwd = req.getParameter("oldPwd");
String newPwd = req.getParameter("newPwd");
String confirmPwd = req.getParameter("confirmPwd");
// 1. 基本驗證
if (userId == null || !newPwd.equals(confirmPwd)) {
sendError(resp, "參數錯誤或兩次新密碼不一致");
return;
}
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS)) {
// 2. 驗證舊密碼 (查詢雜湊值比對)
String checkSql = "SELECT userPwdz FROM userInfo WHERE userId = ?";
try (PreparedStatement ps = conn.prepareStatement(checkSql)) {
ps.setInt(1, Integer.parseInt(userId));
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) { sendError(resp, "使用者不存在"); return; }
String storedHash = rs.getString("userPwdz");
// 此處假設使用 BCrypt,實際專案請引入 jbcrypt 或 Spring Security Crypto
if (!BCrypt.checkpw(oldPwd, storedHash)) {
sendError(resp, "舊密碼錯誤"); return;
}
}
}
// 3. 更新新密碼 (參數化 UPDATE,無星號、無拼接)
String updateSql = "UPDATE userInfo SET userPwdz = ? WHERE userId = ?";
try (PreparedStatement ps = conn.prepareStatement(updateSql)) {
String newHash = BCrypt.hashpw(newPwd, BCrypt.gensalt(12)); // 加鹽雜湊
ps.setString(1, newHash);
ps.setInt(2, Integer.parseInt(userId));
int rows = ps.executeUpdate();
if (rows > 0) {
resp.getWriter().write("密碼修改成功");
} else {
sendError(resp, "更新失敗,請稍後再試");
}
}
} catch (SQLException | NumberFormatException e) {
e.printStackTrace();
sendError(resp, "系統錯誤: " + e.getMessage());
}
}
private void sendError(HttpServletResponse resp, String msg) throws IOException {
resp.setContentType("text/plain;charset=UTF-8");
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
resp.getWriter().write(msg);
}
}
| 錯誤寫法 | 正確寫法 | 說明 |
|---|---|---|
| `UPDATE userInfo SET userPwdz = '" + newPwd + "'` | `UPDATE userInfo SET userPwdz = ?` | 字串拼接導致 SQL Injection |
| `ps.setString(1, "*" + newPwd + "*")` | `ps.setString(1, newHash)` | 星號非 SQL 語法,參數直接傳值 |
| 直接比對明文 `oldPwd == rs.getString("pwd")` | `BCrypt.checkpw(oldPwd, storedHash)` | 明文比對極度不安全 |
JSP 修改資料庫密碼的標準流程:前端表單驗證 → Servlet 接收參數 → `PreparedStatement` 參數化查詢驗證舊密碼 → 參數化 UPDATE 寫入新密碼雜湊值。核心原則:**參數化查詢 + 密碼雜湊儲存 + HTTPS 傳輸**。