diff --git a/src/main/java/com/back/together02be/asset/controller/AssetController.java b/src/main/java/com/back/together02be/asset/controller/AssetController.java index 3fbe491..70aa1b2 100644 --- a/src/main/java/com/back/together02be/asset/controller/AssetController.java +++ b/src/main/java/com/back/together02be/asset/controller/AssetController.java @@ -3,11 +3,13 @@ import com.back.together02be.asset.dto.response.UserStockRes; import com.back.together02be.asset.service.AssetService; import com.back.together02be.global.apiRes.ApiRes; +import com.back.together02be.global.security.SecurityUser; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -21,9 +23,10 @@ public class AssetController { private final AssetService assetService; - @GetMapping("/stocks/{userId}") - public ResponseEntity>> getUserStocks(@PathVariable Long userId) { - List userStocks = assetService.getUserStocks(userId); + @GetMapping("/stocks") + @Operation(summary = "보유 종목 조회") + public ResponseEntity>> getUserStocks(@AuthenticationPrincipal SecurityUser user) { + List userStocks = assetService.getUserStocks(user.getId()); return ResponseEntity.ok(new ApiRes<>("보유 종목 조회 성공", userStocks)); } } diff --git a/src/main/java/com/back/together02be/global/config/CorsConfig.java b/src/main/java/com/back/together02be/global/config/CorsConfig.java deleted file mode 100644 index 8d7e2de..0000000 --- a/src/main/java/com/back/together02be/global/config/CorsConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.back.together02be.global.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class CorsConfig implements WebMvcConfigurer { - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOriginPatterns("*") - .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") - .allowedHeaders("*") - .allowCredentials(true); - } -} diff --git a/src/main/java/com/back/together02be/global/config/SecurityConfig.java b/src/main/java/com/back/together02be/global/config/SecurityConfig.java deleted file mode 100644 index de0d783..0000000 --- a/src/main/java/com/back/together02be/global/config/SecurityConfig.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.back.together02be.global.config; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -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; - -@Configuration -@EnableWebSecurity -@RequiredArgsConstructor -public class SecurityConfig { - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .csrf(AbstractHttpConfigurer::disable) // CSRF 비활성화 - .sessionManagement(session -> // 모든 세션 Stateless - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) - ) - .authorizeHttpRequests(auth -> auth // 모든 URL 허용 - .anyRequest().permitAll() - ); - - return http.build(); - } -} diff --git a/src/main/java/com/back/together02be/global/initData/BaseInitData.java b/src/main/java/com/back/together02be/global/initData/BaseInitData.java index 60ae5cb..64915e0 100644 --- a/src/main/java/com/back/together02be/global/initData/BaseInitData.java +++ b/src/main/java/com/back/together02be/global/initData/BaseInitData.java @@ -16,6 +16,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; @Configuration @@ -26,6 +27,7 @@ public class BaseInitData { private final UsersRepository usersRepository; private final StockRepository stockRepository; private final UserAccountRepository userAccountRepository; + private final PasswordEncoder passwordEncoder; @Autowired @Lazy @@ -45,19 +47,19 @@ public ApplicationRunner initData() { @Transactional public void work1() { // 테스트용 시드 데이터 (H2 dev 환경 전용) - Users user = usersRepository.save(new Users("testuser", "password", "테스트유저")); + Users user = usersRepository.save(new Users("testuser", passwordEncoder.encode("password1234"), "테스트유저")); userAccountRepository.save(new UserAccount(user, 0L, 50_000_000L)); } @Transactional public void work2() { - if (usersService.count() > 0) { - return; - } + if (usersService.count() > 0) { + return; + } - usersService.signup(new SignupReq("user1", "1234", "1234", "유저1")); - usersService.signup(new SignupReq("user2", "1234", "1234", "유저2")); - usersService.signup(new SignupReq("user3", "1234", "1234", "유저3")); + usersService.signup(new SignupReq("user1", "password01", "password01", "유저1")); + usersService.signup(new SignupReq("user2", "password02", "password02", "유저2")); + usersService.signup(new SignupReq("user3", "password03", "password03", "유저3")); } @Transactional diff --git a/src/main/java/com/back/together02be/global/security/CustomAuthenticationFilter.java b/src/main/java/com/back/together02be/global/security/CustomAuthenticationFilter.java new file mode 100644 index 0000000..b4350d4 --- /dev/null +++ b/src/main/java/com/back/together02be/global/security/CustomAuthenticationFilter.java @@ -0,0 +1,66 @@ +package com.back.together02be.global.security; + +import com.back.together02be.global.util.JwtUtil; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class CustomAuthenticationFilter extends OncePerRequestFilter { + + private final CustomUserDetailsService userDetailsService; + + @Value("${jwt.secret}") + private String jwtSecret; + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + + // 헤더에서 토큰 꺼냄 + String authHeader = request.getHeader("Authorization"); + + // 토큰 없으면 그냥 통과 + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + // "Bearer eyJhbGciOiJIUzI1NiJ9..." -> 7부터 토큰 + String token = authHeader.substring(7); + + Map payload = JwtUtil.payloadOrNull(token, jwtSecret); + + // JWT 검증 후 유효하면 Security Context에 저장 + if (payload != null) { + String username = (String) payload.get("username"); + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + Authentication authentication = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/back/together02be/global/security/CustomUserDetailsService.java b/src/main/java/com/back/together02be/global/security/CustomUserDetailsService.java new file mode 100644 index 0000000..f386218 --- /dev/null +++ b/src/main/java/com/back/together02be/global/security/CustomUserDetailsService.java @@ -0,0 +1,32 @@ +package com.back.together02be.global.security; + +import com.back.together02be.users.entity.Users; +import com.back.together02be.users.repository.UsersRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final UsersRepository usersRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Users user = usersRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("존재하지 않는 아이디입니다: " + username)); + + return new SecurityUser( + user.getId(), + user.getUsername(), + user.getPassword(), + user.getNickname(), + List.of() + ); + } +} diff --git a/src/main/java/com/back/together02be/global/security/SecurityConfig.java b/src/main/java/com/back/together02be/global/security/SecurityConfig.java new file mode 100644 index 0000000..6f98c8e --- /dev/null +++ b/src/main/java/com/back/together02be/global/security/SecurityConfig.java @@ -0,0 +1,96 @@ +package com.back.together02be.global.security; + +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.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +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; +import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final CustomAuthenticationFilter jwtAuthFilter; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + .authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests + .requestMatchers("/favicon.ico").permitAll() + .requestMatchers("/h2-console/**").permitAll() + .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers(HttpMethod.POST, + "/api/users/signup", + "/api/users/login", + "/api/users/token" + ) + .permitAll() + .requestMatchers(HttpMethod.POST, "/api/users/logout").authenticated() + .anyRequest().authenticated() + ) + + .headers(headers -> headers + .addHeaderWriter(new XFrameOptionsHeaderWriter( + XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN) + ) + ) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(exceptionHandling -> exceptionHandling + .authenticationEntryPoint((request, response, authenticationException) -> { + response.setContentType("application/json; charset=UTF-8"); + response.setStatus(401); + response.getWriter().write( + """ + {"message": "로그인 후 이용해주세요.", "data": null} + """ + ); + }) + .accessDeniedHandler((request, response, authenticationException) -> { + response.setContentType("application/json; charset=UTF-8"); + response.setStatus(403); + response.getWriter().write( + """ + {"message": "권한이 없습니다.", "data": null} + """ + ); + }) + ); + return http.build(); + } + + // 기존의 CORS 설정을 옮김 + @Bean + public UrlBasedCorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOriginPatterns(List.of("*")); + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} diff --git a/src/main/java/com/back/together02be/global/security/SecurityUser.java b/src/main/java/com/back/together02be/global/security/SecurityUser.java new file mode 100644 index 0000000..2d5fb35 --- /dev/null +++ b/src/main/java/com/back/together02be/global/security/SecurityUser.java @@ -0,0 +1,26 @@ +package com.back.together02be.global.security; + +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; + +import java.util.Collection; + +@Getter +public class SecurityUser extends User { + + private final Long id; + private final String nickname; + + public SecurityUser( + Long id, + String username, + String password, + String nickname, + Collection authorities + ) { + super(username, password, authorities); + this.id = id; + this.nickname = nickname; + } +} diff --git a/src/main/java/com/back/together02be/global/springDoc/SpringDoc.java b/src/main/java/com/back/together02be/global/springDoc/SpringDoc.java index 98001e1..81d06f2 100644 --- a/src/main/java/com/back/together02be/global/springDoc/SpringDoc.java +++ b/src/main/java/com/back/together02be/global/springDoc/SpringDoc.java @@ -1,14 +1,25 @@ package com.back.together02be.global.springDoc; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.security.SecurityScheme; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.info.Info; - @Configuration -@OpenAPIDefinition(info = @Info(title = "모의투자 투게더 API", version = "beta", description = "2차 프로젝트 API")) +@OpenAPIDefinition( + info = @Info(title = "모의투자 투게더 API", version = "beta", description = "2차 프로젝트 API"), + security = @SecurityRequirement(name = "bearerAuth") +) +@SecurityScheme( + name = "bearerAuth", + type = SecuritySchemeType.HTTP, + scheme = "bearer", + bearerFormat = "JWT" +) public class SpringDoc { @Bean @@ -28,11 +39,18 @@ public GroupedOpenApi usersApi() { } @Bean - public GroupedOpenApi assetApi() { // 예시 + public GroupedOpenApi tradeApi() { + return GroupedOpenApi.builder() + .group("거래 API") + .pathsToMatch("/api/trades/**") + .build(); + } + + @Bean + public GroupedOpenApi assetApi() { return GroupedOpenApi.builder() .group("보유 자산 조회 API") .pathsToMatch("/api/asset/stocks/**") .build(); } - } \ No newline at end of file diff --git a/src/main/java/com/back/together02be/stock/controller/StockController.java b/src/main/java/com/back/together02be/stock/controller/StockController.java index b271922..e066d55 100644 --- a/src/main/java/com/back/together02be/stock/controller/StockController.java +++ b/src/main/java/com/back/together02be/stock/controller/StockController.java @@ -14,7 +14,6 @@ import java.util.List; -@CrossOrigin(origins = "http://localhost:3000") @RestController @RequiredArgsConstructor @RequestMapping("/api/stocks") diff --git a/src/main/java/com/back/together02be/trade/controller/TradeController.java b/src/main/java/com/back/together02be/trade/controller/TradeController.java index 2175969..fbc49ea 100644 --- a/src/main/java/com/back/together02be/trade/controller/TradeController.java +++ b/src/main/java/com/back/together02be/trade/controller/TradeController.java @@ -1,6 +1,7 @@ package com.back.together02be.trade.controller; import com.back.together02be.global.apiRes.ApiRes; +import com.back.together02be.global.security.SecurityUser; import com.back.together02be.trade.dto.BuyReq; import com.back.together02be.trade.dto.BuyRes; import com.back.together02be.trade.service.TradeService; @@ -10,12 +11,8 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -25,19 +22,15 @@ public class TradeController { private final TradeService tradeService; - /** - * TR-01 매수 - * TODO: Spring Security 적용 후 @AuthenticationPrincipal로 userId 주입 예정 - */ @Operation(summary = "주식 매수", description = "실시간 현재가 기준으로 매수를 처리합니다. X-Idempotency-Key 헤더 필수.") @PostMapping("/buy") public ResponseEntity> buy( + @AuthenticationPrincipal SecurityUser user, @Parameter(description = "중복 요청 방지용 UUID (버튼 클릭마다 새로 생성)", required = true) @RequestHeader("X-Idempotency-Key") String idempotencyKey, - @RequestParam Long userId, // TODO: Security 적용 후 제거 @Valid @RequestBody BuyReq request ) { - BuyRes response = tradeService.buy(userId, idempotencyKey, request); + BuyRes response = tradeService.buy(user.getId(), idempotencyKey, request); return ResponseEntity.ok(new ApiRes<>("매수가 완료되었습니다.", response)); } } diff --git a/src/main/java/com/back/together02be/users/controller/UsersController.java b/src/main/java/com/back/together02be/users/controller/UsersController.java index bbda431..c19525a 100644 --- a/src/main/java/com/back/together02be/users/controller/UsersController.java +++ b/src/main/java/com/back/together02be/users/controller/UsersController.java @@ -2,20 +2,17 @@ import com.back.together02be.global.apiRes.ApiRes; import com.back.together02be.users.dto.request.LoginReq; -import com.back.together02be.users.dto.request.TokenReq; import com.back.together02be.users.dto.request.SignupReq; import com.back.together02be.users.dto.response.UsersRes; import com.back.together02be.users.service.UsersService; import io.swagger.v3.oas.annotations.Operation; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -31,10 +28,7 @@ public ResponseEntity> signup(@RequestBody @Valid SignupReq req) { usersService.signup(req); return ResponseEntity.ok( - new ApiRes<>( - "회원가입 성공", - null - ) + new ApiRes<>("회원가입 성공", null) ); } @@ -42,40 +36,57 @@ public ResponseEntity> signup(@RequestBody @Valid SignupReq req) { @PostMapping("/login") @Operation(summary = "로그인") - public ResponseEntity> login(@RequestBody LoginReq req) { + public ResponseEntity> login(@RequestBody @Valid LoginReq req, HttpServletResponse response) { String[] tokens = usersService.login(req); + addRefreshTokenCookie(response, tokens[1]); return ResponseEntity.ok( - new ApiRes<>( - "로그인 성공", - new UsersRes(tokens[0], tokens[1]) - ) + new ApiRes<>("로그인 성공", new UsersRes(tokens[0])) ); } @PostMapping("/token") @Operation(summary = "토큰 재발급") - public ResponseEntity> reissueToken(@RequestBody TokenReq req) { - - String[] tokens = usersService.reissueToken(req); + public ResponseEntity> reissueToken( + @CookieValue(name = "refreshToken") String refreshToken, + HttpServletResponse response + ) { + String[] tokens = usersService.reissueToken(refreshToken); + addRefreshTokenCookie(response, tokens[1]); return ResponseEntity.ok( - new ApiRes<>( - "토큰 재발급 성공", - new UsersRes(tokens[0], tokens[1]) - ) + new ApiRes<>("토큰 재발급 성공", new UsersRes(tokens[0])) ); } @PostMapping("/logout") @Operation(summary = "로그아웃") - public ResponseEntity> logout(@RequestBody TokenReq req) { - - usersService.logout(req); + public ResponseEntity> logout( + @CookieValue(name = "refreshToken") String refreshToken, + HttpServletResponse response + ) { + usersService.logout(refreshToken); + deleteRefreshTokenCookie(response); return ResponseEntity.ok( - new ApiRes<>( - "로그아웃 성공", - null - ) + new ApiRes<>("로그아웃 성공", null) ); } + + private void addRefreshTokenCookie(HttpServletResponse response, String refreshToken) { + Cookie cookie = new Cookie("refreshToken", refreshToken); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setDomain("localhost"); + cookie.setSecure(true); + cookie.setAttribute("SameSite", "Strict"); + response.addCookie(cookie); + } + + private void deleteRefreshTokenCookie(HttpServletResponse response) { + Cookie cookie = new Cookie("refreshToken", ""); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setDomain("localhost"); + cookie.setMaxAge(0); + response.addCookie(cookie); + } } diff --git a/src/main/java/com/back/together02be/users/dto/request/LoginReq.java b/src/main/java/com/back/together02be/users/dto/request/LoginReq.java index f2cbc6d..d0ef41c 100644 --- a/src/main/java/com/back/together02be/users/dto/request/LoginReq.java +++ b/src/main/java/com/back/together02be/users/dto/request/LoginReq.java @@ -1,6 +1,11 @@ package com.back.together02be.users.dto.request; +import jakarta.validation.constraints.NotBlank; + public record LoginReq( + @NotBlank(message = "아이디를 입력해주세요.") String username, + + @NotBlank(message = "비밀번호를 입력해주세요.") String password ) {} diff --git a/src/main/java/com/back/together02be/users/dto/request/TokenReq.java b/src/main/java/com/back/together02be/users/dto/request/TokenReq.java deleted file mode 100644 index b940f16..0000000 --- a/src/main/java/com/back/together02be/users/dto/request/TokenReq.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.back.together02be.users.dto.request; - -public record TokenReq( - String refreshToken -) {} diff --git a/src/main/java/com/back/together02be/users/dto/response/UsersRes.java b/src/main/java/com/back/together02be/users/dto/response/UsersRes.java index 76e8776..99c756c 100644 --- a/src/main/java/com/back/together02be/users/dto/response/UsersRes.java +++ b/src/main/java/com/back/together02be/users/dto/response/UsersRes.java @@ -1,6 +1,5 @@ package com.back.together02be.users.dto.response; public record UsersRes( - String accessToken, - String refreshToken + String accessToken ) {} \ No newline at end of file diff --git a/src/main/java/com/back/together02be/users/entity/Users.java b/src/main/java/com/back/together02be/users/entity/Users.java index 2ccc3c0..7d91cdc 100644 --- a/src/main/java/com/back/together02be/users/entity/Users.java +++ b/src/main/java/com/back/together02be/users/entity/Users.java @@ -17,7 +17,7 @@ public class Users extends BaseEntity { @Column(nullable = false, unique = true, length = 30) private String username; - @Column(nullable = false, length = 100) + @Column(nullable = false, length = 255) private String password; @Column(nullable = false, length = 30) diff --git a/src/main/java/com/back/together02be/users/service/UsersService.java b/src/main/java/com/back/together02be/users/service/UsersService.java index a309874..2769099 100644 --- a/src/main/java/com/back/together02be/users/service/UsersService.java +++ b/src/main/java/com/back/together02be/users/service/UsersService.java @@ -1,16 +1,17 @@ package com.back.together02be.users.service; +import com.back.together02be.asset.entity.UserAccount; +import com.back.together02be.asset.repository.UserAccountRepository; import com.back.together02be.global.util.JwtUtil; import com.back.together02be.users.dto.request.LoginReq; -import com.back.together02be.users.dto.request.TokenReq; import com.back.together02be.users.dto.request.SignupReq; + import com.back.together02be.users.entity.Users; import com.back.together02be.users.repository.UsersRepository; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; - -import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; @@ -22,8 +23,11 @@ public class UsersService { private final UsersRepository usersRepository; + private final UserAccountRepository userAccountRepository; private final PasswordEncoder passwordEncoder; + private static final long INITIAL_DEPOSIT = 50_000_000L; + @Value("${jwt.secret}") private String jwtSecret; @@ -52,6 +56,7 @@ public void signup(SignupReq req) { ); usersRepository.save(user); + userAccountRepository.save(new UserAccount(user, 0L, INITIAL_DEPOSIT)); } public long count() { @@ -64,11 +69,11 @@ public String[] login(LoginReq req) { Users user = usersRepository .findByUsername(req.username()) .orElseThrow( - () -> new IllegalArgumentException("존재하지 않는 아이디입니다.") + () -> new IllegalArgumentException("아이디 또는 비밀번호가 올바르지 않습니다.") ); - if (!req.password().equals(user.getPassword())) { - throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + if (!passwordEncoder.matches(req.password(), user.getPassword())) { + throw new IllegalArgumentException("아이디 또는 비밀번호가 올바르지 않습니다."); } // AccessToken 발급 @@ -89,10 +94,10 @@ public String[] login(LoginReq req) { } @Transactional - public void logout(TokenReq req) { + public void logout(String refreshToken) { Users user = usersRepository - .findByRefreshToken(req.refreshToken()) + .findByRefreshToken(refreshToken) .orElseThrow( () -> new IllegalArgumentException("유효하지 않은 리프레시 토큰입니다.") ); @@ -101,10 +106,10 @@ public void logout(TokenReq req) { } @Transactional - public String[] reissueToken(TokenReq req) { + public String[] reissueToken(String refreshToken) { Users user = usersRepository - .findByRefreshToken(req.refreshToken()) + .findByRefreshToken(refreshToken) .orElseThrow( () -> new IllegalArgumentException("유효하지 않은 리프레시 토큰입니다.") );