23 - Login
เป้าหมายของบท
Section titled “เป้าหมายของบท”บทนี้จะเพิ่ม endpoint:
POST /api/v1/auth/loginหลังจบบทนี้ผู้อ่านควรเข้าใจ:
- login flow ทำงานอย่างไร
- ทำไมต้องหา user ด้วย email
- ทำไมต้องใช้
passwordEncoder.matches - ทำไมไม่ควรบอกละเอียดว่า email ผิดหรือ password ผิด
- login response ควรมี token และข้อมูล user
Login flow
Section titled “Login flow”Client -> POST /api/v1/auth/login -> AuthController -> AuthService -> UserRepository -> PasswordEncoder -> LoginResponseขั้นตอน:
- รับ email/password
- validate request
- หา user ด้วย email
- ถ้าไม่เจอ ตอบ
401 Unauthorized - ถ้า password ไม่ถูก ตอบ
401 Unauthorized - ถ้าถูกต้อง สร้าง login response
สร้าง LoginRequest
Section titled “สร้าง LoginRequest”สร้างไฟล์:
src/main/java/com/example/backendapi/dto/LoginRequest.javapackage com.example.backendapi.dto;
import jakarta.validation.constraints.Email;import jakarta.validation.constraints.NotBlank;
public record LoginRequest( @NotBlank(message = "Email is required") @Email(message = "Email format is invalid") String email,
@NotBlank(message = "Password is required") String password) {}สร้าง LoginResponse
Section titled “สร้าง LoginResponse”สร้างไฟล์:
src/main/java/com/example/backendapi/dto/LoginResponse.javapackage com.example.backendapi.dto;
public record LoginResponse( String token, UserResponse user) {}บทนี้จะใส่ token เป็น placeholder ก่อน บทถัดไปจะสร้าง JWT จริง
เพิ่ม login endpoint
Section titled “เพิ่ม login endpoint”เปิด AuthController แล้วเพิ่ม:
@PostMapping("/login")public ApiResponse<LoginResponse> login( @Valid @RequestBody LoginRequest request) { return ApiResponse.ok("Login successful", authService.login(request));}เพิ่ม login method ใน AuthService
Section titled “เพิ่ม login method ใน AuthService”public LoginResponse login(LoginRequest request) { User user = userRepository.findByEmail(request.email()) .orElseThrow(() -> new BadCredentialsException("Invalid email or password"));
if (!passwordEncoder.matches(request.password(), user.getPassword())) { throw new BadCredentialsException("Invalid email or password"); }
return new LoginResponse("token-will-be-created-next", toResponse(user));}ใช้ BadCredentialsException จาก Spring Security:
import org.springframework.security.authentication.BadCredentialsException;เพิ่ม handler สำหรับ 401
Section titled “เพิ่ม handler สำหรับ 401”เปิด GlobalExceptionHandler แล้วเพิ่ม:
@ExceptionHandler(BadCredentialsException.class)public ResponseEntity<ErrorResponse> handleBadCredentials( BadCredentialsException ex, HttpServletRequest request) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(ErrorResponse.of(401, ex.getMessage(), request.getRequestURI()));}อย่าตอบละเอียดเกินไป
Section titled “อย่าตอบละเอียดเกินไป”อย่าทำแบบนี้:
Email not foundPassword is wrongเพราะผู้โจมตีจะใช้แยกได้ว่า email ไหนมี account อยู่จริง
ควรตอบกลาง ๆ:
Invalid email or passwordกรณีสำเร็จ:
POST /api/v1/auth/loginContent-Type: application/json{ "email": "john@example.com", "password": "password123"}กรณี password ผิด:
{ "email": "john@example.com", "password": "wrong-password"}ควรได้:
401 Unauthorizedแบบฝึกหัดท้ายบท
Section titled “แบบฝึกหัดท้ายบท”- สร้าง
LoginRequest - สร้าง
LoginResponse - เพิ่ม endpoint
/login - ทดสอบ login สำเร็จ
- ทดสอบ email ไม่มี
- ทดสอบ password ผิด