Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 코드잽 프로덕션 v1.1.1 배포 #604

Merged
merged 15 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package codezap.auth.encryption;

public interface PasswordEncryptor {
String encrypt(String plainPassword, String salt);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package codezap.auth.encryption;

import java.security.SecureRandom;
import java.util.Base64;

import org.springframework.stereotype.Component;

@Component
public class RandomSaltGenerator implements SaltGenerator {

@Override
public String generate() {
SecureRandom byteGenerator = new SecureRandom();
byte[] saltByte = new byte[32];
byteGenerator.nextBytes(saltByte);
return Base64.getEncoder().encodeToString(saltByte);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package codezap.auth.encryption;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import codezap.global.exception.CodeZapException;

@Component
public class SHA2PasswordEncryptor implements PasswordEncryptor {
private final MessageDigest digest;

public SHA2PasswordEncryptor() {
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new CodeZapException(HttpStatus.INTERNAL_SERVER_ERROR, "암호화 알고리즘이 잘못 명시되었습니다.");
}
}

@Override
public String encrypt(String plainPassword, String salt) {
String passwordWithSalt = plainPassword + salt;
byte[] encryptByte = digest.digest(passwordWithSalt.getBytes());
return Base64.getEncoder().encodeToString(encryptByte);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package codezap.auth.encryption;

@FunctionalInterface
public interface SaltGenerator {
String generate();
}
6 changes: 5 additions & 1 deletion backend/src/main/java/codezap/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import codezap.global.exception.CodeZapException;
import codezap.member.domain.Member;
import codezap.member.repository.MemberRepository;
import codezap.auth.encryption.PasswordEncryptor;
import lombok.RequiredArgsConstructor;

@Service
Expand All @@ -18,6 +19,7 @@ public class AuthService {

private final CredentialProvider credentialProvider;
private final MemberRepository memberRepository;
private final PasswordEncryptor passwordEncryptor;

public LoginAndCredentialDto login(LoginRequest loginRequest) {
Member member = getVerifiedMember(loginRequest.name(), loginRequest.password());
Expand All @@ -32,7 +34,9 @@ private Member getVerifiedMember(String name, String password) {
}

private void validateCorrectPassword(Member member, String password) {
if (!member.matchPassword(password)) {
String salt = member.getSalt();
String encryptedPassword = passwordEncryptor.encrypt(password, salt);
if (!member.matchPassword(encryptedPassword)) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "로그인에 실패하였습니다. 아이디 또는 비밀번호를 확인해주세요.");
}
}
Expand Down
7 changes: 5 additions & 2 deletions backend/src/main/java/codezap/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ public class Member extends BaseTimeEntity {
@Column(nullable = false)
private String password;

public Member(String name, String password) {
this(null, name, password);
@Column(nullable = false)
private String salt;

public Member(String name, String password, String salt) {
this(null, name, password, salt);
}

public boolean matchPassword(String password) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import codezap.member.dto.request.SignupRequest;
import codezap.member.dto.response.FindMemberResponse;
import codezap.member.repository.MemberRepository;
import codezap.auth.encryption.PasswordEncryptor;
import codezap.auth.encryption.SaltGenerator;
import lombok.RequiredArgsConstructor;

@Service
Expand All @@ -21,10 +23,14 @@ public class MemberService {

private final MemberRepository memberRepository;
private final CategoryRepository categoryRepository;
private final SaltGenerator saltGenerator;
private final PasswordEncryptor passwordEncryptor;

public Long signup(SignupRequest request) {
assertUniqueName(request.name());
Member member = memberRepository.save(new Member(request.name(), request.password()));
String salt = saltGenerator.generate();
String encryptedPassword = passwordEncryptor.encrypt(request.password(), salt);
Member member = memberRepository.save(new Member(request.name(), encryptedPassword, salt));
categoryRepository.save(Category.createDefaultCategory(member));
return member.getId();
}
Expand Down
1 change: 1 addition & 0 deletions backend/src/main/resources/db/migration/V2__add_salt.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE member ADD COLUMN salt VARCHAR(255);
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package codezap.auth.encryption;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.HashSet;
import java.util.Set;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class RandomSaltGeneratorTest {

SaltGenerator saltGenerator = new RandomSaltGenerator();

@Test
@DisplayName("1,000,000번의 난수 생성에서 난수 충돌이 발생하지 않는다.")
@Disabled
void randomGeneratorCollisionTest() {
Set<String> salts = new HashSet<>();
for (int i = 0; i < 1_000_000; i++) {
salts.add(saltGenerator.generate());
}

assertThat(salts).hasSize(1_000_000);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package codezap.auth.encryption;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class SHA2PasswordEncryptorTest {

private final PasswordEncryptor passwordEncryptor = new SHA2PasswordEncryptor();

@Test
@DisplayName("성공 : 동일한 평문으로 여러번 암호화를 해도 똑같은 암호가 나온다.")
void encryptTest() {
//given
String password = "password";
String salt = "salt";

//when
String firstCipherText = passwordEncryptor.encrypt(password, salt);
String secondCipherText = passwordEncryptor.encrypt(password, salt);

//then
assertThat(firstCipherText).isEqualTo(secondCipherText);
}

@Test
@DisplayName("성공 : 동일한 평문, 다른 salt 값을 사용하면 다른 암호문이 나온다.")
void encryptTestWithDifferentSalt() {
//given
String password = "password";

//when
String firstCipherText = passwordEncryptor.encrypt(password, "salt");
String secondCipherText = passwordEncryptor.encrypt(password, "otherSalt");

//then
assertThat(firstCipherText).isNotEqualTo(secondCipherText);
}

@Test
@DisplayName("성공 : 동일한 salt 값, 다른 평문을 사용하면 다른 암호문이 나온다.")
void encryptTestWithDifferentPassword() {
//given
String salt = "salt";

//when
String firstCipherText = passwordEncryptor.encrypt("password1", salt);
String secondCipherText = passwordEncryptor.encrypt("password2", salt);

//then
assertThat(firstCipherText).isNotEqualTo(secondCipherText);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
import codezap.member.fixture.MemberFixture;
import codezap.member.repository.FakeMemberRepository;
import codezap.member.repository.MemberRepository;
import codezap.auth.encryption.PasswordEncryptor;
import codezap.auth.encryption.SHA2PasswordEncryptor;

public class AuthServiceTest {
private final MemberRepository memberRepository = new FakeMemberRepository();
private final PasswordEncryptor passwordEncryptor = new SHA2PasswordEncryptor();
private final CredentialProvider credentialProvider = new BasicAuthCredentialProvider(memberRepository);
private final AuthService authService = new AuthService(credentialProvider, memberRepository);
private final AuthService authService = new AuthService(credentialProvider, memberRepository, passwordEncryptor);

@Nested
@DisplayName("로그인 테스트")
Expand All @@ -36,7 +39,7 @@ class LoginTest {
@DisplayName("로그인 성공")
void login() {
Member member = memberRepository.save(MemberFixture.memberFixture());
LoginRequest loginRequest = new LoginRequest(member.getName(), member.getPassword());
LoginRequest loginRequest = new LoginRequest(member.getName(), MemberFixture.getFixturePlainPassword());

LoginAndCredentialDto loginAndCredentialDto = authService.login(loginRequest);

Expand Down
6 changes: 4 additions & 2 deletions backend/src/test/java/codezap/fixture/MemberFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ public static Member getFirstMember() {
return new Member(
1L,
"몰리",
"password1234"
"password1234",
"salt1"
);
}

public static Member getSecondMember() {
return new Member(
2L,
"몰리2",
"password1234"
"password1234",
"salt2"
);
}
}
15 changes: 13 additions & 2 deletions backend/src/test/java/codezap/member/fixture/MemberFixture.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
package codezap.member.fixture;

import codezap.member.domain.Member;
import codezap.auth.encryption.PasswordEncryptor;
import codezap.auth.encryption.SHA2PasswordEncryptor;

public class MemberFixture {

private static final PasswordEncryptor passwordEncryptor = new SHA2PasswordEncryptor();

public static Member memberFixture() {
String encrypted = passwordEncryptor.encrypt(getFixturePlainPassword(), "salt");
return new Member(
1L,
"몰리",
"password1234"
encrypted,
"salt"
);
}

public static String getFixturePlainPassword() {
return "password1234";
}

public static Member createFixture(String name) {
return new Member(
name,
"password1234"
"password1234",
"salt"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ public boolean existsByName(String name) {
public Member save(Member entity) {
var saved = new Member(
getOrGenerateId(entity),
entity.getName(),
entity.getPassword(),
entity.getName()
entity.getSalt()
);
members.removeIf(member -> Objects.equals(member.getId(), entity.getId()));
members.add(saved);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@
import codezap.member.fixture.MemberFixture;
import codezap.member.repository.FakeMemberRepository;
import codezap.member.repository.MemberRepository;
import codezap.auth.encryption.PasswordEncryptor;
import codezap.auth.encryption.RandomSaltGenerator;
import codezap.auth.encryption.SHA2PasswordEncryptor;
import codezap.auth.encryption.SaltGenerator;

public class MemberServiceTest {

private final MemberRepository memberRepository = new FakeMemberRepository();
private final CategoryRepository categoryRepository = new FakeCategoryRepository();
private final MemberService memberService = new MemberService(memberRepository, categoryRepository);
private final SaltGenerator saltGenerator = new RandomSaltGenerator();
private final PasswordEncryptor passwordEncryptor = new SHA2PasswordEncryptor();
private final MemberService memberService = new MemberService(memberRepository, categoryRepository, saltGenerator, passwordEncryptor);

@Nested
@DisplayName("회원가입 테스트")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class TemplateTagJpaRepositoryTest {

@BeforeEach
void setUp() {
member1 = memberRepository.save(new Member("[email protected]", "pp"));
member2 = memberRepository.save(new Member("[email protected]", "pp"));
member1 = memberRepository.save(new Member("[email protected]", "pp", "salt1"));
member2 = memberRepository.save(new Member("[email protected]", "pp", "salt2"));

category1 = categoryRepository.save(new Category("Category 1", member1));
category2 = categoryRepository.save(new Category("Category 2", member1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
import codezap.member.repository.FakeMemberRepository;
import codezap.member.repository.MemberRepository;
import codezap.member.service.MemberService;
import codezap.auth.encryption.PasswordEncryptor;
import codezap.auth.encryption.RandomSaltGenerator;
import codezap.auth.encryption.SHA2PasswordEncryptor;
import codezap.auth.encryption.SaltGenerator;
import codezap.tag.service.TemplateTagService;
import codezap.template.dto.request.CreateSourceCodeRequest;
import codezap.template.dto.request.CreateTemplateRequest;
Expand Down Expand Up @@ -69,6 +73,9 @@ class TemplateControllerTest {
private final MemberRepository memberRepository = new FakeMemberRepository(
List.of(MemberFixture.getFirstMember(), MemberFixture.getSecondMember())
);

private final SaltGenerator saltGenerator = new RandomSaltGenerator();
private final PasswordEncryptor passwordEncryptor = new SHA2PasswordEncryptor();
private final TemplateService templateService = new TemplateService(templateRepository);
private final CategoryService categoryService = new CategoryService(categoryRepository);

Expand All @@ -91,7 +98,7 @@ class TemplateControllerTest {

private final MemberTemplateApplicationService memberTemplateApplicationService =
new MemberTemplateApplicationService(
new MemberService(memberRepository, categoryRepository),
new MemberService(memberRepository, categoryRepository, saltGenerator, passwordEncryptor),
categoryTemplateApplicationService,
templateApplicationService
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ class TemplateRepositoryFindAllTest {

@BeforeEach
void setUp() {
member1 = memberRepository.save(new Member("[email protected]", "pp"));
member2 = memberRepository.save(new Member("[email protected]", "pp"));
member1 = memberRepository.save(new Member("[email protected]", "pp", "salt1"));
member2 = memberRepository.save(new Member("[email protected]", "pp", "salt2"));

category1 = categoryRepository.save(new Category("Category 1", member1));
category2 = categoryRepository.save(new Category("Category 2", member1));
Expand Down