Skip to content

Commit

Permalink
[BE] 참가자 비밀번호 암호화 적용 (#345)
Browse files Browse the repository at this point in the history
* chore(build.gradle): 비밀번호 암호화 알고리즘을 위한 의존성 추가

* feat(PasswordEncoderConfig): 비밀번호 암호화 알고리즘 스프링 Bean 등록

* feat(PasswordConverter): `@Converter`를 사용한 비밀번호 암호화 로직 추가

* test(AttendeeEncryptedPasswordFixture): 암호화된 비밀번호 Fixture 추가

* feat(AttendeeService): 비밀번호 암호화 추가로 인한 주요 Service 로직 수정

* refactor(AttendeePassword): 직관적인 메서드명으로 변경

* refactor(AttendeePassword): 불필요한 어노테이션 삭제

* test(AttendeeEncryptedPasswordFixture): 리플렉션 설정 변경 후 패키지 이

* refactor(AttendeePassword): Converter 문제 발생으로 인한 암호화 로직 리팩토링

* refactor(AttendeePassword): 생성자 내 PasswordEncoder 제거
  • Loading branch information
ikjo39 authored Sep 26, 2024
1 parent 1df794e commit d91478c
Show file tree
Hide file tree
Showing 18 changed files with 211 additions and 102 deletions.
3 changes: 3 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

implementation 'org.springframework.security:spring-security-crypto'
implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

Expand Down
15 changes: 15 additions & 0 deletions backend/src/main/java/kr/momo/config/PasswordEncoderConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kr.momo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordEncoderConfig {

@Bean
public PasswordEncoder passwordEncoder() {
return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
}
}
19 changes: 8 additions & 11 deletions backend/src/main/java/kr/momo/domain/attendee/Attendee.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;

@Table(name = "attendee")
@Entity
Expand Down Expand Up @@ -46,17 +47,17 @@ public class Attendee extends BaseEntity {
@Column(nullable = false, length = 10)
private Role role;

public Attendee(Meeting meeting, String name, AttendeePassword password, Role role) {
this(meeting, new AttendeeName(name), password, role);
}

public Attendee(Meeting meeting, AttendeeName name, AttendeePassword password, Role role) {
this.meeting = meeting;
this.name = name;
this.password = password;
this.role = role;
}

public Attendee(Meeting meeting, String name, String password, Role role) {
this(meeting, new AttendeeName(name), new AttendeePassword(password), role);
}

public boolean isHost() {
return role.isHost();
}
Expand All @@ -65,15 +66,11 @@ public boolean isNotHost() {
return !isHost();
}

public void verifyPassword(AttendeePassword other) {
this.password.verifyPassword(other);
public void verifyPassword(AttendeeRawPassword rawPassword, PasswordEncoder passwordEncoder) {
password.verifyMatch(rawPassword, passwordEncoder);
}

public String name() {
return this.name.getName();
}

public String password() {
return this.password.getPassword();
return name.getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,27 @@

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.util.regex.Pattern;
import kr.momo.exception.MomoException;
import kr.momo.exception.code.AttendeeErrorCode;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;

@Embeddable
@Getter
@EqualsAndHashCode
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class AttendeePassword {

private static final Pattern PASSWORD_PATTERN = Pattern.compile("^\\d{4}+$");

@Column(nullable = false, length = 4)
@Column(nullable = false)
private String password;

public AttendeePassword(String password) {
validatePassword(password);
this.password = password;
}

private void validatePassword(String password) {
validatePasswordFormat(password);
}

private void validatePasswordFormat(String password) {
if (!PASSWORD_PATTERN.matcher(password).matches()) {
throw new MomoException(AttendeeErrorCode.INVALID_PASSWORD_FORMAT);
}
}

public void verifyPassword(AttendeePassword other) {
if (!this.equals(other)) {
public void verifyMatch(AttendeeRawPassword rawPassword, PasswordEncoder passwordEncoder) {
if (!passwordEncoder.matches(rawPassword.password(), password)) {
throw new MomoException(AttendeeErrorCode.PASSWORD_MISMATCHED);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kr.momo.domain.attendee;

import java.util.regex.Pattern;
import kr.momo.exception.MomoException;
import kr.momo.exception.code.AttendeeErrorCode;
import org.springframework.security.crypto.password.PasswordEncoder;

public record AttendeeRawPassword(String password) {

private static final Pattern PASSWORD_PATTERN = Pattern.compile("^\\d{4}+$");

public AttendeeRawPassword {
validatePassword(password);
}

private void validatePassword(String password) {
if (password == null || !PASSWORD_PATTERN.matcher(password).matches()) {
throw new MomoException(AttendeeErrorCode.INVALID_PASSWORD_FORMAT);
}
}

public AttendeePassword encodePassword(PasswordEncoder passwordEncoder) {
return new AttendeePassword(passwordEncoder.encode(password));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import kr.momo.domain.attendee.Attendee;
import kr.momo.domain.attendee.AttendeeName;
import kr.momo.domain.attendee.AttendeePassword;
import kr.momo.domain.attendee.AttendeeRawPassword;
import kr.momo.domain.attendee.AttendeeRepository;
import kr.momo.domain.attendee.Role;
import kr.momo.domain.meeting.Meeting;
Expand All @@ -14,6 +15,7 @@
import kr.momo.service.attendee.dto.AttendeeLoginResponse;
import kr.momo.service.auth.JwtManager;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -24,26 +26,28 @@ public class AttendeeService {
private final AttendeeRepository attendeeRepository;
private final MeetingRepository meetingRepository;
private final JwtManager jwtManager;
private final PasswordEncoder passwordEncoder;

@Transactional
public AttendeeLoginResponse login(String uuid, AttendeeLoginRequest request) {
Meeting meeting = meetingRepository.findByUuid(uuid)
.orElseThrow(() -> new MomoException(MeetingErrorCode.INVALID_UUID));

AttendeeName name = new AttendeeName(request.attendeeName());
AttendeePassword password = new AttendeePassword(request.password());
AttendeeRawPassword rawPassword = new AttendeeRawPassword(request.password());

return attendeeRepository.findByMeetingAndName(meeting, name)
.map(attendee -> verifyPassword(attendee, password))
.orElseGet(() -> signup(meeting, name, password));
.map(attendee -> verifyPassword(attendee, rawPassword))
.orElseGet(() -> signup(meeting, name, rawPassword));
}

private AttendeeLoginResponse verifyPassword(Attendee attendee, AttendeePassword password) {
attendee.verifyPassword(password);
private AttendeeLoginResponse verifyPassword(Attendee attendee, AttendeeRawPassword rawPassword) {
attendee.verifyPassword(rawPassword, passwordEncoder);
return AttendeeLoginResponse.from(jwtManager.generate(attendee.getId()), attendee);
}

private AttendeeLoginResponse signup(Meeting meeting, AttendeeName name, AttendeePassword password) {
private AttendeeLoginResponse signup(Meeting meeting, AttendeeName name, AttendeeRawPassword rawPassword) {
AttendeePassword password = rawPassword.encodePassword(passwordEncoder);
Attendee attendee = new Attendee(meeting, name, password, Role.GUEST);
attendeeRepository.save(attendee);
return AttendeeLoginResponse.from(jwtManager.generate(attendee.getId()), attendee);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.time.LocalTime;
import java.util.List;
import kr.momo.domain.attendee.Attendee;
import kr.momo.domain.attendee.AttendeeRawPassword;
import kr.momo.domain.attendee.AttendeeRepository;
import kr.momo.domain.attendee.Role;
import kr.momo.domain.availabledate.AvailableDateBatchRepository;
Expand All @@ -22,6 +23,7 @@
import kr.momo.service.meeting.dto.MeetingResponse;
import kr.momo.service.meeting.dto.MeetingSharingResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -39,6 +41,7 @@ public class MeetingService {
private final AvailableDateRepository availableDateRepository;
private final AttendeeRepository attendeeRepository;
private final AvailableDateBatchRepository availableDateBatchRepository;
private final PasswordEncoder passwordEncoder;

@Transactional
public MeetingCreateResponse create(MeetingCreateRequest request) {
Expand Down Expand Up @@ -82,7 +85,8 @@ private void validateNotPast(AvailableDates meetingDates) {
}

private Attendee saveHostAttendee(Meeting meeting, String hostName, String hostPassword) {
Attendee attendee = new Attendee(meeting, hostName, hostPassword, Role.HOST);
AttendeeRawPassword rawPassword = new AttendeeRawPassword(hostPassword);
Attendee attendee = new Attendee(meeting, hostName, rawPassword.encodePassword(passwordEncoder), Role.HOST);
return attendeeRepository.save(attendee);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public List<LocalDate> toAvailableMeetingDates() {
}

public LocalTime toMeetingStartTime() {
return LocalTime.parse(meetingStartTime);
return LocalTime.parse(meetingStartTime);
}

public LocalTime toMeetingEndTime() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;

@IsolateDatabase
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
Expand All @@ -43,6 +44,9 @@ class AttendeeControllerTest {
@Autowired
private JwtManager jwtManager;

@Autowired
private PasswordEncoder passwordEncoder;

@BeforeEach
void setUp() {
RestAssured.port = port;
Expand All @@ -52,9 +56,9 @@ void setUp() {
@Test
void login() {
Meeting meeting = meetingRepository.save(MeetingFixture.COFFEE.create());
Attendee attendee = attendeeRepository.save(AttendeeFixture.HOST_JAZZ.create(meeting));

AttendeeLoginRequest request = new AttendeeLoginRequest(attendee.name(), attendee.password());
AttendeeFixture jazz = AttendeeFixture.HOST_JAZZ;
Attendee attendee = attendeeRepository.save(jazz.create(meeting));
AttendeeLoginRequest request = new AttendeeLoginRequest(attendee.name(), jazz.getPassword());

Response response = RestAssured.given().log().all()
.contentType(ContentType.JSON)
Expand Down
Loading

0 comments on commit d91478c

Please sign in to comment.