From 06c3e09a00d56e33985bb219816d772fbee1b91b Mon Sep 17 00:00:00 2001 From: su-hwani Date: Tue, 28 May 2024 18:48:10 +0900 Subject: [PATCH 1/6] Feat: spring security setting --- .../capstone/server/config/BCryptConfig.java | 13 +++++ .../server/config/SecurityConfig.java | 48 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 server/src/main/java/com/capstone/server/config/BCryptConfig.java create mode 100644 server/src/main/java/com/capstone/server/config/SecurityConfig.java diff --git a/server/src/main/java/com/capstone/server/config/BCryptConfig.java b/server/src/main/java/com/capstone/server/config/BCryptConfig.java new file mode 100644 index 0000000000..a9610d9ec3 --- /dev/null +++ b/server/src/main/java/com/capstone/server/config/BCryptConfig.java @@ -0,0 +1,13 @@ +package com.capstone.server.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class BCryptConfig { + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/server/src/main/java/com/capstone/server/config/SecurityConfig.java b/server/src/main/java/com/capstone/server/config/SecurityConfig.java new file mode 100644 index 0000000000..4f9b4ab628 --- /dev/null +++ b/server/src/main/java/com/capstone/server/config/SecurityConfig.java @@ -0,0 +1,48 @@ +package com.capstone.server.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +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.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import com.capstone.server.model.enums.UserRole; +import com.capstone.server.service.JwtTokenService; +import com.capstone.server.service.UserService; + +import lombok.RequiredArgsConstructor; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + private final JwtTokenService jwtTokenService; + private final UserService userService; + + @Value("${jwt.secretKey}") + private String secretKey; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { + return httpSecurity + .httpBasic(http -> http.disable()) + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .addFilterBefore(new JwtTokenFilter(jwtTokenService, userService, secretKey), UsernamePasswordAuthenticationFilter.class) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/guardian/**").permitAll() + .requestMatchers(HttpMethod.GET, "/api/missing-people").permitAll() + .requestMatchers("/api/user/info/**").authenticated() + .requestMatchers("/api/user/**").permitAll() + .anyRequest().hasAuthority(UserRole.ADMIN.name()) + ) + .build(); + } +} \ No newline at end of file From c2d977acfffd619785b76470ff45966de1ead56e Mon Sep 17 00:00:00 2001 From: su-hwani Date: Tue, 28 May 2024 18:48:48 +0900 Subject: [PATCH 2/6] Feat: JWT login --- .../server/config/JwtTokenFilter.java | 73 +++++++++++++++++++ .../server/service/JwtTokenService.java | 54 ++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 server/src/main/java/com/capstone/server/config/JwtTokenFilter.java create mode 100644 server/src/main/java/com/capstone/server/service/JwtTokenService.java diff --git a/server/src/main/java/com/capstone/server/config/JwtTokenFilter.java b/server/src/main/java/com/capstone/server/config/JwtTokenFilter.java new file mode 100644 index 0000000000..c17fa3b35d --- /dev/null +++ b/server/src/main/java/com/capstone/server/config/JwtTokenFilter.java @@ -0,0 +1,73 @@ +package com.capstone.server.config; + +import java.io.IOException; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.capstone.server.model.UserEntity; +import com.capstone.server.service.JwtTokenService; +import com.capstone.server.service.UserService; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +// OncePerRequestFilter : 매번 들어갈 때 마다 체크 해주는 필터 +@RequiredArgsConstructor +public class JwtTokenFilter extends OncePerRequestFilter { + + private final JwtTokenService jwtTokenService; + private final UserService userService; + private final String secretKey; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + + // Header의 Authorization 값이 비어있으면 => Jwt Token을 전송하지 않음 => 로그인 하지 않음 + if(authorizationHeader == null) { + filterChain.doFilter(request, response); + return; + } + + // Header의 Authorization 값이 'Bearer '로 시작하지 않으면 => 잘못된 토큰 + if(!authorizationHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + // 전송받은 값에서 'Bearer ' 뒷부분(Jwt Token) 추출 + String token = authorizationHeader.split(" ")[1]; + + // 전송받은 Jwt Token이 만료되었으면 => 다음 필터 진행(인증 X) + if (jwtTokenService.isExpired(token, secretKey)) { + filterChain.doFilter(request, response); + return; + } + + // Jwt Token에서 loginId 추출 + String loginId = jwtTokenService.getLoginId(token, secretKey); + + // 추출한 loginId로 User 찾아오기 + UserEntity loginUser = userService.getLoginUserByLoginId(loginId); + + // loginUser 정보로 UsernamePasswordAuthenticationToken 발급 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + loginUser.getLoginId(), null, List.of(new SimpleGrantedAuthority(loginUser.getRole().name()))); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + // 권한 부여 + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/server/src/main/java/com/capstone/server/service/JwtTokenService.java b/server/src/main/java/com/capstone/server/service/JwtTokenService.java new file mode 100644 index 0000000000..e9b772e8b5 --- /dev/null +++ b/server/src/main/java/com/capstone/server/service/JwtTokenService.java @@ -0,0 +1,54 @@ +package com.capstone.server.service; + +import java.util.Date; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import com.capstone.server.code.ErrorCode; +import com.capstone.server.exception.CustomException; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +@Service +public class JwtTokenService { + + // JWT Token 발급 + public static String createToken(String loginId, String secretKey, long expireTime) { + try { + // Claim = Jwt Token에 들어갈 정보 + // Claim에 loginId를 넣어 줌으로써 나중에 loginId를 꺼낼 수 있음 + Claims claims = Jwts.claims(); + claims.put("loginId", loginId); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + expireTime)) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + } catch (Exception e) { + throw new CustomException(ErrorCode.JWT_CREATE_ERROR); + } + } + + // Claims에서 loginId 꺼내기 + public static String getLoginId(String token, String secretKey) { + return extractClaims(token, secretKey).get("loginId").toString(); + } + + // 발급된 Token이 만료 시간이 지났는지 체크 + public static boolean isExpired(String token, String secretKey) { + Date expiredDate = extractClaims(token, secretKey).getExpiration(); + + // Token의 만료 날짜가 지금보다 이전인지 check + return expiredDate.before(new Date()); + } + + // SecretKey를 사용해 Token Parsing + private static Claims extractClaims(String token, String secretKey) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody(); + } +} \ No newline at end of file From 3703a0147819bb370271983a5a5e812db8c8fe8e Mon Sep 17 00:00:00 2001 From: su-hwani Date: Tue, 28 May 2024 18:49:24 +0900 Subject: [PATCH 3/6] Feat: login DTO & User Role --- .../server/dto/login/JoinRequestDto.java | 28 +++++++++++++++++++ .../server/dto/login/LoginRequestDto.java | 17 +++++++++++ .../server/dto/login/LoginResponseDto.java | 20 +++++++++++++ .../capstone/server/model/enums/UserRole.java | 17 +++++++++++ 4 files changed, 82 insertions(+) create mode 100644 server/src/main/java/com/capstone/server/dto/login/JoinRequestDto.java create mode 100644 server/src/main/java/com/capstone/server/dto/login/LoginRequestDto.java create mode 100644 server/src/main/java/com/capstone/server/dto/login/LoginResponseDto.java create mode 100644 server/src/main/java/com/capstone/server/model/enums/UserRole.java diff --git a/server/src/main/java/com/capstone/server/dto/login/JoinRequestDto.java b/server/src/main/java/com/capstone/server/dto/login/JoinRequestDto.java new file mode 100644 index 0000000000..050e4b01e1 --- /dev/null +++ b/server/src/main/java/com/capstone/server/dto/login/JoinRequestDto.java @@ -0,0 +1,28 @@ +package com.capstone.server.dto.login; + +import com.capstone.server.model.UserEntity; +import com.capstone.server.model.enums.UserRole; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class JoinRequestDto { + @NotBlank(message = "아이디가 비었습니다.") + private String loginId; + + @NotBlank(message = "비밀번호가 비었습니다.") + private String password; + + public UserEntity toEntity(String encodedPassword) { + return UserEntity.builder() + .loginId(this.loginId) + .password(encodedPassword) + .role(UserRole.USER) + .build(); + } +} \ No newline at end of file diff --git a/server/src/main/java/com/capstone/server/dto/login/LoginRequestDto.java b/server/src/main/java/com/capstone/server/dto/login/LoginRequestDto.java new file mode 100644 index 0000000000..530eb0b4d3 --- /dev/null +++ b/server/src/main/java/com/capstone/server/dto/login/LoginRequestDto.java @@ -0,0 +1,17 @@ +package com.capstone.server.dto.login; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class LoginRequestDto { + @NotBlank(message = "아이디가 비었습니다.") + private String loginId; + + @NotBlank(message = "비밀번호가 비었습니다.") + private String password; +} diff --git a/server/src/main/java/com/capstone/server/dto/login/LoginResponseDto.java b/server/src/main/java/com/capstone/server/dto/login/LoginResponseDto.java new file mode 100644 index 0000000000..664c020b06 --- /dev/null +++ b/server/src/main/java/com/capstone/server/dto/login/LoginResponseDto.java @@ -0,0 +1,20 @@ +package com.capstone.server.dto.login; + +import com.capstone.server.model.UserEntity; +import com.capstone.server.model.enums.UserRole; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class LoginResponseDto { + private String loginId; + private String jwtToken; + + public LoginResponseDto(UserEntity user, String jwtToken) { + this.loginId = user.getLoginId(); + this.jwtToken = jwtToken; + } +} diff --git a/server/src/main/java/com/capstone/server/model/enums/UserRole.java b/server/src/main/java/com/capstone/server/model/enums/UserRole.java new file mode 100644 index 0000000000..6add7cae82 --- /dev/null +++ b/server/src/main/java/com/capstone/server/model/enums/UserRole.java @@ -0,0 +1,17 @@ +package com.capstone.server.model.enums; + +import com.capstone.server.code.ErrorCode; +import com.capstone.server.exception.CustomException; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum UserRole { + USER("사용자", "user"), + ADMIN("관리자", "admin"); + + private final String kor; + private final String value; +} From f478b6ed922d2491ccfcec5fe53504b71bf2f34f Mon Sep 17 00:00:00 2001 From: su-hwani Date: Tue, 28 May 2024 18:50:41 +0900 Subject: [PATCH 4/6] Delete: Not used user api --- .../server/controller/UserApiController.java | 61 ------------------- .../server/dto/UserCreateRequestDto.java | 41 ------------- .../server/dto/UserUpdateRequestDto.java | 17 ------ 3 files changed, 119 deletions(-) delete mode 100644 server/src/main/java/com/capstone/server/controller/UserApiController.java delete mode 100644 server/src/main/java/com/capstone/server/dto/UserCreateRequestDto.java delete mode 100644 server/src/main/java/com/capstone/server/dto/UserUpdateRequestDto.java diff --git a/server/src/main/java/com/capstone/server/controller/UserApiController.java b/server/src/main/java/com/capstone/server/controller/UserApiController.java deleted file mode 100644 index 4d6074371c..0000000000 --- a/server/src/main/java/com/capstone/server/controller/UserApiController.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.capstone.server.controller; - -import com.capstone.server.code.ErrorCode; -import com.capstone.server.dto.UserCreateRequestDto; -import com.capstone.server.dto.UserUpdateRequestDto; -import com.capstone.server.exception.CustomException; -import com.capstone.server.model.UserEntity; -import com.capstone.server.response.SuccessResponse; -import com.capstone.server.service.UserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.BindingResult; -import org.springframework.validation.FieldError; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - - -@RestController -@RequestMapping("/api/user") -public class UserApiController { - - @Autowired - private UserService userService; - - @GetMapping("/all") - public ResponseEntity getUsers() { - List userEntities = userService.getAllUsers(); - int port = 3000; - return ResponseEntity.ok().body(new SuccessResponse(userEntities)); - } - - @PostMapping("/create") - public ResponseEntity createUser(@Validated @RequestBody UserCreateRequestDto userCreateRequestDto, BindingResult bindingResult) { - if (bindingResult.hasErrors()) { - Map errorMap = new HashMap<>(); - for (FieldError error : bindingResult.getFieldErrors()) { - errorMap.put(error.getField(), error.getDefaultMessage()); - } - throw new CustomException(ErrorCode.BAD_REQUEST, errorMap); - } else { - return ResponseEntity.ok().body(new SuccessResponse(userService.createUser(userCreateRequestDto.toEntity()))); - } - } - - @PutMapping("/{userId}") - public ResponseEntity updateUserNameById(@PathVariable Long userId, @Validated @RequestBody UserUpdateRequestDto userUpdateRequestDto, BindingResult bindingResult) { - if (bindingResult.hasErrors()) { - Map errorMap = new HashMap<>(); - for (FieldError error : bindingResult.getFieldErrors()) { - errorMap.put(error.getField(), error.getDefaultMessage()); - } - throw new CustomException(ErrorCode.BAD_REQUEST, errorMap); - } else { - return ResponseEntity.ok().body(userService.updateUserNameById(userId, userUpdateRequestDto)); - } - } -} \ No newline at end of file diff --git a/server/src/main/java/com/capstone/server/dto/UserCreateRequestDto.java b/server/src/main/java/com/capstone/server/dto/UserCreateRequestDto.java deleted file mode 100644 index 9d4a3a0775..0000000000 --- a/server/src/main/java/com/capstone/server/dto/UserCreateRequestDto.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.capstone.server.dto; - -import com.capstone.server.model.UserEntity; -import com.capstone.server.model.enums.userEnum; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.math.BigDecimal; -import java.time.LocalDateTime; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -public class UserCreateRequestDto { - - // @NotEmpty(message = "Name is required") - private String name; - - // @Positive(message = "Age is up to 0") - // @NotNull(message = "Age is required") - private Integer age; - - // @Email(message = "Invalid email address") - // @NotEmpty(message = "Email is required") - private String email; - - private BigDecimal latitude; - - private BigDecimal longitude; - - private LocalDateTime whenCreatedAt; - - private userEnum userEnum; - - public UserEntity toEntity() { - return UserEntity.builder().name(name).age(age).email(email).latitude(latitude).longitude(longitude).whenCreatedAt(whenCreatedAt).userEnum(userEnum).build(); - } -} diff --git a/server/src/main/java/com/capstone/server/dto/UserUpdateRequestDto.java b/server/src/main/java/com/capstone/server/dto/UserUpdateRequestDto.java deleted file mode 100644 index d06df3d3f6..0000000000 --- a/server/src/main/java/com/capstone/server/dto/UserUpdateRequestDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.capstone.server.dto; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@NoArgsConstructor -@AllArgsConstructor -@Getter -@Setter -public class UserUpdateRequestDto { - @NotNull(message = "Name is required") - private String name; - -} From dc588a41e1b65b285b1c8f7c3bd11c3c1d29ece2 Mon Sep 17 00:00:00 2001 From: su-hwani Date: Tue, 28 May 2024 18:50:56 +0900 Subject: [PATCH 5/6] Feat: login ErrorCode --- .../main/java/com/capstone/server/code/ErrorCode.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/capstone/server/code/ErrorCode.java b/server/src/main/java/com/capstone/server/code/ErrorCode.java index 3d96d1edf5..06d9757405 100644 --- a/server/src/main/java/com/capstone/server/code/ErrorCode.java +++ b/server/src/main/java/com/capstone/server/code/ErrorCode.java @@ -17,7 +17,6 @@ public enum ErrorCode { DATA_INTEGRITY_VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "Invalid request."), - USER_NOT_FOUND(HttpStatus.NOT_FOUND, "user not found by id."), MISSING_PEOPLE_NOT_FOUND_BY_ID(HttpStatus.NOT_FOUND, "Not found by id."), RESULT_NOT_FOUND(HttpStatus.NOT_FOUND, "don't exist results"), @@ -26,7 +25,14 @@ public enum ErrorCode { MESSAGE_SEND_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "message sending fail"), AI_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "AI SERVER ERROR"), INVALID_REQUEST_DATA(HttpStatus.UNPROCESSABLE_ENTITY, "AI server request is wrong"), - CONNECTION_ERROR(HttpStatus.SERVICE_UNAVAILABLE, "AI SERVER is CLOSE"); + CONNECTION_ERROR(HttpStatus.SERVICE_UNAVAILABLE, "AI SERVER is CLOSE"), + + // Login Error + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "User not found by login id."), + USER_NOT_MATCH_PASSWORD(HttpStatus.UNAUTHORIZED, "Invalid password"), + JWT_CREATE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Can not create JWT token"), + USER_NOT_ADMIN(HttpStatus.UNAUTHORIZED, "User not Admin, Please contact probee team"), + DUPLICATE_USER_LOGIN_ID(HttpStatus.CONFLICT, "Duplicate user login id."); private final HttpStatus httpStatus; private final String message; From fc13dbff28bb49170c2e7857c8a16a9a69c06c6c Mon Sep 17 00:00:00 2001 From: su-hwani Date: Tue, 28 May 2024 18:51:19 +0900 Subject: [PATCH 6/6] Feat: User Login & Join --- server/build.gradle | 5 ++ .../controller/JwtLoginApiController.java | 67 +++++++++++++++++++ .../com/capstone/server/model/UserEntity.java | 48 +++++-------- .../server/repository/UserRepository.java | 6 +- .../capstone/server/service/UserService.java | 66 +++++++++++++----- 5 files changed, 143 insertions(+), 49 deletions(-) create mode 100644 server/src/main/java/com/capstone/server/controller/JwtLoginApiController.java diff --git a/server/build.gradle b/server/build.gradle index c03d78f699..e843df3a68 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -36,6 +36,11 @@ dependencies { implementation 'org.springframework.kafka:spring-kafka' implementation 'org.hibernate:hibernate-spatial:6.4.4.Final' implementation 'net.nurigo:sdk:4.2.7' //sms 발송 관련 sdk + implementation 'org.springframework.boot:spring-boot-starter-security' // login + implementation 'io.jsonwebtoken:jjwt:0.9.1' // JWT + implementation 'com.sun.xml.bind:jaxb-impl:4.0.1' // com.sun.xml.bind + implementation 'com.sun.xml.bind:jaxb-core:4.0.1' // com.sun.xml.bind + implementation 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359' // javax.xml.bind compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/server/src/main/java/com/capstone/server/controller/JwtLoginApiController.java b/server/src/main/java/com/capstone/server/controller/JwtLoginApiController.java new file mode 100644 index 0000000000..6fbfa47634 --- /dev/null +++ b/server/src/main/java/com/capstone/server/controller/JwtLoginApiController.java @@ -0,0 +1,67 @@ +package com.capstone.server.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +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 com.capstone.server.dto.login.JoinRequestDto; +import com.capstone.server.dto.login.LoginRequestDto; +import com.capstone.server.dto.login.LoginResponseDto; +import com.capstone.server.model.UserEntity; +import com.capstone.server.response.SuccessResponse; +import com.capstone.server.service.JwtTokenService; +import com.capstone.server.service.UserService; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/user") +public class JwtLoginApiController { + @Value("${jwt.secretKey}") + private String secretKey; + @Value("${jwt.expireTime}") + private long expireTime; + + @Autowired + private UserService userService; + @Autowired + private JwtTokenService jwtTokenService; + + @PostMapping("/join") + public ResponseEntity join(@RequestBody JoinRequestDto joinRequestDto) { + + // loginId 중복 체크 + userService.checkLoginIdDuplicate(joinRequestDto.getLoginId()); + + // 회원가입 + userService.join(joinRequestDto); + return ResponseEntity.ok().body(new SuccessResponse("Join Success")); + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequestDto loginRequestDto) { + // 로그인 + UserEntity user = userService.login(loginRequestDto); + + // Jwt Token 발급 + String jwtToken = jwtTokenService.createToken(user.getLoginId(), secretKey, expireTime); + + LoginResponseDto loginResponseDto = new LoginResponseDto(user, jwtToken); + return ResponseEntity.ok().body(new SuccessResponse(loginResponseDto)); + } + + @GetMapping("/info") + public ResponseEntity userInfo(Authentication auth) { + UserEntity loginUser = userService.getLoginUserByLoginId(auth.getName()); + + return ResponseEntity.ok().body(new SuccessResponse(String.format("loginId : %s, role : %s", + loginUser.getLoginId(), loginUser.getRole().name()))); + } +} \ No newline at end of file diff --git a/server/src/main/java/com/capstone/server/model/UserEntity.java b/server/src/main/java/com/capstone/server/model/UserEntity.java index 8d47534833..e76d7dac3b 100644 --- a/server/src/main/java/com/capstone/server/model/UserEntity.java +++ b/server/src/main/java/com/capstone/server/model/UserEntity.java @@ -1,5 +1,6 @@ package com.capstone.server.model; +import com.capstone.server.model.enums.UserRole; import com.capstone.server.model.enums.userEnum; import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -17,50 +18,35 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@Entity(name = "test_user") +@Entity(name = "users") @EntityListeners(AuditingEntityListener.class) public class UserEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") private Long id; + @Column(name = "login_id", nullable = false) + private String loginId; - private String name; + @Column(nullable = false) + private String password; + @Enumerated(EnumType.STRING) + @Column(name = "role", nullable = false) + private UserRole role; - private Integer age; - - - private String email; - - private BigDecimal latitude; - - private BigDecimal longitude; - - private userEnum userEnum; - - - private Date createdAt; - - - private Date updatedAt; - - @CreatedDate - private LocalDateTime localDate; - - - private LocalDateTime whenCreatedAt; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; @PrePersist protected void onCreate() { - createdAt = new Date(); - updatedAt = new Date(); - localDate = LocalDateTime.now(); + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); } - @PreUpdate // 업데이트가 발생할 때 호출되는 메소드 + @PreUpdate protected void onUpdate() { - updatedAt = new Date(); + updatedAt = LocalDateTime.now(); } - -} +} \ No newline at end of file diff --git a/server/src/main/java/com/capstone/server/repository/UserRepository.java b/server/src/main/java/com/capstone/server/repository/UserRepository.java index 6dc46e13a1..80d7df166a 100644 --- a/server/src/main/java/com/capstone/server/repository/UserRepository.java +++ b/server/src/main/java/com/capstone/server/repository/UserRepository.java @@ -3,7 +3,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import com.capstone.server.model.UserEntity; +import java.util.List; +import java.util.Optional; -public interface UserRepository extends JpaRepository{ +public interface UserRepository extends JpaRepository{ + boolean existsByLoginId(String loginId); + Optional findByLoginId(String loginId); } diff --git a/server/src/main/java/com/capstone/server/service/UserService.java b/server/src/main/java/com/capstone/server/service/UserService.java index 914bb3d44f..11e489f7f1 100644 --- a/server/src/main/java/com/capstone/server/service/UserService.java +++ b/server/src/main/java/com/capstone/server/service/UserService.java @@ -1,42 +1,74 @@ package com.capstone.server.service; -import java.util.List; -import java.util.Map; import java.util.Optional; +import org.postgresql.shaded.com.ongres.scram.common.message.ServerFinalMessage.Error; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import com.capstone.server.code.ErrorCode; -import com.capstone.server.dto.UserUpdateRequestDto; +import com.capstone.server.dto.login.JoinRequestDto; +import com.capstone.server.dto.login.LoginRequestDto; import com.capstone.server.exception.CustomException; import com.capstone.server.model.UserEntity; +import com.capstone.server.model.enums.UserRole; import com.capstone.server.repository.UserRepository; @Service public class UserService { @Autowired private UserRepository userRepository; + @Autowired + private PasswordEncoder passwordEncoder; + @Value("${admin.loginId}") + private String adminLoginId; - public UserEntity createUser(UserEntity userEntity) { - try { - return userRepository.save(userEntity); - } catch (Exception e) { - throw new CustomException(ErrorCode.USER_EXISTS, e); + // 중복된 ID 체크 + public void checkLoginIdDuplicate(String loginId) { + + if (userRepository.existsByLoginId(loginId)) { + throw new CustomException(ErrorCode.DUPLICATE_USER_LOGIN_ID); } } - public List getAllUsers() { - return userRepository.findAll(); + // 회원가입 + @Transactional + public UserEntity join(JoinRequestDto joinRequestDto) { + if (!joinRequestDto.getLoginId().contains(adminLoginId)) { + throw new CustomException(ErrorCode.USER_NOT_ADMIN); + } + UserEntity user = userRepository.save(joinRequestDto.toEntity(passwordEncoder.encode(joinRequestDto.getPassword()))); + + user.setRole(UserRole.ADMIN); + return user; } - public UserEntity updateUserNameById(Long userId, UserUpdateRequestDto userUpdateRequestDto) { - Optional existingUserOptional = userRepository.findById(userId); - if (existingUserOptional.isPresent()) { - UserEntity existingUser = existingUserOptional.get(); - existingUser.setName(userUpdateRequestDto.getName()); - return userRepository.save(existingUser); - } else { + // 로그인 + public UserEntity login(LoginRequestDto loginRequestDto) { + Optional optionalUser = userRepository.findByLoginId(loginRequestDto.getLoginId()); + if (optionalUser.isEmpty()) { throw new CustomException(ErrorCode.USER_NOT_FOUND); } + + UserEntity user = optionalUser.get(); + + if (!passwordEncoder.matches(loginRequestDto.getPassword(), user.getPassword())) { + throw new CustomException(ErrorCode.USER_NOT_MATCH_PASSWORD); + } + + return user; + } + + // user 찾기 + public UserEntity getLoginUserByLoginId(String loginId) { + if (loginId == null) return null; + + Optional optionalUser = userRepository.findByLoginId(loginId); + if (optionalUser.isEmpty()) return null; + + return optionalUser.get(); } }