Skip to content

Commit bdcbc6e

Browse files
authored
Merge pull request #35 from classic-daramg/feature/34
Feature/34
2 parents 30d922a + 338734c commit bdcbc6e

37 files changed

+1307
-126
lines changed

โ€Ž.github/workflows/ci.ymlโ€Ž

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,16 @@ jobs:
1111
runs-on: ubuntu-latest
1212
permissions:
1313
contents: write
14-
14+
services:
15+
redis:
16+
image: redis:latest
17+
ports:
18+
- 6379:6379
19+
options: >-
20+
--health-cmd "redis-cli ping | grep PONG || exit 1"
21+
--health-interval 5s
22+
--health-timeout 3s
23+
--health-retries 5
1524
steps:
1625
- uses: actions/checkout@v4
1726
- name: Set up JDK 22
@@ -24,6 +33,10 @@ jobs:
2433
uses: gradle/actions/setup-gradle@v3
2534

2635
- name: Test with CI Profile
36+
env:
37+
JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
38+
SPRING_MAIL_USERNAME: ${{ secrets.SPRING_MAIL_USERNAME }}
39+
SPRING_MAIL_PASSWORD: ${{ secrets.SPRING_MAIL_PASSWORD }}
2740
run: ./gradlew build -Dspring.profiles.active=ci
2841

2942
- name: Submit Dependency Graph

โ€Žbuild.gradleโ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ dependencies {
6767

6868
// ์ฝ”ํ‹€๋ฆฐ
6969
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
70+
71+
// jwt
72+
implementation 'com.auth0:java-jwt:4.4.0'
7073
}
7174

7275
test {

โ€Žsrc/main/java/com/daramg/server/ServerApplication.javaโ€Ž

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
55
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
6-
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
76

8-
// ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์ž„์‹œ์„ค์ •
97
@SpringBootApplication(exclude = {
10-
FlywayAutoConfiguration.class,
11-
SecurityAutoConfiguration.class
8+
FlywayAutoConfiguration.class
129
})
1310
public class ServerApplication {
1411
public static void main(String[] args) {

โ€Žsrc/main/java/com/daramg/server/auth/application/AuthService.javaโ€Ž

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,39 @@
11
package com.daramg.server.auth.application;
22

3+
import com.daramg.server.auth.dto.TokenResponseDto;
4+
import com.daramg.server.auth.exception.AuthErrorStatus;
5+
import com.daramg.server.auth.util.JwtUtil;
36
import com.daramg.server.common.exception.BusinessException;
47
import com.daramg.server.domain.user.domain.User;
58
import com.daramg.server.auth.domain.SignupVo;
6-
import com.daramg.server.auth.dto.LoginDto;
7-
import com.daramg.server.auth.dto.PasswordDto;
8-
import com.daramg.server.auth.dto.SignupDto;
9+
import com.daramg.server.auth.dto.LoginRequestDto;
10+
import com.daramg.server.auth.dto.PasswordRequestDto;
11+
import com.daramg.server.auth.dto.SignupRequestDto;
912
import com.daramg.server.domain.user.repository.UserRepository;
1013
import lombok.RequiredArgsConstructor;
14+
import org.springframework.beans.factory.annotation.Value;
15+
import org.springframework.data.redis.core.RedisTemplate;
16+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
17+
import org.springframework.security.crypto.password.PasswordEncoder;
1118
import org.springframework.stereotype.Service;
1219
import org.springframework.transaction.annotation.Transactional;
1320

21+
import java.util.concurrent.TimeUnit;
22+
1423
@Service
1524
@Transactional
1625
@RequiredArgsConstructor
1726
public class AuthService {
1827

28+
@Value("${jwt.refresh-time}")
29+
private long REFRESH_TOKEN_VALID_TIME;
30+
1931
private final UserRepository userRepository;
32+
private final JwtUtil jwtTokenProvider;
33+
private final PasswordEncoder passwordEncoder;
34+
private final RedisTemplate<String, String> redisTemplate;
2035

21-
public void signup(SignupDto dto){
36+
public void signup(SignupRequestDto dto){
2237
if (userRepository.existsByEmail(dto.getEmail())) {
2338
throw new BusinessException("์ค‘๋ณต๋œ ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค.");
2439
}
@@ -39,10 +54,54 @@ public void signup(SignupDto dto){
3954
userRepository.save(user);
4055
}
4156

42-
public void login(LoginDto dto){
57+
public TokenResponseDto login(LoginRequestDto dto){
58+
User user = userRepository.findByEmail(dto.getEmail())
59+
.filter(u -> passwordEncoder.matches(dto.getPassword(), u.getPassword()))
60+
.orElseThrow(() -> new BusinessException(AuthErrorStatus.USER_NOT_FOUND_EXCEPTION));
61+
62+
TokenResponseDto tokens = jwtTokenProvider.generateTokens(user);
63+
redisTemplate.opsForValue().set(
64+
user.getEmail(),
65+
tokens.getRefreshToken(),
66+
REFRESH_TOKEN_VALID_TIME,
67+
TimeUnit.MILLISECONDS
68+
);
69+
return tokens;
4370
}
4471

45-
public void resetPassword(PasswordDto dto, User user){
72+
public TokenResponseDto refreshAccessToken(String refreshToken) {
73+
if (!jwtTokenProvider.validateRefreshToken(refreshToken)) {
74+
throw new BusinessException(AuthErrorStatus.INVALID_TOKEN_EXCEPTION);
75+
}
76+
77+
String userEmail = jwtTokenProvider.getUserEmail(refreshToken);
78+
String originRefreshToken = redisTemplate.opsForValue().get(userEmail);
79+
80+
if (originRefreshToken == null || !originRefreshToken.equals(refreshToken)) {
81+
throw new BusinessException(AuthErrorStatus.INVALID_TOKEN_EXCEPTION);
82+
}
83+
84+
User user = userRepository.findByEmail(userEmail)
85+
.orElseThrow(() -> new BusinessException(AuthErrorStatus.USER_NOT_FOUND_EXCEPTION));
86+
87+
String newAccessToken = jwtTokenProvider.createAccessToken(user);
88+
return new TokenResponseDto(newAccessToken, refreshToken);
89+
}
90+
91+
public void logout(User user){
92+
redisTemplate.delete(user.getEmail());
93+
}
94+
95+
public void resetPassword(PasswordRequestDto dto){
96+
User user = userRepository.findByEmail(dto.getEmail())
97+
.orElseThrow(() -> new BusinessException(AuthErrorStatus.USER_NOT_FOUND_EXCEPTION));
98+
4699
user.changePassword(dto.getPassword());
100+
logout(user);
101+
}
102+
103+
public User loadUserByEmail(String email) throws UsernameNotFoundException {
104+
return userRepository.findByEmail(email)
105+
.orElseThrow(() -> new BusinessException("(email: " + email + ")", AuthErrorStatus.USER_NOT_FOUND_EXCEPTION));
47106
}
48107
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.daramg.server.auth.application;
22

3-
import com.daramg.server.auth.dto.EmailVerificationRequest;
4-
import com.daramg.server.auth.dto.CodeVerificationRequest;
3+
import com.daramg.server.auth.dto.EmailVerificationRequestDto;
4+
import com.daramg.server.auth.dto.CodeVerificationRequestDto;
55

66
public interface MailVerificationService {
7-
void sendVerificationEmail(EmailVerificationRequest request);
8-
void verifyEmailWithCode(CodeVerificationRequest request);
7+
void sendVerificationEmail(EmailVerificationRequestDto request);
8+
void verifyEmailWithCode(CodeVerificationRequestDto request);
99
}

โ€Žsrc/main/java/com/daramg/server/auth/application/MailVerificationServiceImpl.javaโ€Ž

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.daramg.server.auth.application;
22

3-
import com.daramg.server.auth.MailMessages;
4-
import com.daramg.server.auth.dto.EmailVerificationRequest;
5-
import com.daramg.server.auth.dto.CodeVerificationRequest;
3+
import com.daramg.server.auth.domain.MailMessages;
4+
import com.daramg.server.auth.dto.EmailVerificationRequestDto;
5+
import com.daramg.server.auth.dto.CodeVerificationRequestDto;
66
import com.daramg.server.auth.exception.AuthErrorStatus;
77
import com.daramg.server.auth.repository.VerificationCodeRepository;
88
import com.daramg.server.auth.util.MailContentBuilder;
@@ -26,26 +26,26 @@ public class MailVerificationServiceImpl implements MailVerificationService{
2626
private final VerificationCodeRepository verificationCodeRepository;
2727
private final UserRepository userRepository;
2828

29-
public void sendVerificationEmail(EmailVerificationRequest request) {
29+
public void sendVerificationEmail(EmailVerificationRequestDto request) {
3030
switch (request.getEmailPurpose()) {
3131
case SIGNUP -> sendForSignup(request);
3232
case PASSWORD_RESET -> sendForPasswordReset(request);
3333
default -> throw new BusinessException("์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ด๋ฉ”์ผ ๋ฐœ์†ก ๋ชฉ์ ์ž…๋‹ˆ๋‹ค.");
3434
}
3535
}
3636

37-
private void sendForSignup(EmailVerificationRequest request) {
37+
private void sendForSignup(EmailVerificationRequestDto request) {
3838
sendVerificationCode(request);
3939
}
4040

41-
private void sendForPasswordReset(EmailVerificationRequest request) {
41+
private void sendForPasswordReset(EmailVerificationRequestDto request) {
4242
if (!userRepository.existsByEmail(request.getEmail())){
4343
throw new BusinessException(AuthErrorStatus.EMAIL_NOT_REGISTERED);
4444
}
4545
sendVerificationCode(request);
4646
}
4747

48-
private void sendVerificationCode(EmailVerificationRequest request) {
48+
private void sendVerificationCode(EmailVerificationRequestDto request) {
4949
String verificationCode = VerificationCodeGenerator.generate();
5050
verificationCodeRepository.save(request.getEmail(), verificationCode);
5151

@@ -63,7 +63,7 @@ private void sendVerificationCode(EmailVerificationRequest request) {
6363
}
6464
}
6565

66-
public void verifyEmailWithCode(CodeVerificationRequest request) {
66+
public void verifyEmailWithCode(CodeVerificationRequestDto request) {
6767
String storedCode = verificationCodeRepository.findByEmail(request.getEmail())
6868
.orElse(null);
6969

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.daramg.server.auth.config;
2+
3+
import com.daramg.server.auth.filter.JwtAuthorizationFilter;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
9+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
10+
import org.springframework.security.config.http.SessionCreationPolicy;
11+
import org.springframework.security.web.AuthenticationEntryPoint;
12+
import org.springframework.security.web.SecurityFilterChain;
13+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
14+
15+
@Configuration
16+
@EnableWebSecurity
17+
@RequiredArgsConstructor
18+
public class SecurityConfig {
19+
20+
private final JwtAuthorizationFilter jwtAuthorizationFilter;
21+
private final AuthenticationEntryPoint authEntryPoint;
22+
23+
@Bean
24+
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
25+
http
26+
.csrf(AbstractHttpConfigurer::disable)
27+
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
28+
.authorizeHttpRequests(auth -> auth
29+
// ์ธ์ฆ ์—†์ด ํ—ˆ์šฉํ•  ๊ฒฝ๋กœ
30+
.requestMatchers("/auth/email-verifications").permitAll()
31+
.requestMatchers("/auth/verify-email").permitAll()
32+
.requestMatchers("/auth/signup").permitAll()
33+
.requestMatchers("/auth/login").permitAll()
34+
35+
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
36+
37+
// ์œ„์—์„œ ๋“ฑ๋ก๋˜์ง€ ์•Š์€ ๋ชจ๋“  ๊ฒฝ๋กœ๋Š” ์ธ์ฆ ํ•„์š”
38+
.anyRequest().authenticated()
39+
)
40+
.httpBasic(basic -> basic.authenticationEntryPoint(authEntryPoint))
41+
.addFilterBefore(jwtAuthorizationFilter,
42+
UsernamePasswordAuthenticationFilter.class);
43+
return http.build();
44+
}
45+
}

src/main/java/com/daramg/server/auth/MailMessages.java renamed to src/main/java/com/daramg/server/auth/domain/MailMessages.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.daramg.server.auth;
1+
package com.daramg.server.auth.domain;
22

33
public final class MailMessages {
44
private MailMessages() {}

src/main/java/com/daramg/server/auth/dto/CodeVerificationRequest.kt renamed to src/main/java/com/daramg/server/auth/dto/CodeVerificationRequestDto.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import jakarta.validation.constraints.Email
44
import jakarta.validation.constraints.NotBlank
55
import jakarta.validation.constraints.Pattern
66

7-
data class CodeVerificationRequest(
7+
data class CodeVerificationRequestDto(
88
@field:NotBlank(message = "AUTH_400_2")
99
@field:Email(message = "AUTH_400_3")
1010
val email: String,

src/main/java/com/daramg/server/auth/dto/EmailVerificationRequest.kt renamed to src/main/java/com/daramg/server/auth/dto/EmailVerificationRequestDto.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import jakarta.validation.constraints.Email
55
import jakarta.validation.constraints.NotBlank
66
import jakarta.validation.constraints.NotNull
77

8-
data class EmailVerificationRequest(
8+
data class EmailVerificationRequestDto(
99
@field:NotBlank(message = "AUTH_400_2")
1010
@field:Email(message = "AUTH_400_3")
1111
val email: String,

0 commit comments

Comments
ย (0)