이번에는 회원가입및 로그인 인증처리 포스팅을 작성해보겠다.
UserDetailsService 를 상속받아 사용하는 방식이 일반적이지만 나는 사용하지않고 구현해보았다.
앞서 의존성을 받아준다
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
testImplementation 'org.springframework.security:spring-security-test'
회원가입은 간단하다
회원가입에 request로 사용할 dto를 하나 선언해준다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "회원가입 요청 DTO")
public class SignupRequest {
@NotBlank
@ApiModelProperty(value = "이메일 입력 필드", dataType = "String")
private String email;
@NotBlank
@ApiModelProperty(value = "패스워드 입력 필드", dataType = "String")
private String password;
@NotBlank
@ApiModelProperty(value = "패스워드 확인 입력 필드", dataType = "String")
private String passwordCheck;
@NotBlank
@ApiModelProperty(value = "닉네임 입력 필드", dataType = "String")
private String nickname;
@ApiModelProperty(value = "이미지 파일 입력 필드", dataType = "MultipartFile")
private MultipartFile profileFile;
}
@PostMapping(value = "/signup", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "회원 가입 API", description = "폼데이터로 요청")
public Response<LoginResponse> signup(@ModelAttribute SignupRequest signupRequest) {
return ApiUtils.success(HttpStatus.CREATED, "회원 가입 성공", userService.signup(signupRequest));
}
스펙에 따라 컨트롤러에서 요청을 받고
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Transactional
public LoginResponse signup(SignupRequest signupRequest) {
checkDuplicateEmail(signupRequest.getEmail());
checkConfirmPassword(signupRequest.getPassword(), signupRequest.getPasswordCheck());
User user = User.from(signupRequest, passwordEncoder.encode(signupRequest.getPassword()));
userRepository.save(user);
user.setProfileUrl(uploadImageFile(signupRequest.getProfileFile(), user));
String accessToken = TokenProvider.createToken(user);
return LoginResponse.from(user, accessToken);
}
private void checkDuplicateEmail(String email) {
if (userRepository.existsByEmail(email)) {
throw new CustomException(UserErrorCode.DUPLICATE_USER_ID);
}
}
private void checkConfirmPassword(String password, String passwordCheck) {
if (!password.equals(passwordCheck)) {
throw new CustomException(UserErrorCode.NOT_MATCH_PASSWORD_CONFIRM);
}
}
일반적인 회원가입이다. 한가지 짚어보고 넘어갈 부분은
userRepository.save(user);
user.setProfileUrl(uploadImageFile(signupRequest.getProfileFile(), user));
이 부분
save로 먼저 저장을 하고 저장한 객체에 접근하여 데이터를 변경하여도 DB에 적용이 된다.
이는 JPA의 영속성과 관련된 부분으로 save를 통해 user를 영속성 컨테이너에 저장하여 저장된 영속 엔티티들은 DB와 동일성을 보장해준다.
그 후 로그인 로직을 처리해보도록 하자
먼저 jwt 방식의 토큰 로그인을 구현하기 위해 TokenPorvider 클래스를 생성한다
@Service
public class TokenProvider {
private static String SECRET_KEY;
@Value("${secret-key-source}")
public void setKey(String value) {
SECRET_KEY = value;
}
public static String createToken(User user) {
Date expiryDate = Date.from(
Instant.now()
.plus(1, ChronoUnit.DAYS)
);
Claims claims = Jwts.claims();
claims.put("userId", user.getId());
claims.put("userEmail", user.getEmail());
return Jwts.builder()
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.setClaims(claims)
.setExpiration(expiryDate)
.compact();
}
// Claims에서 loginId 꺼내기
public static String getUserEmail(String token) {
return extractClaims(token).get("userEmail").toString();
}
public static Long getUserId(String token) {
return Long.valueOf(extractClaims(token).get("userId").toString());
}
// 밝급된 Token이 만료 시간이 지났는지 체크
public static boolean isExpired(String token) {
Date expiredDate = extractClaims(token).getExpiration();
// Token의 만료 날짜가 지금보다 이전인지 check
return expiredDate.before(new Date());
}
private static Claims extractClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
}
당연히 시크릿키는 노출되면 안된다.
간단한 코드들만 있으니 서비스에 맞게 커스텀해서 사용하면 된다