Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] 인지원 로그인 #8

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,20 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.projectlombok:lombok:1.18.26'
implementation 'org.springframework.boot:spring-boot-starter-security'

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.jsonwebtoken:jjwt:0.9.1'

runtimeOnly 'com.mysql:mysql-connector-j'

annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}

tasks.named('test') {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/leets/attendance/AttendanceApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class AttendanceApplication {

Expand Down
23 changes: 23 additions & 0 deletions src/main/java/leets/attendance/domain/common/BaseTimeEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package leets.attendance.domain.common;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;

@LastModifiedDate
private LocalDateTime modifiedDate;
}
24 changes: 24 additions & 0 deletions src/main/java/leets/attendance/domain/common/dto/ResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package leets.attendance.domain.common.dto;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.io.Serializable;

public record ResponseDto<T>(T date) implements Serializable {
public static <T> ResponseEntity<T> ok(T data){
return ResponseEntity.ok(data);
}

public static <T> ResponseEntity<T> created(T data){
return ResponseEntity
.status(HttpStatus.CREATED)
.body(data);
}

public static <T> ResponseEntity<T> noContent(){
return ResponseEntity
.status(HttpStatus.NO_CONTENT)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package leets.attendance.domain.user.application;

import leets.attendance.domain.user.domain.User;
import leets.attendance.domain.user.exception.InvalidPasswordException;
import leets.attendance.domain.user.exception.UserNotFoundException;
import leets.attendance.domain.user.presentation.dto.Request.LoginRequestDto;
import leets.attendance.domain.user.presentation.dto.Response.DuplicationResponseDto;
import leets.attendance.domain.user.presentation.dto.Response.TokenResponseDto;
import leets.attendance.domain.user.repository.UserRepository;
import leets.attendance.domain.user.presentation.dto.Request.RegisterRequestDto;
import leets.attendance.global.jwt.TokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final TokenProvider tokenProvider;

@Transactional
public User register(RegisterRequestDto registerRequestDto){
User user = User.builder()
.userId(registerRequestDto.id())
.userPwd(passwordEncoder.encode(registerRequestDto.password()))
.name(registerRequestDto.name())
.part(registerRequestDto.part()).build();
return userRepository.save(user);

}

public DuplicationResponseDto checkDuplicateId(String joinId){
boolean isDuplicate = userRepository.existsByUserId(joinId);
return new DuplicationResponseDto(isDuplicate); //true, false로 중복확인
}

public TokenResponseDto login(LoginRequestDto loginRequestDto){
User user = userRepository.findByUserId(loginRequestDto.id()).orElseThrow(UserNotFoundException::new);
String password = loginRequestDto.password();

//복호화 후 체크해야하는데 시간이 없어서.. ^^..
if(!user.getUserPwd().equals(password)){
throw new InvalidPasswordException();
}

String accessToken = tokenProvider.generateAccessToken(user);
String refreshToken = tokenProvider.generateRefreshToken(user);
return new TokenResponseDto(accessToken, refreshToken);
}

}
13 changes: 13 additions & 0 deletions src/main/java/leets/attendance/domain/user/domain/Part.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package leets.attendance.domain.user.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum Part {
FE("FE"),
BE("BE"),
DESIGN("DE");
private final String gender;
}
34 changes: 34 additions & 0 deletions src/main/java/leets/attendance/domain/user/domain/User.java
Copy link
Member

@rootTiket rootTiket May 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정적 팩토리 메서드 패턴으로 수정하시는 방향도 고려해보시면 좋을것 같습니다!
다른 분 프로젝트에서 참고할 만한 코드가 있어 첨부합니다

public static Blog create(final Member member, final BlogCreateRequest blogCreateRequest) {
    return Blog.builder()
            .member(member)
            .title(blogCreateRequest.title())
            .description(blogCreateRequest.description())
            .build();
}

@Builder
public Blog(Member member, String title, String description) {
    this.member = member;
    this.title = title;
    this.description = description;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 것 같습니다! 리팩토링 해볼게요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package leets.attendance.domain.user.domain;

import jakarta.persistence.*;
import leets.attendance.domain.common.BaseTimeEntity;
import lombok.*;

import static jakarta.persistence.GenerationType.IDENTITY;

@Getter
@Entity
@Table(name = "users")
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "user_id")
private Long id;

@Column(nullable = false)
private String userId;

@Column(nullable = false)
private String userPwd;

@Column(nullable = false, length = 20)
private String name;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Part part;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package leets.attendance.domain.user.exception;

import leets.attendance.global.error.dto.ErrorCode;
import leets.attendance.global.error.exception.ServiceException;

public class InvalidPasswordException extends ServiceException {
public InvalidPasswordException(){
super(ErrorCode.INVALID_PASSWORD_EXCEPTION);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package leets.attendance.domain.user.exception;

import leets.attendance.global.error.dto.ErrorCode;
import leets.attendance.global.error.exception.ServiceException;

public class UserNotFoundException extends ServiceException {
public UserNotFoundException(){
super(ErrorCode.USER_NOT_FOUND_EXCEPTION);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package leets.attendance.domain.user.presentation;

import leets.attendance.domain.common.dto.ResponseDto;
import leets.attendance.domain.user.application.UserService;
import leets.attendance.domain.user.domain.User;
import leets.attendance.domain.user.presentation.dto.Request.LoginRequestDto;
import leets.attendance.domain.user.presentation.dto.Request.RegisterRequestDto;
import leets.attendance.domain.user.presentation.dto.Response.DuplicationResponseDto;
import leets.attendance.domain.user.presentation.dto.Response.TokenResponseDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.net.URI;

@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {
private final UserService userService;
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequestDto registerRequestDto){
User register = userService.register(registerRequestDto);
return ResponseDto.created(URI.create("/users/" + register.getId()));
}

@GetMapping("/duplication/{joinId}")
public ResponseEntity<DuplicationResponseDto> checkDuplicateId(@PathVariable String joinId){
return ResponseDto.ok(userService.checkDuplicateId(joinId));
}

@PostMapping("/login")
public ResponseEntity<TokenResponseDto> login(@RequestBody LoginRequestDto loginRequestDto) {
return ResponseDto.ok(userService.login(loginRequestDto));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package leets.attendance.domain.user.presentation.dto.Request;

import jakarta.validation.constraints.NotNull;

public record LoginRequestDto(
@NotNull
String id,
@NotNull
String password
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package leets.attendance.domain.user.presentation.dto.Request;

import jakarta.validation.constraints.NotNull;
import leets.attendance.domain.user.domain.Part;

public record RegisterRequestDto(
@NotNull
String id,

@NotNull
String name,

@NotNull
String password,

@NotNull
Part part
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part를 dto에서 enum type으로 받게 되면 "fe" , "be" 처럼 소문자 입력에서 에러가 발생할 수도 있을 것 같습니다.
String으로 변경후 받은 part를 대문자로 변환 -> enum에서 가져와서 저장하는 로직은 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 그 부분은 기획적인 부분도 포함될 거같은데요! 해당 선택 부분이 드롭다운 바라고 생각했어서 자체를 enum으로 가져오도록 구현했었네요 말씀하신 방법도 좋은 것 같습니다:)

) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package leets.attendance.domain.user.presentation.dto.Response;

public record DuplicationResponseDto(
boolean isDuplicated
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package leets.attendance.domain.user.presentation.dto.Response;

import jakarta.validation.constraints.NotNull;

public record TokenResponseDto(
@NotNull
String accessToken,

@NotNull
String refreshToken
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 서비스는 로그인 세션을 길게 유지할(길어도 일주일에 한번) 필요가 없는 서비스라는 생각이 들었습니다.
따라서 refreshToken을 사용하지 않는 방향도 고려해보시면 좋을것 같습니다.
습관적으로 refreshToken을 사용하지만 꼭 사용해야 할까? 에 대해서도 같이 고민해 보면 좋을 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네! 습관적으로 refreshtoken을 생각했네요.. ^^..ㅜ 조언 감사합니다:)

) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package leets.attendance.domain.user.repository;

import leets.attendance.domain.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.swing.text.html.Option;
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByUserId(String userId);

Optional<User> findByUserId(String userId);
}
37 changes: 37 additions & 0 deletions src/main/java/leets/attendance/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package leets.attendance.global.config;

import leets.attendance.global.jwt.JwtFilter;
import leets.attendance.global.jwt.TokenProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final TokenProvider tokenProvider;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf ->
csrf.disable());
return http.build();
}

@Bean
public JwtFilter jwtFilter() {
return new JwtFilter(tokenProvider);
}

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}

}
23 changes: 23 additions & 0 deletions src/main/java/leets/attendance/global/error/dto/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package leets.attendance.global.error.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum ErrorCode {
BAD_REQUEST(HttpStatus.BAD_REQUEST, "BAD_REQUEST", "잘못된 요청입니다."),
NOT_FOUND(HttpStatus.NOT_FOUND, "NOT_FOUND", "리소스를 찾을 수 없습니다."),
METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "METHOD_NOT_ALLOWED", "허용되지 않은 HTTP 메서드 입니다."),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR", "내부에서 서버 오류가 발생했습니다."),
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "UNAUTHORIZED","유효하지 않은 토큰입니다."),
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "UNAUTHORIZED", "만료된 토큰입니다."),
NOT_FOUND_TOKEN_ROLL(HttpStatus.FORBIDDEN, "FORBIDDEN", "토큰의 ROLE을 확인할 수 없습니다."),
USER_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND,"USER_NOT_FOUND_EXCEPTION","사용자를 찾을 수 없습니다."),
INVALID_PASSWORD_EXCEPTION(HttpStatus.UNAUTHORIZED,"INVALID_PASSWORD_EXCEPTION","비밀번호가 일치하지 않습니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;
}
22 changes: 22 additions & 0 deletions src/main/java/leets/attendance/global/error/dto/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package leets.attendance.global.error.dto;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public class ErrorResponse {
private final HttpStatus status;
private final String error;
private final String code;
private final String message;

public ErrorResponse(ErrorCode errorCode) {
this.status = errorCode.getHttpStatus();
this.error = errorCode.getHttpStatus().name();
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
public static ErrorResponse of(ErrorCode errorCode) {
return new ErrorResponse(errorCode);
}
}
Loading