Lesson Completion
Back to course

Security Best Practices: Writing Secure Java Code

Beginner
12 minutes4.6Java

1. The Hook (The "Byte-Sized" Intro)

  • In a Nutshell: Security = never trust user input! SQL injection: Use PreparedStatement (not concatenation).
  • XSS: Sanitize outputs, escape HTML.
  • Input validation: Whitelist > blacklist.
  • Authentication: Hash passwords (BCrypt, not MD5!), use tokens (JWT).
  • Sensitive data: Never log passwords, use HTTPS, encrypt at rest.
  • Dependencies: Update regularly, scan for vulnerabilities (OWASP).
  • CSRF: Use tokens for state-changing requests.
  • Golden rule: Assume all input is malicious. Security is not optional!

Think of home security. Input validation = check ID before entry. SQL injection = guest secretly brings in burglar. XSS = poisoned food. Password hashing = safe combination (not sticky note). HTTPS = armored car vs regular car for valuables!


2. Conceptual Clarity (The "Simple" Tier)

💡 The Analogy

  • SQL Injection: Trojan horse (hidden attack)
  • XSS: Poisoned gift (looks safe, isn't)
  • Password Hashing: Shredded document (irreversible)
  • Input Validation: Airport security (check everything)

3. Technical Mastery (The "Deep Dive")

java
// =========================================== // 1. SQL INJECTION PREVENTION // =========================================== // ❌ CRITICAL VULNERABILITY: SQL Injection String userId = request.getParameter("id"); String sql = "SELECT * FROM users WHERE id = '" + userId + "'"; // If userId = "1' OR '1'='1", returns ALL users! // If userId = "1'; DROP TABLE users;--", DELETES TABLE! Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); // ❌ NEVER DO THIS! // ✅ SAFE: Prepared Statement (parameterized query) String sql = "SELECT * FROM users WHERE id = ?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setInt(1, Integer.parseInt(userId)); // ✅ Safely escapes input ResultSet rs = stmt.executeQuery(); // ORM (JPA) is also safe User user = entityManager.find(User.class, userId); // ✅ Safe // =========================================== // 2. XSS (Cross-Site Scripting) PREVENTION // =========================================== // ❌ VULNERABILITY: Reflecting user input String comment = request.getParameter("comment"); response.getWriter().write("<div>" + comment + "</div>"); // If comment = "<script>alert('XSS')</script>", executes JavaScript! // ✅ SAFE: Escape HTML import org.apache.commons.text.StringEscapeUtils; String comment = request.getParameter("comment"); String safeComment = StringEscapeUtils.escapeHtml4(comment); response.getWriter().write("<div>" + safeComment + "</div>"); // "<script>" becomes "&lt;script&gt;" (displayed, not executed) // ✅ SAFE: Use templating engines (Thymeleaf, JSP with JSTL) // Thymeleaf auto-escapes by default // <div th:text="${comment}"></div> <!-- Auto-escaped! --> // =========================================== // 3. INPUT VALIDATION // =========================================== // ❌ BAD: Blacklist (incomplete) if (input.contains("script") || input.contains("SQL")) { // Block } // Bypass: "scr<script>ipt", "Sql", etc. // ✅ GOOD: Whitelist (explicit allowed values) Pattern emailPattern = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"); if (!emailPattern.matcher(email).matches()) { throw new ValidationException("Invalid email"); } // ✅ GOOD: Use validation libraries import javax.validation.constraints.*; class User { @Email @NotEmpty private String email; @Min(0) @Max(150) private int age; } // =========================================== // 4. PASSWORD SECURITY // =========================================== // ❌ CRITICAL: Storing plaintext passwords String password = "secret123"; database.save(user.getId(), password); // ❌ NEVER! // ❌ CRITICAL: Weak hashing (MD5, SHA-1) String hash = MessageDigest.getInstance("MD5") .digest(password.getBytes()); // ❌ Easily cracked! // ✅ SAFE: BCrypt (slow hash, with salt) import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String hashedPassword = encoder.encode("secret123"); database.save(user.getId(), hashedPassword); // Verify password boolean matches = encoder.matches("secret123", hashedPassword); // ✅ SAFE: Argon2 (winner of Password Hashing Competition) import org.bouncycastle.crypto.generators.Argon2BytesGenerator; // =========================================== // 5. SENSITIVE DATA HANDLING // =========================================== // ❌ BAD: Logging sensitive data logger.info("User login: " + username + ", password: " + password); // ❌ NEVER! logger.info("Credit card: " + creditCard); // ❌ NEVER! // ✅ GOOD: Don't log sensitive data logger.info("User login: " + username); // ✅ No password! // ✅ GOOD: Mask sensitive data String masked = creditCard.substring(0, 4) + "********" + creditCard.substring(creditCard.length() - 4); logger.info("Payment with card: " + masked); // 1234********5678 // =========================================== // 6. AUTHENTICATION & AUTHORIZATION // =========================================== // ✅ Token-based auth (JWT) import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; String token = Jwts.builder() .setSubject(user.getId()) .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); // Verify token Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); // ✅ Role-based access control @PreAuthorize("hasRole('ADMIN')") public void deleteUser(int userId) { // Only admins can execute } // =========================================== // 7. DEPENDENCY VULNERABILITIES // =========================================== // ✅ Scan dependencies (Maven) // pom.xml <plugin> <groupId>org.owasp</groupId> <artifactId>dependency-check-maven</artifactId> <version>8.0.0</version> </plugin> // Run: mvn dependency-check:check // ✅ Keep dependencies updated // Check: mvn versions:display-dependency-updates // =========================================== // 8. CSRF PREVENTION // =========================================== // ❌ VULNERABLE: No CSRF protection @PostMapping("/transfer") public void transfer(@RequestParam int amount) { // ❌ Attacker can trigger this via hidden form! } // ✅ SAFE: CSRF token (Spring Security) // Automatically includes CSRF token in forms <form method="POST" action="/transfer"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <input type="number" name="amount"/> <button type="submit">Transfer</button> </form>

5. The Comparison & Decision Layer

VulnerabilityAttackPrevention
SQL InjectionMalicious SQLPreparedStatement
XSSMalicious JavaScriptEscape HTML output
Weak PasswordsBrute forceBCrypt/Argon2
CSRFForged requestsCSRF tokens

6. The "Interview Corner" (The Edge)

The "Killer" Interview Question: "Explain SQL injection and how to prevent it." Answer: SQL injection = attacker injects malicious SQL via user input!

Vulnerable code:

java
String sql = "SELECT * FROM users WHERE name = '" + userInput + "'"; // If userInput = "admin' OR '1'='1", becomes: // SELECT * FROM users WHERE name = 'admin' OR '1'='1' // Returns ALL users (bypasses authentication)!

Prevention:

java
// Use PreparedStatement (parameterized queries) String sql = "SELECT * FROM users WHERE name = ?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, userInput); // ✅ Safely escaped!

Pro-Tips (OWASP Top 10):

  1. Input validation:
java
// Validate everything! @NotNull @Email private String email; @Min(0) @Max(150) private int age; // Whitelist allowed characters if (!input.matches("[a-zA-Z0-9]+")) { throw new ValidationException(); }
  1. Security headers (Spring Security):
java
http.headers() .contentSecurityPolicy("default-src 'self'") .xssProtection() .frameOptions().deny();

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01