๐Ÿ‘ค ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ

jijiยท2023๋…„ 11์›” 20์ผ

Spring Boot Project ๐ŸŒฑ

๋ชฉ๋ก ๋ณด๊ธฐ
13/16

๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฐ JWT๋ฅผ ํ™œ์šฉ - ์ธ์ฆ, ์ธ๊ฐ€ ๊ตฌํ˜„
ํšŒ์›๊ฐ€์ž… โ†’ ๋กœ๊ทธ์ธ โ†’ ๋‚ด ์ •๋ณด ์กฐํšŒ

โœ” ๋ชฉํ‘œ

  • ํšŒ์›๊ฐ€์ž…
    • ์š”์ฒญ : ์ด๋ฉ”์ผ, ํŒจ์Šค์›Œ๋“œ / DB์— ์ด๋ฉ”์ผ, ํŒจ์Šค์›Œ๋“œ, ํšŒ์› ๊ฐ€์ž… ์‹œ๊ฐ„์„ ์ €์žฅ
    • id(PK, primary key) : Auto-increment โœ”
    • ์ด๋ฉ”์ผ :
      - @ ํฌํ•จ โœ”
      - ๊ณต๋ฐฑ X โœ”
      - ์ค‘๋ณต X
    • ํŒจ์Šค์›Œ๋“œ
      - ๊ณต๋ฐฑ X โœ”
      - 8์ž ์ด์ƒ 15์ž ์ดํ•˜ โœ”
      - ์•”ํ˜ธํ™” X โœ”
  • ๋กœ๊ทธ์ธ
    - ๋กœ๊ทธ์ธ ์‹œ ์ด๋ฉ”์ผ, ํŒจ์Šค์›Œ๋“œ ๊ฐ’์„ ๋ฐ›๋Š”๋‹ค. โœ”
    - ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์„ ๋•Œ, JWT๋ฅผ ํ™œ์šฉํ•ด Access Token ๊ฐ’์„ ์‘๋‹ตํ•ด์•ผ ํ•œ๋‹ค.
    - JWT์˜ payload์—๋Š” ์‚ฌ์šฉ์ž์˜ id(PK, primary key)๊ฐ€ ๋ฐ˜๋“œ์‹œ ๋‹ด๊ฒจ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  • ๋‚ด ์ •๋ณด ์กฐํšŒ ๊ธฐ๋Šฅ
    • ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ Header์— JWT ํ† ํฐ์„ ๋„˜๊ธฐ๋„๋ก ํ•œ๋‹ค.
    • Header์— JWT ํ† ํฐ์ด ๋‹ด๊ฒจ์žˆ์ง€ ์•Š๋‹ค๋ฉด ์—๋Ÿฌ๋กœ ์‘๋‹ตํ•œ๋‹ค.
    • Header์— ๋‹ด๊ฒจ์žˆ๋Š” JWT ํ† ํฐ์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š๊ฑฐ๋‚˜ ์กฐ์ž‘๋˜์—ˆ๋‹ค๋ฉด ์—๋Ÿฌ๋กœ ์‘๋‹ตํ•œ๋‹ค.
    • Header์— ๋‹ด๊ฒจ์žˆ๋Š” JWT ํ† ํฐ์˜ ๋งŒ๋ฃŒ๊ธฐ๊ฐ„์ด ์ง€๋‚ฌ๋‹ค๋ฉด ์—๋Ÿฌ๋กœ ์‘๋‹ตํ•œ๋‹ค.
    • Haeder์— ๋‹ด๊ฒจ์žˆ๋Š” JWT ํ† ํฐ์ด ์˜ฌ๋ฐ”๋ฅด๋‹ค๋ฉด, JWT ํ† ํฐ์˜ payload์— ๋‹ด๊ฒจ์žˆ๋Š” ์‚ฌ์šฉ์ž์˜ id(PK, primary key)๋ฅผ ํ™œ์šฉํ•ด๋ผ.
    • ์‘๋‹ต๊ฐ’์—๋Š” id(PK, primary key), ์ด๋ฉ”์ผ, ํšŒ์› ๊ฐ€์ž… ์‹œ๊ฐ„์ด ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค.
    • ์‘๋‹ต๊ฐ’์—๋Š” ํŒจ์Šค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋˜๋ฉด ์•ˆ ๋œ๋‹ค.

๐Ÿ“Œ ๋ฐฐ์šด์ 


๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป ๊ฐœ๋ฐœ ๊ณผ์ •

1. ํšŒ์›๊ฐ€์ž…

1) User Entity

@Getter
@ToString
@EqualsAndHashCode(of = "id", callSuper = false)
@NoArgsConstructor
@AllArgsConstructor
@Builder

@Entity
@Table(name = "tbl_user")
public class User extends BaseEntity {
  @Id
  @Column(name = "user_id")
  @GeneratedValue(generator = "system-uuid")
  @GenericGenerator(name = "system-uuid", strategy = "uuid")
  private String id; // ๊ณ„์ •๋ช…์ด ์•„๋‹ˆ๋ผ ์‹๋ณ„์ฝ”๋“œ

  @Column(unique = true, nullable = false)
  private String email;
  @Column(nullable = false)
  private String password;

  @Builder
  public User(String email, String password) {
    this.email = email;
    this.password = password;
  }
}

2) SignUpRequestDTO

@Getter
...
@Builder
public class SignUpRequestDTO {
  @NotBlank(message = "์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
  @Email(message = "์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค")
  private String email;

  @NotBlank(message = "ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
  @Size(min = 8, max = 15, message = "ํŒจ์Šค์›Œ๋“œ๋Š” 8์ž ์ด์ƒ 15์ž ์ดํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
  private String password;
}

3) UserController

@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("api/user")
public class UserController {
  private final UserService userService;

  @PostMapping("/sign-up")
  public ResponseEntity<SignUpResponseDTO> signUp(
      @Validated @RequestBody SignUpRequestDTO dto
  ) {
    log.info("/api/user/sign-up POST! - {}", dto);

    SignUpResponseDTO signUpResponseDTO = userService.create(dto);
    return ResponseEntity.ok().body(signUpResponseDTO);
  }

4) UserService

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
  private final UserRepository userRepository;
  private final TokenProvider tokenProvider;

  public SignUpResponseDTO create(SignUpRequestDTO dto) {
    if (dto == null) {
      throw new NoRegisteredArgumentsException("๊ฐ€์ž… ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.");
    }
    String email = dto.getEmail();

    if (isDuplicate(email)) {
      throw new DuplicatedEmailException("์ค‘๋ณต๋œ ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค : [ " + email + " ]");
    }

    User saved = UserMapper.toEntity(dto);
    userRepository.save(saved);

    log.info(String.valueOf(saved));

    return new SignUpResponseDTO(saved);

  }
  
  // ์ด๋ฉ”์ผ ์ค‘๋ณต ๊ฒ€์‚ฌ
  public boolean isDuplicate(String email) {
    return userRepository.existsByEmail(email);
  }

๐Ÿคš๐Ÿป UserExceptionHandler

@Slf4j
@RestControllerAdvice
public class UserExceptionHandler {
  @ExceptionHandler(NoRegisteredArgumentsException.class)
  public ResponseEntity<?> handleNoRegisteredArgumentsException(NoRegisteredArgumentsException e) {
    log.warn("ํ•„์ˆ˜ ๊ฐ€์ž… ์ •๋ณด๋ฅผ ์ „๋‹ฌ๋ฐ›์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.");
    return ResponseEntity.badRequest()
        .body(e.getMessage());
  }

  @ExceptionHandler(DuplicatedEmailException.class)
  public ResponseEntity<?> handleDuplicatedEmailException(DuplicatedEmailException e) {
    log.warn("์ด๋ฉ”์ผ ์ค‘๋ณต์ž…๋‹ˆ๋‹ค!");
    return ResponseEntity.badRequest()
        .body(e.getMessage());
  }

  @ExceptionHandler(Exception.class)
  public ResponseEntity<?> handleOtherExceptions(Exception e) {
    log.warn("๊ธฐํƒ€ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. - {}", e.getMessage());
//    e.printStackTrace();
//    return ResponseEntity.internalServerError().build();
    return ResponseEntity.badRequest().body(e.getMessage());
  }
}

exception class

@NoArgsConstructor
public class NoRegisteredArgumentsException
    extends RuntimeException {

    // ๊ธฐ๋ณธ ์ƒ์„ฑ์ž + ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›๋Š” ์ƒ์„ฑ์ž
    public NoRegisteredArgumentsException(String message) {
        super(message);
    }
}
@NoArgsConstructor
public class DuplicatedEmailException
    extends RuntimeException {

    public DuplicatedEmailException(String message) {
        super(message);
    }
}

2. ๋กœ๊ทธ์ธ

  • controller
 @PostMapping("/sign-in")
  public ResponseEntity<LoginResponseDTO> signIn(@Validated @RequestBody LoginRequestDTO dto) {
    LoginResponseDTO responseDTO = userService.authenticate(dto);

    return ResponseEntity.ok().body(responseDTO);
  }
  • service
  // ๋กœ๊ทธ์ธ - ํšŒ์› ์ธ์ฆ
  public LoginResponseDTO authenticate(LoginRequestDTO dto) {

    // ์ด๋ฉ”์ผ์„ ํ†ตํ•ด ํšŒ์› ์ •๋ณด ์กฐํšŒ
    User user = userRepository.findByEmail(dto.getEmail())
        .orElseThrow(() -> new RuntimeException("๊ฐ€์ž…๋œ ํšŒ์›์ด ์•„๋‹™๋‹ˆ๋‹ค!"));

    // ํŒจ์Šค์›Œ๋“œ ๊ฒ€์ฆ
    String rawPassword = dto.getPassword(); // ์ž…๋ ฅ ๋น„๋ฒˆ
    String savedPassword = user.getPassword(); // DB์— ์ €์žฅ๋œ ๋น„๋ฒˆ

    if (!rawPassword.equals(savedPassword)) {
      throw new RuntimeException("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค.");
    }
    log.info("{}๋‹˜ ๋กœ๊ทธ์ธ ์„ฑ๊ณต!!", user.getEmail());

    // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„์— ํด๋ผ์ด์–ธํŠธ์— ๋ญ˜ ๋ฆฌํ„ดํ•  ๊ฒƒ์ธ๊ฐ€??
    // -> JWT๋ฅผ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐœ๊ธ‰ํ•ด์ค˜์•ผ ํ•จ.
    String token = tokenProvider.createToken(user);

    return new LoginResponseDTO(user, token);
  }

๐Ÿ‘‰๐Ÿป JWT token

0๊ฐœ์˜ ๋Œ“๊ธ€