JDBC PreparedStatement - SQL Injection වලින් බේරෙමු! | SC Guide

කොහොමද යාලුවනේ! අද අපි කතා කරන්න යන්නේ Java Programming වලදී Database එක්ක වැඩ කරනකොට නැතුවම බැරි, ගොඩක් වැදගත් Concepts එකක් ගැන. ඒ තමයි PreparedStatement.
ගොඩක් අය Database Queries දානකොට සාමාන්යයෙන් Statement
object එක පාවිච්චි කරනවා. හැබැයි Statement
එකට වඩා PreparedStatement
එක පාවිච්චි කරන එකට ගොඩක් හේතු තියෙනවා. ඒ වගේම ඒක ගොඩක් ආරක්ෂාකාරී වගේම වේගවත් ක්රමයක්.
විශේෂයෙන්ම, අපි SQL Injection වගේ Attacks වලින් අපේ Application එක බේරගන්න නම් අනිවාර්යයෙන්ම PreparedStatement
පාවිච්චි කරන්න ඕනේ. මොකක්ද මේ SQL Injection කියන්නේ? ඒකෙන් බේරෙන්නේ කොහොමද? PreparedStatement
එක වැඩ කරන්නේ කොහොමද? අපි මේ හැමදේම අද මේ Post එකෙන් ඉගෙන ගනිමු.
SQL Injection කියන්නේ මොකද්ද?
හරි, මුලින්ම බලමු මොකක්ද මේ SQL Injection කියන්නේ කියලා. සරලවම කිව්වොත්, ඒක තමයි Hackers ලා අපේ Application එකට Database Queries වලට හානි කරන්න පුළුවන් Code (malicious code) දාන විදිහ. ඒකෙන් පුළුවන් අපේ Database එකේ තියෙන දත්ත (data) ගන්න, මකන්න, නැත්නම් වෙනස් කරන්න. සමහරවිට මුළු Database එකටම හානි කරන්නත් පුළුවන්.
අපි හිතමු ඔයාලා Login Page එකක් හදනවා කියලා. ඒකේ Username එකයි Password එකයි අරගෙන Database එකේ තියෙන Details එක්ක Compare කරලා Login කරන්න දෙනවා. සාමාන්යයෙන් ඔයාලා SQL Query එකක් හදන්නේ මෙහෙම වෙන්න පුළුවන්:
String query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
හරි, මේකෙන් අපේ username
variable එකට එන Input එකයි password
variable එකට එන Input එකයි කෙලින්ම SQL Query එකට එකතු වෙනවා (concatenate වෙනවා). ඔතන තමයි අවුල පටන් ගන්නේ.
දැන් හිතන්න Hacker කෙනෙක් Username එක විදිහට admin' OR '1'='1' --
කියලා දුන්නොත් මොකද වෙන්නේ කියලා? එතකොට අපේ Query එක මෙහෙම වෙනවා:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' --' AND password = 'any_password'
බලන්න, --
කියන්නේ SQL වල Comment එකක්. ඒ කියන්නේ ඒකෙන් පස්සේ තියෙන දේවල් Run වෙන්නේ නැහැ. එතකොට මේ Query එකේ username = 'admin'
කියන එක FALSE
උනත්, '1'='1'
කියන එක හැමතිස්සෙම TRUE
නිසා, Query එක හැමතිස්සෙම TRUE
වෙනවා. එතකොට Hackerට කිසිම Valid Username, Password එකක් නැතුව Login වෙන්න පුළුවන් වෙනවා! අන්න ඒක තමයි SQL Injection කියන්නේ. භයානකයි නේද?
Statement එකේ අවුල
අපි දැක්කා Statement
object එක පාවිච්චි කරනකොට SQL Injection වලට කොච්චර Vulnerable ද කියලා. Statement
object එක හැමතිස්සෙම User Input එක SQL Query එකේ කොටසක් විදිහට සලකනවා. ඒ කියන්නේ, අපිට ඕන නම් Query එකට මොනව හරි Malicious Code එකක් දාලා Run කරන්න පුළුවන්. ඒක හරියට, ඔයාලා ලිපියක් ලියන්න යනකොට කාටහරි හිතුමතේට වචන දාන්න දෙනවා වගේ වැඩක්. හරිම අවදානම්.
Statement
එකෙන් SQL Queries Run කරන සරල Example එකක් බලමු.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class StatementRiskExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/test_db";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
// Vulnerable user input
String userNameInput = "john_doe'; DROP TABLE users; --"; // Malicious Input!
// Or for login: "admin' OR '1'='1' --"
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
Statement stmt = conn.createStatement()) {
String sqlQuery = "SELECT * FROM users WHERE username = '" + userNameInput + "'";
System.out.println("Executing SQL (Vulnerable): " + sqlQuery);
ResultSet rs = stmt.executeQuery(sqlQuery);
if (rs.next()) {
System.out.println("User found: " + rs.getString("username") + ", Email: " + rs.getString("email"));
} else {
System.out.println("No user found or malicious query executed.");
}
} catch (SQLException e) {
System.err.println("Database error: " + e.getMessage());
e.printStackTrace();
}
}
}
මේ Example එකේදී userNameInput
කියන Variable එකට Malicious SQL Code එකක් දීලා තියෙනවා. Statement
එකෙන් මේ String එක කෙලින්ම Query එකට එකතු කරන නිසා, DROP TABLE users;
කියන විනාශකාරී Command එක Execute වෙන්න පුළුවන්. ඒකෙන් මුළු users
Table එකම Delete වෙලා යන්න පුළුවන්. හිතන්නකෝ ඔයාලගේ Customer Details තියෙන Table එකක් මෙහෙම Delete උනොත් වෙන හානිය!
PreparedStatement ගේ Solid Solution එක
අන්න ඕකට තමයි අපේ PreparedStatement කියන වීරයා එන්නේ! PreparedStatement
එක තමයි SQL Injection වලින් ආරක්ෂා වෙන්න තියෙන හොඳම විසඳුම. ඒක වැඩ කරන්නේ කොහොමද?
සරලවම කිව්වොත්, PreparedStatement
එකක් කියන්නේ SQL Query එකක් Database එකට යැවීමට කලින්, ඒක Pre-compile කරලා Ready කරන ක්රමයක්. මේකේදී අපි User Input දාන්න ඕන තැන් වලට ?
(question mark) කියලා Placeholder එකක් දානවා. පස්සේ අපි මේ ?
වලට අදාළ Values දානවා.
උදාහරණයක් විදිහට, කලින් අපි දැක්ක Login Query එක PreparedStatement
එකකින් හැදුවොත් මෙහෙම වෙනවා:
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = con.prepareStatement(query);
pstmt.setString(1, username); // User input for username
pstmt.setString(2, password); // User input for password
ResultSet rs = pstmt.executeQuery();
දැන් බලන්න, username
සහ password
කියන ඒවා කෙලින්ම Query String එකට එකතු වෙන්නේ නැහැ. PreparedStatement
එකේ setString()
වගේ Methods වලින් අපි දෙන Input, Database එක විසින් Data එකක් විදිහටම සලකනවා මිසක්, SQL Code එකක් විදිහට සලකන්නේ නැහැ. ඒ නිසා, Hacker කෙනෙක් ' OR '1'='1' --
වගේ දෙයක් Username එකට දුන්නත්, ඒක සාමාන්ය String එකක් විදිහටම යනවා මිසක්, Query එක වෙනස් කරන්නේ නැහැ. අන්න ඒක තමයි PreparedStatement
එකේ තියෙන සුපිරි ආරක්ෂාව!
PreparedStatement පාවිච්චි කිරීමේ වාසි:
- SQL Injection Prevention: මේක තමයි ප්රධානම වාසිය. User Input එක Data විදිහට හඳුනගන්නා නිසා, Malicious Code Run වෙන්නේ නැහැ.
- Performance Improvement:
PreparedStatement
එකක් පාවිච්චි කරනකොට, Database එකට Query එක එක පාරක් Pre-compile කරන්න අවස්ථාව ලැබෙනවා. ඒ කියන්නේ, අපි එකම Query එකට වෙනස් Parameters දීලා ගොඩක් පාරක් Run කරනවා නම්, Database එකට හැමතිස්සෙම Query එක Parse කරන්න, Optimize කරන්න ඕනේ නැහැ. ඒකෙන් Performance එක වැඩි වෙනවා. (විශේෂයෙන් Batch Operations වලදී මේක ගොඩක් ප්රයෝජනවත්). - Readability and Maintainability: Queries වලට Data දාන්න String Concatenation පාවිච්චි කරනවට වඩා
setX()
Methods පාවිච්චි කරන එක කියවන්න පහසුයි, වරදින්න තියෙන ඉඩ අඩුයි.
PreparedStatement පාවිච්චි කරමු! (Practical Examples)
හරි, දැන් අපි බලමු කොහොමද PreparedStatement
එක අපි Data Insert කරන්න, Update කරන්න සහ Select කරන්න පාවිච්චි කරන්නේ කියලා. හැම වෙලාවෙම මතක තියාගන්න, JDBC Resources (Connection
, PreparedStatement
, ResultSet
) හැමතිස්සෙම Close කරන්න ඕනේ. ඒකට හොඳම ක්රමය තමයි Java 7 වල ආපු Try-with-resources Statement එක පාවිච්චි කරන එක.
පහල තියෙන Code Examples වලට කලින්, ඔයාලට MySQL Database එකක් (හෝ වෙනත් ඕනෑම Database එකක්) එක්ක test_db
කියලා Database එකක් සහ users
, products
, customers
වගේ Tables හදාගන්න පුළුවන්.
1. Data Insert කිරීමට (Inserting Data)
අපි හිතමු users
කියලා Table එකකට අලුත් User කෙනෙක් Insert කරන්න ඕනේ කියලා. Table එකේ Columns තියෙන්නේ id
, username
, email
, password
කියලා.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class InsertExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/test_db";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
String username = "tharindu";
String email = "[email protected]";
String password = "tharindu_pass_123";
String insertSQL = "INSERT INTO users (username, email, password) VALUES (?, ?, ?)";
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
pstmt.setString(1, username); // 1st '?' is for username
pstmt.setString(2, email); // 2nd '?' is for email
pstmt.setString(3, password); // 3rd '?' is for password
int rowsAffected = pstmt.executeUpdate();
if (rowsAffected > 0) {
System.out.println("User '" + username + "' inserted successfully!");
} else {
System.out.println("Failed to insert user.");
}
} catch (SQLException e) {
System.err.println("Database error: " + e.getMessage());
e.printStackTrace();
}
}
}
මේකේදී setString(index, value)
කියලා Method එකක් පාවිච්චි කරලා තියෙනවා. index
කියන්නේ Query එකේ තියෙන ?
වල අංකය (1 ඉඳන් පටන් ගන්නේ). value
කියන්නේ ඒ ?
එකට දාන්න ඕන Data එක. executeUpdate()
Method එක පාවිච්චි කරන්නේ INSERT
, UPDATE
, DELETE
වගේ Queries Run කරන්න.
2. Data Update කිරීමට (Updating Data)
දැන් අපි බලමු products
කියලා Table එකක Product එකක Price එක Update කරන්නේ කොහොමද කියලා. Table එකේ Columns තියෙන්නේ id
, name
, price
කියලා.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class UpdateExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/test_db";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
int productId = 101;
double newPrice = 1500.75;
String updateSQL = "UPDATE products SET price = ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(updateSQL)) {
pstmt.setDouble(1, newPrice); // 1st '?' for price
pstmt.setInt(2, productId); // 2nd '?' for id
int rowsAffected = pstmt.executeUpdate();
if (rowsAffected > 0) {
System.out.println("Product ID " + productId + " updated with new price: " + newPrice);
} else {
System.out.println("No product found with ID " + productId + " or no update needed.");
}
} catch (SQLException e) {
System.err.println("Database error: " + e.getMessage());
e.printStackTrace();
}
}
}
මෙහිදී අපි setDouble()
සහ setInt()
වගේ Methods පාවිච්චි කරලා තියෙනවා. PreparedStatement
එකේ setString()
, setInt()
, setDouble()
, setBoolean()
, setDate()
, setTimestamp()
වගේ දත්ත වර්ග වලට අදාළ Methods ගොඩක් තියෙනවා. අපේ Data Type එකට ගැලපෙන Method එක පාවිච්චි කරන එක ගොඩක් වැදගත්.
3. Data Select කිරීමට (Selecting Data)
දැන් අපි බලමු customers
කියලා Table එකක කොළඹ ඉන්න, වයස 30ට වැඩි Customers ලා Select කරන්නේ කොහොමද කියලා. Table එකේ Columns තියෙන්නේ id
, name
, email
, city
, age
කියලා.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class SelectExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/test_db";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
String city = "Colombo";
int minAge = 30;
String selectSQL = "SELECT * FROM customers WHERE city = ? AND age > ?";
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(selectSQL)) {
pstmt.setString(1, city); // 1st '?' for city
pstmt.setInt(2, minAge); // 2nd '?' for age
ResultSet rs = pstmt.executeQuery(); // For SELECT queries
System.out.println("Customers in " + city + " older than " + minAge + ":");
while (rs.next()) {
System.out.println(" Name: " + rs.getString("name") +
", Email: " + rs.getString("email") +
", Age: " + rs.getInt("age"));
}
} catch (SQLException e) {
System.err.println("Database error: " + e.getMessage());
e.printStackTrace();
}
}
}
SELECT
Queries වලට අපි executeQuery()
Method එක පාවිච්චි කරනවා. ඒකෙන් අපිට ResultSet
object එකක් ලැබෙනවා. ඒකෙන් අපිට Results ටික Iterate කරලා ගන්න පුළුවන්.
Performance වාසි තවත් පැහැදිලිව
අපි කලින් කතා කරපු Performance වාසිය තවත් පොඩ්ඩක් පැහැදිලි කරගමු. සාමාන්ය Statement
එකක් පාවිච්චි කරනකොට, හැම Query එකක්ම Database එකට යවනකොට Database එකට ඒක නැවත Parse කරන්න, Compile කරන්න, Optimise කරන්න සිද්ධ වෙනවා. මේ හැම දේටම Process Time එකක් යනවා.
හැබැයි PreparedStatement
එකකදී, අපි Query එක Database එකට යවන්නේ එක පාරයි. Database එක ඒක Pre-compile කරලා Execution Plan එකක් හදාගන්නවා. ඊට පස්සේ අපි ඒ PreparedStatement එකට වෙනස් Parameters දීලා Run කරන හැම පාරකම, Database එකට කලින්ම හදපු Execution Plan එක පාවිච්චි කරන්න පුළුවන්. ඒකෙන් Process Time එක ගොඩක් අඩු වෙනවා. Batch Operations (එක වතාවට Data ගොඩක් Insert කරන, Update කරන) වගේ දේවල් වලදී මේ Performance Advantage එක ගොඩක් වැදගත්.
පොඩි Tip එකක් - Always Close Resources!
මතක තියාගන්න, ඔයාලා JDBC පාවිච්චි කරනකොට, Connection
, Statement
, PreparedStatement
, ResultSet
වගේ Objects පාවිච්චි කරලා ඉවර වුණාම අනිවාර්යයෙන්ම ඒවාවල close()
Method එක Call කරන්න ඕනේ. එහෙම නොකළොත් Database Resources ටික Free වෙන්නේ නැහැ. ඒකෙන් Memory Leaks වෙන්න පුළුවන්, Database එකේ Performance එක අඩු වෙන්න පුළුවන්. Try-with-resources පාවිච්චි කරන එක තමයි හොඳම පුරුද්ද. ඒකෙන් අපිට වෙනම finally
Block එකක close()
කරන්න ඕනේ නැහැ, Java Runtime එකෙන් ඒක බලාගන්නවා.
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(someSQL);
ResultSet rs = pstmt.executeQuery()) {
// Your database logic here
} catch (SQLException e) {
System.err.println("Database error: " + e.getMessage());
e.printStackTrace();
}
අවසාන වශයෙන්...
ඉතින් යාලුවනේ, ඔයාලා දැක්කා නේද PreparedStatement
එකේ තියෙන වැදගත්කම? SQL Injection වගේ භයානක Attacks වලින් අපේ Application එක ආරක්ෂා කරගන්න, වගේම අපේ Database Operations වල Performance එක වැඩි කරගන්න PreparedStatement
එක නැතුවම බැරි දෙයක්.
හැබැයි හැමවෙලේම මතක තියාගන්න, හොඳ Coding Practices පාවිච්චි කරන එක හරිම වැදගත්. Security කියන්නේ එක දෙයක් විතරක් නෙවෙයි, ඒක මුළු Application එක පුරාම අවධානය යොමු කරන්න ඕනේ දෙයක්. PreparedStatement
කියන්නේ ඒකට තියෙන ප්රබලම මෙවලමක්.
අදට එහෙනම් මෙච්චරයි. මේ Post එක ඔයාලට වැදගත් වෙන්න ඇති කියලා හිතනවා. මොනවා හරි ප්රශ්න තියෙනවා නම්, පහලින් Comment කරන්න. ඔයාලගේ අත්දැකීම් ගැන කියන්නත් පුළුවන්. තවත් මෙවැනිම වටිනා Post එකකින් හමුවෙමු! Happy Coding!