From 3f2a9bb47c370f601aee97f5b35a674121e57a51 Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 01:40:47 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[chore]=20security=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=9C=A0=EC=A0=B8?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20DTO=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++++++ .../com/wayble/server/user/dto/UserLoginRequestDto.java | 7 +++++++ 2 files changed, 13 insertions(+) create mode 100644 src/main/java/com/wayble/server/user/dto/UserLoginRequestDto.java diff --git a/build.gradle b/build.gradle index 3c51d5c5..093ae922 100644 --- a/build.gradle +++ b/build.gradle @@ -32,12 +32,18 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + runtimeOnly 'com.mysql:mysql-connector-j' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' testCompileOnly 'org.projectlombok:lombok' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/src/main/java/com/wayble/server/user/dto/UserLoginRequestDto.java b/src/main/java/com/wayble/server/user/dto/UserLoginRequestDto.java new file mode 100644 index 00000000..6fab2baf --- /dev/null +++ b/src/main/java/com/wayble/server/user/dto/UserLoginRequestDto.java @@ -0,0 +1,7 @@ +package com.wayble.server.user.dto; + +public record UserLoginRequestDto( + String name, // 이름 or 닉네임 + String email, + String password +) {} \ No newline at end of file From 156c10bb0f9a982730e610f7e520862941659955 Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 02:12:13 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[Feat]=20JWT=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EB=B0=8F=20Spring=20Security=20=EC=84=A4=EC=A0=95=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../server/common/config/SecurityConfig.java | 46 +++++++++++++++++++ .../common/config/security/JwtProperties.java | 29 ++++++++++++ .../security/jwt/JwtAuthentication.java | 18 ++++++++ .../security/jwt/JwtAuthenticationFilter.java | 37 +++++++++++++++ .../config/security/jwt/JwtTokenProvider.java | 44 ++++++++++++++++++ 6 files changed, 175 insertions(+) create mode 100644 src/main/java/com/wayble/server/common/config/SecurityConfig.java create mode 100644 src/main/java/com/wayble/server/common/config/security/JwtProperties.java create mode 100644 src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthentication.java create mode 100644 src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/wayble/server/common/config/security/jwt/JwtTokenProvider.java diff --git a/build.gradle b/build.gradle index 093ae922..5017018d 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' diff --git a/src/main/java/com/wayble/server/common/config/SecurityConfig.java b/src/main/java/com/wayble/server/common/config/SecurityConfig.java new file mode 100644 index 00000000..bcb45565 --- /dev/null +++ b/src/main/java/com/wayble/server/common/config/SecurityConfig.java @@ -0,0 +1,46 @@ +package com.wayble.server.common.config; + +import com.wayble.server.common.config.security.jwt.JwtAuthenticationFilter; +import com.wayble.server.common.config.security.jwt.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@RequiredArgsConstructor +@EnableMethodSecurity +public class SecurityConfig { + + private final JwtTokenProvider jwtProvider; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers( + "/api/v1/users/signup", + "/api/v1/users/login", + "/swagger-ui/**", + "/v3/api-docs/**" + ).permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/common/config/security/JwtProperties.java b/src/main/java/com/wayble/server/common/config/security/JwtProperties.java new file mode 100644 index 00000000..a1efd248 --- /dev/null +++ b/src/main/java/com/wayble/server/common/config/security/JwtProperties.java @@ -0,0 +1,29 @@ +package com.wayble.server.common.config.security; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "jwt") +public class JwtProperties { + + private String secret; + private long accessExp; + + + public String getSecret() { + return secret; + } + + public long getAccessExp() { + return accessExp; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public void setAccessExp(long accessExp) { + this.accessExp = accessExp; + } +} diff --git a/src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthentication.java b/src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthentication.java new file mode 100644 index 00000000..924eb60b --- /dev/null +++ b/src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthentication.java @@ -0,0 +1,18 @@ +package com.wayble.server.common.config.security.jwt; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import java.util.List; + +public class JwtAuthentication extends AbstractAuthenticationToken { + private final String email; + + public JwtAuthentication(String email) { + super(List.of(new SimpleGrantedAuthority("ROLE_USER"))); + this.email = email; + setAuthenticated(true); + } + + @Override public Object getCredentials() { return ""; } + @Override public Object getPrincipal() { return email; } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthenticationFilter.java b/src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthenticationFilter.java new file mode 100644 index 00000000..5e498596 --- /dev/null +++ b/src/main/java/com/wayble/server/common/config/security/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,37 @@ +package com.wayble.server.common.config.security.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtProvider; + + @Override + protected void doFilterInternal(HttpServletRequest req, + HttpServletResponse res, + FilterChain chain) throws ServletException, IOException { + + String header = req.getHeader("Authorization"); + + if (header != null && header.startsWith("Bearer ")) { + String token = header.substring(7); + + if (jwtProvider.validateToken(token)) { + String email = jwtProvider.getEmail(token); + var authentication = new JwtAuthentication(email); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + + chain.doFilter(req, res); + } +} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/common/config/security/jwt/JwtTokenProvider.java b/src/main/java/com/wayble/server/common/config/security/jwt/JwtTokenProvider.java new file mode 100644 index 00000000..ace9f67c --- /dev/null +++ b/src/main/java/com/wayble/server/common/config/security/jwt/JwtTokenProvider.java @@ -0,0 +1,44 @@ +package com.wayble.server.common.config.security.jwt; + +import com.wayble.server.common.config.security.JwtProperties; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; + +@Component +@RequiredArgsConstructor +public class JwtTokenProvider { + private final JwtProperties jwtProperties; + + private Key getSigningKey() { + return Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes()); + } + + public String generateToken(String email, String role) { + return Jwts.builder() + .setSubject(email) + .claim("role", role) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getAccessExp())) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public boolean validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + public String getEmail(String token) { + return Jwts.parserBuilder().setSigningKey(getSigningKey()).build() + .parseClaimsJws(token).getBody().getSubject(); + } +} From 4c257ed7b2762e52dac0d3e7e7faede8a66b6082 Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 02:18:06 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[feat]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=9A=94=EC=B2=AD=20DTO=EC=97=90=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/dto/UserRegisterRequestDto.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wayble/server/user/dto/UserRegisterRequestDto.java b/src/main/java/com/wayble/server/user/dto/UserRegisterRequestDto.java index 417306ca..0064628a 100644 --- a/src/main/java/com/wayble/server/user/dto/UserRegisterRequestDto.java +++ b/src/main/java/com/wayble/server/user/dto/UserRegisterRequestDto.java @@ -3,16 +3,36 @@ import com.wayble.server.user.entity.Gender; import com.wayble.server.user.entity.LoginType; import com.wayble.server.user.entity.UserType; +import jakarta.validation.constraints.*; import java.time.LocalDate; public record UserRegisterRequestDto( + @NotBlank(message = "닉네임은 필수입니다") + @Size(max = 8, message = "닉네임은 8자 이하여야 합니다") String nickname, + + @NotBlank(message = "사용자명은 필수입니다") String username, + + @NotBlank(message = "이메일은 필수입니다") + @Email(message = "유효한 이메일 형식이 아닙니다") String email, + + @NotBlank(message = "비밀번호는 필수입니다") + @Size(min = 8, message = "비밀번호는 8자 이상이어야 합니다") String password, + + @NotNull(message = "생년월일은 필수입니다") + @Past(message = "생년월일은 과거 날짜여야 합니다") LocalDate birthDate, + + @NotNull(message = "성별은 필수입니다") Gender gender, + + @NotNull(message = "로그인 타입은 필수입니다") LoginType loginType, + + @NotNull(message = "사용자 타입은 필수입니다") UserType userType -) {} +) {} \ No newline at end of file From 4c067df8e7bced2444341671f10a5e18cc7abe57 Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 02:22:14 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[refactor]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20DTO=EC=97=90=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wayble/server/user/dto/UserLoginRequestDto.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wayble/server/user/dto/UserLoginRequestDto.java b/src/main/java/com/wayble/server/user/dto/UserLoginRequestDto.java index 6fab2baf..9ad1bd39 100644 --- a/src/main/java/com/wayble/server/user/dto/UserLoginRequestDto.java +++ b/src/main/java/com/wayble/server/user/dto/UserLoginRequestDto.java @@ -1,7 +1,16 @@ package com.wayble.server.user.dto; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + public record UserLoginRequestDto( - String name, // 이름 or 닉네임 + @NotBlank(message = "이름 또는 닉네임은 필수입니다") + String name, + + @NotBlank(message = "이메일은 필수입니다") + @Email(message = "유효한 이메일 형식이 아닙니다") String email, + + @NotBlank(message = "비밀번호는 필수입니다") String password ) {} \ No newline at end of file From 62db0069c1afa657e39afd6060eaa96fa3a47d49 Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 16:22:32 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[feat]=20=ED=86=A0=ED=81=B0=20=EA=B0=92?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20DTO=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wayble/server/user/dto/token/TokenResponseDto.java | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/main/java/com/wayble/server/user/dto/token/TokenResponseDto.java diff --git a/src/main/java/com/wayble/server/user/dto/token/TokenResponseDto.java b/src/main/java/com/wayble/server/user/dto/token/TokenResponseDto.java new file mode 100644 index 00000000..57c54dbc --- /dev/null +++ b/src/main/java/com/wayble/server/user/dto/token/TokenResponseDto.java @@ -0,0 +1,3 @@ +package com.wayble.server.user.dto.token; + +public record TokenResponseDto(String accessToken) {} \ No newline at end of file From bc1933309365311be502c3bd3353fc7d21f8f796 Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 16:28:07 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[feat]=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20=EC=A0=95=EC=A0=81=20=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wayble/server/user/entity/User.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/com/wayble/server/user/entity/User.java b/src/main/java/com/wayble/server/user/entity/User.java index fd630d0f..1a19eefa 100644 --- a/src/main/java/com/wayble/server/user/entity/User.java +++ b/src/main/java/com/wayble/server/user/entity/User.java @@ -64,4 +64,26 @@ public class User extends BaseEntity { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List userPlaces = new ArrayList<>(); + + public static User createUser( + String nickname, + String username, + String email, + String password, + LocalDate birthDate, + Gender gender, + LoginType loginType, + UserType userType + ) { + return User.builder() + .nickname(nickname) + .username(username) + .email(email) + .password(password) + .birthDate(birthDate) + .gender(gender) + .loginType(loginType) + .userType(userType) + .build(); + } } From c474bc0b960d0e02723c6737f64b4364180bc759 Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 16:32:28 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[feat]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/user/exception/UserErrorCase.java | 3 ++- .../server/user/service/UserService.java | 24 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/wayble/server/user/exception/UserErrorCase.java b/src/main/java/com/wayble/server/user/exception/UserErrorCase.java index 0bb69bf3..c7a3180b 100644 --- a/src/main/java/com/wayble/server/user/exception/UserErrorCase.java +++ b/src/main/java/com/wayble/server/user/exception/UserErrorCase.java @@ -11,7 +11,8 @@ public enum UserErrorCase implements ErrorCase { USER_NOT_FOUND(404, 1001, "사용자를 찾을 수 없습니다."), WAYBLE_ZONE_NOT_FOUND(404, 1002, "해당 웨이블존을 찾을 수 없습니다."), PLACE_ALREADY_SAVED(400, 1003, "이미 저장한 장소입니다."), - INVALID_USER_ID(400, 1004, "요청 경로의 유저 ID와 바디의 유저 ID가 일치하지 않습니다."); + INVALID_USER_ID(400, 1004, "요청 경로의 유저 ID와 바디의 유저 ID가 일치하지 않습니다."), + USER_ALREADY_EXISTS(400, 1005, "이미 존재하는 회원입니다."); private final Integer httpStatusCode; private final Integer errorCode; diff --git a/src/main/java/com/wayble/server/user/service/UserService.java b/src/main/java/com/wayble/server/user/service/UserService.java index f99c05f2..afbf2390 100644 --- a/src/main/java/com/wayble/server/user/service/UserService.java +++ b/src/main/java/com/wayble/server/user/service/UserService.java @@ -1,9 +1,12 @@ package com.wayble.server.user.service; import com.wayble.server.common.exception.ApplicationException; +import com.wayble.server.user.dto.UserRegisterRequestDto; +import com.wayble.server.user.entity.User; import com.wayble.server.user.exception.UserErrorCase; import com.wayble.server.user.repository.UserRepository; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service @@ -11,8 +14,27 @@ public class UserService { private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + // 회원가입 + public void signup(UserRegisterRequestDto req) { + if (userRepository.existsByEmail(req.email())) { + throw new ApplicationException(UserErrorCase.USER_ALREADY_EXISTS); + } + User user = User.createUser( + req.nickname(), + req.username(), + req.email(), + passwordEncoder.encode(req.password()), + req.birthDate(), + req.gender(), + req.loginType(), + req.userType() + ); + userRepository.save(user); + } public void makeException() { throw new ApplicationException(UserErrorCase.USER_NOT_FOUND); } -} +} \ No newline at end of file From f8b4b5a8a92e4527ca58cecd5d406b1492ce2b87 Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 16:36:47 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[feat]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/user/exception/UserErrorCase.java | 3 +- .../server/user/service/auth/AuthService.java | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/wayble/server/user/service/auth/AuthService.java diff --git a/src/main/java/com/wayble/server/user/exception/UserErrorCase.java b/src/main/java/com/wayble/server/user/exception/UserErrorCase.java index c7a3180b..6398394b 100644 --- a/src/main/java/com/wayble/server/user/exception/UserErrorCase.java +++ b/src/main/java/com/wayble/server/user/exception/UserErrorCase.java @@ -12,7 +12,8 @@ public enum UserErrorCase implements ErrorCase { WAYBLE_ZONE_NOT_FOUND(404, 1002, "해당 웨이블존을 찾을 수 없습니다."), PLACE_ALREADY_SAVED(400, 1003, "이미 저장한 장소입니다."), INVALID_USER_ID(400, 1004, "요청 경로의 유저 ID와 바디의 유저 ID가 일치하지 않습니다."), - USER_ALREADY_EXISTS(400, 1005, "이미 존재하는 회원입니다."); + USER_ALREADY_EXISTS(400, 1005, "이미 존재하는 회원입니다."), + INVALID_CREDENTIALS(400, 1006, "아이디 혹은 비밀번호가 잘못되었습니다."); private final Integer httpStatusCode; private final Integer errorCode; diff --git a/src/main/java/com/wayble/server/user/service/auth/AuthService.java b/src/main/java/com/wayble/server/user/service/auth/AuthService.java new file mode 100644 index 00000000..7e1bf049 --- /dev/null +++ b/src/main/java/com/wayble/server/user/service/auth/AuthService.java @@ -0,0 +1,31 @@ +package com.wayble.server.user.service.auth; + +import com.wayble.server.common.config.security.jwt.JwtTokenProvider; +import com.wayble.server.common.exception.ApplicationException; +import com.wayble.server.user.dto.UserLoginRequestDto; +import com.wayble.server.user.dto.token.TokenResponseDto; +import com.wayble.server.user.entity.User; +import com.wayble.server.user.exception.UserErrorCase; +import com.wayble.server.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final UserRepository userRepository; + private final PasswordEncoder encoder; + private final JwtTokenProvider jwtProvider; + + public TokenResponseDto login(UserLoginRequestDto req) { + User user = userRepository.findByEmail(req.email()) + .orElseThrow(() -> new ApplicationException(UserErrorCase.INVALID_CREDENTIALS)); + if (!encoder.matches(req.password(), user.getPassword())) { + throw new ApplicationException(UserErrorCase.INVALID_CREDENTIALS); + } + String token = jwtProvider.generateToken(user.getEmail(), user.getUserType().name()); + return new TokenResponseDto(token); + } +} From 823e5cc03619af6f54fafea08852faa7c7c96f0c Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 16:49:21 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[feat]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/wayble/server/user/controller/UserController.java b/src/main/java/com/wayble/server/user/controller/UserController.java index 0c875733..13f48aa9 100644 --- a/src/main/java/com/wayble/server/user/controller/UserController.java +++ b/src/main/java/com/wayble/server/user/controller/UserController.java @@ -1,29 +1,59 @@ package com.wayble.server.user.controller; import com.wayble.server.common.response.CommonResponse; +import com.wayble.server.user.dto.UserLoginRequestDto; +import com.wayble.server.user.dto.UserRegisterRequestDto; import com.wayble.server.user.service.UserService; +import com.wayble.server.user.service.auth.AuthService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; @RestController +@RequestMapping("/api/v1/users") @RequiredArgsConstructor -@RequestMapping("/users") +@Validated public class UserController { private final UserService userService; + private final AuthService authService; - // 참고용 컨트롤러(지우셔도 돼요) - @GetMapping("/hello") - public CommonResponse hello() { - return CommonResponse.success("hello"); + @PostMapping("/signup") + @Operation( + summary = "유저 회원가입", + description = "신규 유저 회원가입을 수행합니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "회원가입 성공", + content = @Content(schema = @Schema(implementation = com.wayble.server.common.response.CommonResponse.class))), + @ApiResponse(responseCode = "400", description = "이미 존재하는 회원", + content = @Content(schema = @Schema(implementation = com.wayble.server.common.response.CommonResponse.class))) + }) + public CommonResponse signup(@RequestBody @Valid UserRegisterRequestDto req) { + userService.signup(req); + return CommonResponse.success("회원가입 성공"); } - // 예외 사용 참고용 컨트롤러(지우셔도 돼요) - @GetMapping("/ex") - public CommonResponse exception() { - userService.makeException(); - return CommonResponse.success("예외 발생!"); + @PostMapping("/login") + @Operation( + summary = "유저 로그인", + description = "이메일+비밀번호 로그인 및 JWT 발급" + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "로그인 성공", + content = @Content(schema = @Schema(implementation = com.wayble.server.common.response.CommonResponse.class))), + @ApiResponse(responseCode = "400", description = "아이디 혹은 비밀번호가 잘못됨", + content = @Content(schema = @Schema(implementation = com.wayble.server.common.response.CommonResponse.class))) + }) + public CommonResponse login(@RequestBody @Valid UserLoginRequestDto req) { + authService.login(req); + return CommonResponse.success("로그인 성공!"); } + } From 1896d58408f9cf0b770418668c723b10949f4b32 Mon Sep 17 00:00:00 2001 From: seungin Date: Thu, 17 Jul 2025 22:48:28 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[fix]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EB=B0=98=ED=99=98=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=91=EB=8B=B5=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wayble/server/common/config/SecurityConfig.java | 2 ++ .../com/wayble/server/user/controller/UserController.java | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/wayble/server/common/config/SecurityConfig.java b/src/main/java/com/wayble/server/common/config/SecurityConfig.java index bcb45565..9600ebf1 100644 --- a/src/main/java/com/wayble/server/common/config/SecurityConfig.java +++ b/src/main/java/com/wayble/server/common/config/SecurityConfig.java @@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @@ -31,6 +32,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth + .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers( "/api/v1/users/signup", "/api/v1/users/login", diff --git a/src/main/java/com/wayble/server/user/controller/UserController.java b/src/main/java/com/wayble/server/user/controller/UserController.java index 13f48aa9..01b7e6db 100644 --- a/src/main/java/com/wayble/server/user/controller/UserController.java +++ b/src/main/java/com/wayble/server/user/controller/UserController.java @@ -3,6 +3,7 @@ import com.wayble.server.common.response.CommonResponse; import com.wayble.server.user.dto.UserLoginRequestDto; import com.wayble.server.user.dto.UserRegisterRequestDto; +import com.wayble.server.user.dto.token.TokenResponseDto; import com.wayble.server.user.service.UserService; import com.wayble.server.user.service.auth.AuthService; import io.swagger.v3.oas.annotations.Operation; @@ -51,9 +52,9 @@ public CommonResponse signup(@RequestBody @Valid UserRegisterRequestDto @ApiResponse(responseCode = "400", description = "아이디 혹은 비밀번호가 잘못됨", content = @Content(schema = @Schema(implementation = com.wayble.server.common.response.CommonResponse.class))) }) - public CommonResponse login(@RequestBody @Valid UserLoginRequestDto req) { - authService.login(req); - return CommonResponse.success("로그인 성공!"); + public CommonResponse login(@RequestBody @Valid UserLoginRequestDto req) { + TokenResponseDto tokenDto = authService.login(req); + return CommonResponse.success(tokenDto); } }