Skip to content

Commit 3ceb5e4

Browse files
authored
Merge pull request #1 from NameSolver/feat/auth
Feat/auth
2 parents 07b5c80 + 1279eff commit 3ceb5e4

29 files changed

+1095
-14
lines changed

build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,22 @@ dependencies {
2727
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
2828
implementation 'org.springframework.boot:spring-boot-starter-security'
2929
implementation 'org.springframework.boot:spring-boot-starter-web'
30+
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
3031
compileOnly 'org.projectlombok:lombok'
3132
runtimeOnly 'com.mysql:mysql-connector-j'
3233
annotationProcessor 'org.projectlombok:lombok'
3334
testImplementation 'org.springframework.boot:spring-boot-starter-test'
3435
testImplementation 'org.springframework.security:spring-security-test'
36+
testImplementation 'org.projectlombok:lombok'
3537
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
38+
implementation 'org.seleniumhq.selenium:selenium-java:4.6.0'
39+
testCompileOnly 'org.projectlombok:lombok'
40+
testAnnotationProcessor 'org.projectlombok:lombok'
41+
42+
//JWT
43+
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
44+
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
45+
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
3646
}
3747

3848
tasks.named('test') {

drivers/chromedriver.exe

17.9 MB
Binary file not shown.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.dongdong.nameSolver.domain.auth.application.dto.request;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Data;
6+
import lombok.NoArgsConstructor;
7+
8+
@Data
9+
public class CreateAuthKeyCommand {
10+
private String solvedacName;
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.dongdong.nameSolver.domain.auth.application.dto.request;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
6+
import java.util.UUID;
7+
8+
@Data
9+
@AllArgsConstructor
10+
public class ReissueCommand {
11+
private String refreshToken;
12+
private UUID memberId;
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.dongdong.nameSolver.domain.auth.application.dto.request;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public class SignInCommand {
7+
private String id;
8+
private String password;
9+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.dongdong.nameSolver.domain.auth.application.dto.request;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@AllArgsConstructor
8+
public class SignUpCommand {
9+
private String name;
10+
private String email;
11+
private String solvedacName;
12+
private String id;
13+
private String password;
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.dongdong.nameSolver.domain.auth.application.dto.response;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Data;
6+
7+
@Data
8+
@AllArgsConstructor
9+
public class AuthKeyResponse {
10+
private String key;
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.dongdong.nameSolver.domain.auth.application.dto.response;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
6+
@Data
7+
@AllArgsConstructor
8+
public class AuthTokenResponse {
9+
private String accessToken;
10+
private String refreshToken;
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.dongdong.nameSolver.domain.auth.application.dto.response;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
6+
@Data
7+
@AllArgsConstructor
8+
public class SignUpResponse {
9+
private String memberId;
10+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package com.dongdong.nameSolver.domain.auth.application.service;
2+
3+
import com.dongdong.nameSolver.domain.auth.application.dto.request.CreateAuthKeyCommand;
4+
import com.dongdong.nameSolver.domain.auth.application.dto.request.ReissueCommand;
5+
import com.dongdong.nameSolver.domain.auth.application.dto.request.SignInCommand;
6+
import com.dongdong.nameSolver.domain.auth.application.dto.request.SignUpCommand;
7+
import com.dongdong.nameSolver.domain.auth.application.dto.response.AuthTokenResponse;
8+
import com.dongdong.nameSolver.domain.auth.application.dto.response.AuthKeyResponse;
9+
import com.dongdong.nameSolver.domain.auth.application.dto.response.SignUpResponse;
10+
import com.dongdong.nameSolver.domain.member.domain.entity.Member;
11+
import com.dongdong.nameSolver.domain.member.domain.repository.MemberRepository;
12+
import com.dongdong.nameSolver.global.jwt.JwtTokenHandler;
13+
import com.dongdong.nameSolver.global.util.RedisUtil;
14+
import com.dongdong.nameSolver.global.util.WebDriverUtil;
15+
import lombok.RequiredArgsConstructor;
16+
import lombok.extern.slf4j.Slf4j;
17+
import org.openqa.selenium.By;
18+
import org.openqa.selenium.WebDriver;
19+
import org.openqa.selenium.WebElement;
20+
import org.openqa.selenium.devtools.v85.runtime.Runtime;
21+
import org.springframework.security.crypto.password.PasswordEncoder;
22+
import org.springframework.stereotype.Service;
23+
import org.springframework.transaction.annotation.Transactional;
24+
import org.springframework.util.ObjectUtils;
25+
import java.time.Duration;
26+
import java.util.UUID;
27+
28+
@Slf4j
29+
@Service
30+
@RequiredArgsConstructor
31+
public class AuthService {
32+
private final MemberRepository memberRepository;
33+
private final RedisUtil redisUtil;
34+
private final PasswordEncoder passwordEncoder;
35+
private final JwtTokenHandler jwtTokenHandler;
36+
37+
/**
38+
* solvedac 사용자 인증용 키 발급 메서드
39+
*/
40+
public AuthKeyResponse createKey(CreateAuthKeyCommand createAuthKeyCommand) {
41+
// 랜덤 키 생성
42+
String randomKey = UUID.randomUUID().toString().substring(0, 8);
43+
44+
// 키 redis에 저장 10분 동안 유효
45+
redisUtil.setData(createAuthKeyCommand.getSolvedacName(), randomKey, 600000L);
46+
47+
// 키 반환
48+
return new AuthKeyResponse(randomKey);
49+
}
50+
51+
/**
52+
* 회원가입 메서드
53+
*/
54+
@Transactional
55+
public SignUpResponse signUp(SignUpCommand signUpCommand) {
56+
// redis 에서 키 가져오기
57+
String key = redisUtil.getData(signUpCommand.getSolvedacName()).orElseThrow(() -> {
58+
throw new RuntimeException("인증 시간이 지났습니다.");
59+
});
60+
61+
// solved.ac 크롤링 해서 맞는지 확인하기
62+
String crawledKey = extractKey(signUpCommand.getSolvedacName());
63+
64+
if(!key.equals(crawledKey)) {
65+
throw new RuntimeException("solvedAC 인증에 실패했습니다.");
66+
}
67+
68+
// 이미 있는 회원인지 확인
69+
if(memberRepository.existsBySolvedacName(signUpCommand.getSolvedacName())) {
70+
throw new RuntimeException("이미 가입한 회원입니다.");
71+
}
72+
73+
// 아이디 중복 확인
74+
if(checkIdDuplication(signUpCommand.getId())) {
75+
throw new RuntimeException("아이디가 중복됩니다.");
76+
}
77+
78+
// 비밀번호 암호화
79+
String hashedPassword = passwordEncoder.encode(signUpCommand.getPassword());
80+
81+
// 유저 정보 저장
82+
Member member = Member.join(signUpCommand, hashedPassword);
83+
memberRepository.save(member);
84+
85+
// redis에서 키 삭제
86+
redisUtil.deleteData(signUpCommand.getSolvedacName());
87+
88+
return new SignUpResponse(member.getMemberId().toString());
89+
}
90+
91+
/**
92+
* 로그인 메서드
93+
*/
94+
@Transactional
95+
public AuthTokenResponse signIn(SignInCommand signInDto) {
96+
// 아이디 확인
97+
Member member = memberRepository.findById(signInDto.getId())
98+
.orElseThrow(()-> new RuntimeException("해당하는 유저가 없습니다."));
99+
100+
// 비밀번호 확인
101+
if(!passwordEncoder.matches(signInDto.getPassword(), member.getPassword())) {
102+
throw new RuntimeException("비밀번호가 불일치합니다.");
103+
}
104+
105+
// 토큰 발급
106+
AuthTokenResponse tokenResponse = jwtTokenHandler.generate(member);
107+
108+
// 리프레쉬 토큰 저장
109+
member.storeRefreshToken(tokenResponse.getRefreshToken());
110+
memberRepository.save(member);
111+
112+
// 토큰 리턴
113+
return tokenResponse;
114+
}
115+
116+
/**
117+
* 토큰 재발급 메서드
118+
*/
119+
@Transactional
120+
public AuthTokenResponse reissue(ReissueCommand reissueCommand) {
121+
// 멤버 존재 확인
122+
Member member = memberRepository.findByMemberId(reissueCommand.getMemberId())
123+
.orElseThrow(() -> new RuntimeException("해당하는 아이디가 없습니다."));
124+
125+
// 토큰 일치 확인
126+
validate(reissueCommand.getRefreshToken(), member);
127+
128+
// 토큰 재발급
129+
AuthTokenResponse tokenResponse = jwtTokenHandler.generate(member);
130+
log.info("Member[id: {}] token is reissued.", member.getId());
131+
132+
// 토큰 저장
133+
member.storeRefreshToken(tokenResponse.getRefreshToken());
134+
return tokenResponse;
135+
}
136+
137+
private void validate(String refreshToken, Member member) {
138+
// 유효한 리프레쉬인지 확인
139+
jwtTokenHandler.validateRefreshToken(refreshToken);
140+
141+
// 유저와 일치하는 리프레쉬토큰인지 확인
142+
if (!member.getRefreshToken().equals(refreshToken)) {
143+
throw new RuntimeException("리프레시 토큰이 일치하지 않습니다.");
144+
}
145+
}
146+
147+
public boolean checkIdDuplication(String id) {
148+
return memberRepository.existsById(id);
149+
}
150+
151+
/**
152+
* solvedac 크롤링으로 인증 확인하는 메서드
153+
*/
154+
public String extractKey(String solvedacName) {
155+
156+
// 크롬 드라이버 연결
157+
WebDriver driver = WebDriverUtil.getChromeDriver();
158+
159+
if(ObjectUtils.isEmpty(driver)) {
160+
throw new RuntimeException("셀레니움 연결 실패");
161+
}
162+
163+
// 유저 프로필 페이지 접속
164+
driver.get("https://solved.ac/profile/" + solvedacName);
165+
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
166+
167+
try {
168+
WebElement element = driver.findElement(By.cssSelector("#__next > div.css-1s1t70h > div.css-1948bce > div:nth-child(4) > div.css-0 > span"));
169+
return element.getText();
170+
} catch (Exception e) {
171+
throw new RuntimeException("존재하지 않는 solvedAC닉네임입니다.");
172+
} finally {
173+
WebDriverUtil.quit(driver);
174+
}
175+
}
176+
}

0 commit comments

Comments
 (0)