Spring dan MongoDB USER LOGIN

Jika sebelumnya kita membuat artikel Spring Boot MongoDB - Simple App Tutorial, sekarang kita bangun USER LOGIN dari nol, berangkat dari collection users di MongoDB dan Spring Boot, rapi, aman, dan clean-code minded.

TARGET AKHIR

  • Collection: users

  • Fitur:

    • Register user

    • Login user

    • Password di-hash (BCrypt)

  • Tanpa Spring Security dulu (biar alur logika login mudah dipahami)

STEP 1 — Struktur Collection users (MongoDB)

Di MongoDB, user kita akan berbentuk seperti ini:

{
  "_id": ObjectId("..."),
  "name": "Yusuf",
  "email": "yusuf@gmail.com",
  "passwordHash": "$2a$10$..."
}

Struktur dari collection users


Isi dari collection users

Prinsip penting:

  • Tidak ada password plain

  • Tidak ada token di DB

  • Email unique

  • Password = hash

STEP 2 — User Entity (Domain Layer)

domain/User.java

Gunakan record supaya ringkas & immutable.

package com.yusuf.springmongoclean.domain;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "users")
public record User(
@Id String id,
String name,
String email,
String passwordHash
) {}

Kenapa ini clean:

  • Tidak ada setter

  • Data tidak bisa “diubah diam-diam”

  • Cocok untuk auth data

STEP 3 — User Repository

repository/UserRepository.java

package com.yusuf.springmongoclean.repository;

import com.yusuf.springmongoclean.domain.User;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.Optional;

public interface UserRepository extends MongoRepository<User, String> {

Optional<User> findByEmail(String email);
}

findByEmail = jantung login.

STEP 4 — DTO (Request Object)

Register Request

package com.yusuf.springmongoclean.dto.security;

public record RegisterRequest(
String name,
String email,
String password
) {}

Login Request

package com.yusuf.springmongoclean.dto.security;

public record LoginRequest(
String email,
String password
) {}

STEP 5 — Password Encoder (BCrypt)

Tambahkan dependency (jika belum):

implementation 'org.springframework.security:spring-security-crypto'

Ini bukan full Spring Security, cuma util crypto.

STEP 6 — Auth Service (INTI LOGIN)

service/AuthService.java

package com.yusuf.springmongoclean.service;

import com.yusuf.springmongoclean.domain.User;
import com.yusuf.springmongoclean.repository.UserRepository;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class AuthService {

private final UserRepository repository;
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

public AuthService(UserRepository repository) {
this.repository = repository;
}

// REGISTER
public User register(String name, String email, String password) {
repository.findByEmail(email)
.ifPresent(user -> {
throw new IllegalArgumentException("Email already registered");
});

String passwordHash = encoder.encode(password);

return repository.save(
new User(null, name, email, passwordHash)
);
}

// LOGIN
public User login(String email, String password) {
User user = repository.findByEmail(email)
.orElseThrow(() ->
new IllegalArgumentException("Invalid email or password")
);

if (!encoder.matches(password, user.passwordHash())) {
throw new IllegalArgumentException("Invalid email or password");
}

return user;
}
}
Kenapa error message sama?
-> Supaya attacker tidak tahu email mana yang valid.


STEP 7 — Auth Controller

controller/AuthController.java

package com.yusuf.springmongoclean.service;

import com.yusuf.springmongoclean.domain.User;
import com.yusuf.springmongoclean.repository.UserRepository;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class AuthService {

private final UserRepository repository;
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

public AuthService(UserRepository repository) {
this.repository = repository;
}

// REGISTER
public User register(String name, String email, String password) {
repository.findByEmail(email)
.ifPresent(user -> {
throw new IllegalArgumentException("Email already registered");
});

String passwordHash = encoder.encode(password);

return repository.save(
new User(null, name, email, passwordHash)
);
}

// LOGIN
public User login(String email, String password) {
User user = repository.findByEmail(email)
.orElseThrow(() ->
new IllegalArgumentException("Invalid email or password")
);

if (!encoder.matches(password, user.passwordHash())) {
throw new IllegalArgumentException("Invalid email or password");
}

return user;
}
}

STEP 8 — Test API

Register

postman request POST 'http://localhost:8080/auth/register' \
  --header 'Content-Type: application/json' \
  --body '{
  "name": "Bunny",
  "email": "Bunny@gmail.com",
  "password": "secret123"
}'



Login

postman request POST 'http://localhost:8080/auth/login' \
  --header 'Content-Type: application/json' \
  --body '{
  "email": "Bunny@gmail.com",
  "password": "secret1231"
}'








Comments

Popular posts from this blog

Numpang Kerja Remote dari Bandung Creative Hub

Debugging PHP Web dengan XDebug di Intellij IDEA (PHP STORM)

Numpang Kerja Remote dari Bandung Digital Valley