회원가입 및 로그인 기능 구현
프로젝트를 진행하면서 가장 기본적인 회원 가입과 로그인 기능을 구현해보겠습니다.
JWT 토큰 인증 방식
JWT(토큰)은 JSON Web Token의 약자로, 웹 애플리케이션에서 사용자 인증을 위한 인증 방식 중 하나입니다. 이 방식은 서버에서 사용자에게 발급한 토큰을 사용하여 인증과 권한 부여를 처리합니다.
JWT 토큰 인증 방식은 다음과 같은 원리로 동작합니다
- 사용자가 로그인을 하면, 서버는 사용자의 정보와 함께 서명된 토큰을 발급합니다.
- 발급된 토큰은 사용자의 브라우저에 저장되거나 앱에 저장됩니다.
- 사용자가 다음 요청을 보낼 때마다, 토큰은 요청 헤더에 포함되어 서버로 전송됩니다.
- 서버는 전송된 토큰을 검증하여 사용자의 신원을 확인하고, 필요한 권한을 부여합니다.
- 인증이 필요한 요청에 대해서는 토큰이 유효한지 확인하고, 유효하다면 요청을 처리합니다.
이렇게 JWT 토큰 인증 방식을 사용하면 서버는 세션 상태를 유지할 필요 없이 토큰을 통해 사용자를 인증할 수 있습니다. 또한, 토큰에는 사용자의 정보를 안전하게 저장할 수 있어 서버의 부하를 줄일 수 있습니다.
✔️ 이제 회원 가입과 로그인을 위한 클래스들을 만들어보겠습니다.
우선 DTO 클래스를 만들어보겠습니다.
@Setter
@Getter
public class MemberDto {
@NotBlank
@Size(min=2, max = 15, message = "아이디는 2자 이상 15자 이하로 입력해주세요.")
private String userId;
@NotBlank(message = "이메일 주소를 입력해주세요.")
@Email(message = "이메일 형식으로 입력해주세요.")
private String email;
@NotBlank(message = "비밀번호를 입력해주세요.")
@Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하로 입력해주세요.")
private String password;
}
DTO(Data Transfer Object)란?
데이터 전송 객체의 약자로, 계층 간 데이터 교환을 위해 사용되는 객체입니다. 주로 데이터베이스에서 읽어온 데이터를 비즈니스 로직으로 전달하거나, 클라이언트에서 입력한 데이터를 서버로 전달하는 데 사용됩니다.
장점
- 특정 데이터만 전달하기 때문에 불필요한 데이터 방지
- 유효성 검증
- 비즈니스 로직과의 분리를 통한 편리한 유지보수
- 클라이언트와의 기대하는 데이터 형식 일치
✔️ 다음은 클라이언트의 요청을 처리하기 위한 컨트롤러 클래스입니다.
@RequiredArgsConstructor
@RestController
@RequestMapping("/members")
public class MemberController {
private final UserService userService;
private final MailService mailService;
private final JwtUtilityService jwtUtilityService;
// 회원 가입 기능
@PostMapping("/signup")
public ResponseEntity<String> signup(@RequestBody MemberDto request) {
try {
userService.save(request);
return ResponseEntity.ok("회원가입이 완료되었습니다.");
} catch (IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(e.getMessage());
}
}
// 로그인 기능
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody MemberDto request) {
String userId = request.getUserId();
String password = request.getPassword();
JwtToken token = userService.login(userId, password);
return ResponseEntity.ok(token);
}
signup 메소드는 회원 가입 기능을 구현하고, login 메소드는 로그인 기능을 구현합니다. 클라이언트가 입력한 아이디의 중복 여부를 검사하고, 중복되지 않으면 회원 정보를 저장합니다. 로그인 기능에서는 클라이언트가 입력한 아이디와 비밀번호를 검사하여, 유효하다면 JWT 토큰을 생성하여 반환합니다.
✔️ 다음은 실질적인 비즈니스 로직을 담고 있는 Service 클래스입니다.
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final RefreshTokenRepository refreshTokenRepository;
// 회원가입 서비스
public void save(MemberDto dto) {
String checkId = dto.getUserId();
boolean isDuplicate = checkIdDuplicate(checkId);
if (isDuplicate) {
} else {
userRepository.save(User.builder()
.userId(dto.getUserId())
.email(dto.getEmail())
.password(bCryptPasswordEncoder.encode(dto.getPassword()))
.build());
}
}
// 아이디 중복 검사
public boolean checkIdDuplicate(String userId) {
return userRepository.existsByUserId(userId);
}
// 로그인 서비스
public JwtToken login(String userId, String password) {
// Authentication 객체 생성
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userId, password);
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
// 검증된 인증 정보로 JWT 토큰 생성
JwtToken token = tokenProvider.generateToken(authentication);
// 아이디 기반 사용자 ID 검색
String userIdInDB = getIdByUserId(userId);
// RefreshToken 객체 생성
RefreshToken refreshToken = new RefreshToken(userIdInDB, token.getRefreshToken());
// 데이터 베이스에 refreshToken 저장
refreshTokenRepository.save(refreshToken);
return token;
}
위 코드는 회원 가입과 로그인 기능을 구현하기 위한 코드입니다.
Service 클래스는 실질적인 비즈니스 로직을 담고 있습니다. save 메소드는 회원 정보를 저장하는 회원 가입 서비스를 구현하고, checkIdDuplicate 메소드는 아이디의 중복 여부를 검사합니다. login 메소드는 클라이언트가 입력한 아이디와 비밀번호를 검사하여, 검증된 인증 정보로 JWT 토큰을 생성합니다.
✔️다음은 Repository 클래스를 만들어보겠습니다.
Repository(레포지토리)란?
Repository는 데이터베이스와의 상호작용을 처리하기 위한 객체입니다. 주로 데이터베이스에 접근하여 데이터를 생성, 조회, 수정, 삭제하는 역할을 담당합니다. Repository는 데이터베이스를 추상화하여 개발자가 간편하게 데이터 조작을 할 수 있도록 도와줍니다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Repository 클래스는 JPA 인터페이스를 상속받아 repository를 작성하겠습니다.
JPA란?
JPA(Java Persistence API)는 자바에서 데이터베이스와의 상호작용을 간편하게 처리하기 위한 인터페이스입니다. JPA를 사용하면 개발자는 SQL 쿼리문을 직접 작성하지 않고도 객체 지향적인 방식으로 데이터베이스를 다룰 수 있습니다.
장점
- 객체-관계 매핑 : JPA는 자바 객체와 데이터베이스 테이블 간의 매핑을 지원하기 때문에 간단한 어노테이션을 사용하여 객체와 테이블을 매핑할 수 있으며, JPA가 자동으로 SQL을 생성하여 데이터를 저장하고 조회할 수 있습니다.
- 자동 CRUD 기능 : 별도의 CRUD(Create, Read, Update, Delete) 쿼리문을 작성하지 않아도 자동으로 데이터를 조작할 수 있습니다.
- 캐시 기능 : 애플리케이션 내부에 캐시를 생성하여 데이터베이스 조회 성능을 향상시킵니다.
- 트랜잭션 관리: JPA는 데이터베이스 작업을 트랜잭션 단위로 관리할 수 있습니다. 트랜잭션을 사용하면 데이터의 일관성과 안정성을 보장할 수 있습니다.
