[260708]인증, 인가 실습

이상민·2026년 7월 8일

Spring

목록 보기
40/59

요구조건

  • 일정CRUD
  • 유저CRUD
  • 회원가입
  • 로그인(인증)

ERD

API명세서 < 클릭

일정CRUD

ScheduleEntity

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "schedules")
public class Schedule extends BaseEntity {


    //id, title, content, 작성/수정일
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(length = 10, nullable = false)
    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @Column(length = 200, nullable = false)
    private String content;


    public Schedule(String title, String content, User user) {
        this.title = title;
        this.content = content;
        this.user = user;
    }
    public void update(String title, String content){
        this.title = title;
        this.content = content;
    }
}

Schedule엔티티의 경우, Table명을 schedules로 바꿔 주었고, @ManyToOne 어노테이션을 사용하여 User를 매핑시켜주었다.

ScheduleController

@RestController
@RequiredArgsConstructor
@RequestMapping("/schedules")
public class ScheduleController {
    private final ScheduleService scheduleService;

    @PostMapping
    public ResponseEntity<CreateScheduleResponse> CreateSchedule(
            @RequestBody CreateScheduleRequest createScheduleRequest,
            HttpSession session){
        if(session.getAttribute("LoginUser") == null){
            throw new IllegalArgumentException("로그인이 필요합니다");
        }
        CreateScheduleResponse result = scheduleService.createSchedule(createScheduleRequest);
        return ResponseEntity.status(HttpStatus.CREATED).body(result);
    }

    @GetMapping
    public ResponseEntity<List<GetScheduleResponse>> GetAllSchedule(){
        List<GetScheduleResponse> result = scheduleService.getAllSchedule();
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

    @GetMapping("/{id}")
    public ResponseEntity<GetScheduleResponse> GetSchedule(@PathVariable Long id){
        GetScheduleResponse result = scheduleService.getSchedule(id);
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

    @PutMapping("/{id}")
    public ResponseEntity<UpdateScheduleResponse> UpdateSchedule(
            @PathVariable Long id,
            @RequestBody UpdateScheduleRequest request,
            HttpSession session){
        if(session.getAttribute("LoginUser") == null){
            throw new IllegalArgumentException("로그인이 필요합니다");
        }
        UpdateScheduleResponse result = scheduleService.updateSchedule(id, request);
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> DeleteSchedule(
            @PathVariable Long id,
            @RequestBody DeleteScheduleRequest request,
            HttpSession session){
        if(session.getAttribute("LoginUser") == null){
            throw new IllegalArgumentException("로그인이 필요합니다");
        }
        scheduleService.deleteSchedule(id, request);
        return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
    }
}

컨트롤러에서는 HttpSession session을 불러와서 getAttribute를 사용하여 로그인 유무를 파악해주고, 만약 로그인이 되어있지 않은 상태라면 조회를 제외한 CUD조작을 불가능하게 해주었다.

ScheduleService

@Service
@RequiredArgsConstructor
public class ScheduleService {
    private final ScheduleRepository scheduleRepository;
    private final UserRepository userRepository;

    @Transactional
    public CreateScheduleResponse createSchedule(CreateScheduleRequest request){
        User user = userRepository.findById(request.getUser_id()).orElseThrow(
                () -> new IllegalStateException("유저가 없습니다")
        );
        Schedule schedule = new Schedule(
                request.getTitle(),
                request.getContent(),
                user
        );
        Schedule saveSchedule = scheduleRepository.save(schedule);

        return new CreateScheduleResponse(
                saveSchedule.getId(),
                saveSchedule.getTitle(),
                saveSchedule.getUser().getName(),
                saveSchedule.getContent(),
                saveSchedule.getCreatedAt(),
                saveSchedule.getModifiedAt()
        );
    }

    @Transactional(readOnly = true)
    public List<GetScheduleResponse> getAllSchedule(){
        List<Schedule> schedules = scheduleRepository.findAll();
        List<GetScheduleResponse> dtos = new ArrayList<>();

        for (Schedule schedule : schedules) {
            GetScheduleResponse dto = new GetScheduleResponse(
                schedule.getId(),
                schedule.getTitle(),
                schedule.getUser().getName(),
                schedule.getContent(),
                schedule.getCreatedAt(),
                schedule.getModifiedAt()
            );
            dtos.add(dto);
        }
        dtos.sort((dto1, dto2) ->
                dto2.getModified().compareTo(dto1.getModified())
        );
        return dtos;
    }

    @Transactional(readOnly = true)
    public GetScheduleResponse getSchedule(Long id) {
        Schedule schedule = scheduleRepository.findById(id).orElseThrow(
                () -> new IllegalStateException("해당 일정이 없습니다")
        );
        return new GetScheduleResponse(
                schedule.getId(),
                schedule.getTitle(),
                schedule.getUser().getName(),
                schedule.getContent(),
                schedule.getCreatedAt(),
                schedule.getModifiedAt()
        );
    }

    @Transactional
    public UpdateScheduleResponse updateSchedule(Long id, UpdateScheduleRequest request) {
        Schedule schedule = scheduleRepository.findById(id).orElseThrow(
                () -> new IllegalStateException("해당 일정이 없습니다.")
        );
        schedule.update(
                request.getTitle(),
                request.getContent()
        );
        return new UpdateScheduleResponse(
                schedule.getTitle(),
                schedule.getContent(),
                schedule.getModifiedAt()
        );
    }

    @Transactional
    public void deleteSchedule(Long id, DeleteScheduleRequest request){
        Schedule schedule = scheduleRepository.findById(id).orElseThrow(
                () -> new IllegalStateException("없는 일정입니다.")
        );
        scheduleRepository.deleteById(id);
    }
}

ScheduleService에서는 엔티티에서 가져왔던 User객체를 통해 getUser().getName()을 사용하여 유저의 이름을 가져올 수 있게 하였다. 또한, 조회를 할 경우, 사정일이 빠른 순으로 자동 정렬하여 출력해주는 기능을 추가하였다.

유저CRUD

UserEntity

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false, unique = true)
    @Email
    private String email;

    @Column(nullable = false)
    @Size(min = 8, message = "비밀번호를 8글자 이상으로 설정해주세요")
    private String password;

    public User(String name, String email, String password) {
        this.name = name;
        this.email = email;
        this.password = password;
    }

    public void update(String email){
        this.email = email;
    }
}

UserEntity에서는 email을 형식에 맞춰 입력할 수 있게 @Email어노테이션을 사용해 주었고, password의 경우 8글자 이상이되게 size를 설정해주었다.

UserController

@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    // 회원가입
    @PostMapping
    public ResponseEntity<CreateUserResponse> signUp(
            @RequestBody CreateUserRequest request){
        CreateUserResponse result = userService.signUp(request);

        return ResponseEntity.status(HttpStatus.CREATED).body(result);
    }

    //로그인 로그아웃
    @PostMapping("/login")
    public ResponseEntity<Void> login(@Valid @RequestBody LoginRequest request, HttpSession session){
        SessionUser sessionUser = userService.login(request);
        session.setAttribute("LoginUser", sessionUser);
        return ResponseEntity.status(HttpStatus.OK).build();
    }
    @PostMapping("/logout")
    public ResponseEntity<Void> logout(HttpSession session){
        session.invalidate();

        return ResponseEntity.status(HttpStatus.OK).build();
    }

    @GetMapping
    public  ResponseEntity<List<GetUserResponse>> getAllUser(){
        List<GetUserResponse> result = userService.getAllUser();
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

    @GetMapping("/{id}")
    public ResponseEntity<GetUserResponse> getUser(
            @PathVariable Long id){
        GetUserResponse result = userService.getUser(id);
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

    @PutMapping("/{id}")
    public ResponseEntity<UpdateUserResponse> UpdateUser(
            @PathVariable Long id,
            @RequestBody UpdateUserRequest request,
            HttpSession session){
        if(session.getAttribute("LoginUser") == null){
            throw new IllegalArgumentException("로그인이 필요합니다");
        }
        UpdateUserResponse result = userService.updateUser(id, request);
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> DeleteUser(
            @PathVariable Long id,
            @RequestBody DeleteUserRequest request,
            HttpSession session){
        if(session.getAttribute("LoginUser") == null){
            throw new IllegalArgumentException("로그인이 필요합니다");
        }
        userService.deleteUser(id, request);
        return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
    }
}

UserController의 코드도 ScheduleController코드와 별반 다르지 않다. 중복되는 url을 @RequestMapping으로 처리해 주었으며, setAttribute 매서드를 사용하여 로그인이 됐다고 설정해줄 수 있게 하였다.
또한, session.invalidate를 사용하여 로그아웃 기능을 구현하였다.

UserService

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    @Transactional
    public CreateUserResponse signUp(CreateUserRequest request){

        User user = new User(
                request.getName(),
                request.getEmail(),
                request.getPassword()
        );

        User savedUser = userRepository.save(user);

        return new CreateUserResponse(
                savedUser.getId(),
                savedUser.getName(),
                savedUser.getEmail(),
                savedUser.getCreatedAt(),
                savedUser.getModifiedAt()
        );
    }
    @Transactional(readOnly = true)
    public SessionUser login(@Valid LoginRequest request){
        User user = userRepository.findByEmail(request.getEmail()).orElseThrow(
                () -> new IllegalArgumentException("유효하지 않은 로그인 요청입니다")
        );
        if(!user.getPassword().equals(request.getPassword())){
            throw new IllegalArgumentException("이메일 또는 비밀번호가 일치하지 않습니다");
        }
        return new SessionUser(
                user.getId(),
                user.getName(),
                user.getEmail()
        );
    }

    @Transactional(readOnly = true)
    public List<GetUserResponse> getAllUser(){
        List<User> users = userRepository.findAll();
        List<GetUserResponse> dtos = new ArrayList<>();

        for (User user : users) {

            GetUserResponse dto = new GetUserResponse(
                    user.getId(),
                    user.getName(),
                    user.getEmail(),
                    user.getCreatedAt(),
                    user.getModifiedAt()
            );
            dtos.add(dto);
        }
        dtos.sort((dto1, dto2) ->
                dto2.getId().compareTo(dto1.getId())
        );
        return dtos;
    }

    @Transactional(readOnly = true)
    public GetUserResponse getUser(Long id){
        User user = userRepository.findById(id).orElseThrow(
                () -> new IllegalStateException("없는 유저입니다")
        );
        return new GetUserResponse(
                user.getId(),
                user.getName(),
                user.getEmail(),
                user.getCreatedAt(),
                user.getModifiedAt()
        );
    }

    @Transactional
    public UpdateUserResponse updateUser(Long id, UpdateUserRequest request){
        User user = userRepository.findById(id).orElseThrow(
                () -> new IllegalStateException("없는 유저 입니다.")
        );
        user.update(
                request.getEmail()
        );
        return new UpdateUserResponse(
                user.getEmail(),
                user.getModifiedAt()
        );
    }
    @Transactional
    public void deleteUser(Long id, DeleteUserRequest request){
        User user = userRepository.findById(id).orElseThrow(
                () -> new IllegalStateException("없는 유저 입니다.")
        );
        userRepository.deleteById(id);
    }
}

UserService의 기능들은 ScheduleService와 유사하다. 다른점은, 로그인 기능이 구현되어있는데, email과 password를 전달받아 일치할 경우에만 로그인이 성공하게 한다는 점이다.
UserRepository에 findByEmail을 작성한 뒤, password를 user에게서 한개, requestDto에서 한개 전달받아 값이 같을경우 로그인이 성공하게 되는 원리이다.

트러블슈팅

목표한 기간 내에 비밀번호 암호화, 커스텀 예외처리, 댓글CRUD, 페이징 기능 등을 개발하지 못하여 아쉬웠다.
@ManyToOne 매핑을 처음 써봐서, 처음 엔티티를 작성하고 값을 불러올때는 많이 헤맸었다. 언제 User를 불러올수 있고, 이걸 dto에 포함해야 되는지, user객체를 불러오는것에 성공하였다면 user의 어떤값을 빼내야 하는지 등이 헷갈렸다.
하지만, 한번 막힌 곳을 해결하자 이전 어노테이션을 사용하지 않고 매핑했을때와는 차원이 다른 편리함을 느꼈다.

저번에 작성했던 스프링 프로젝트는, 매핑되어있는 데이터를 불러오는것이 어려워서, 단건조회와 다건조회의 dto를 따로 작성했던 기억이 난다. 하지만 @ManyToOne, @OneToMany를 사용한다면 굳이 이런 번거로운 방식을 따르지 않아도 간단하게 getsUser().getName()과 같이 값을 편하게 빼올수 있어서 좋았다.

profile
백앤드 개발 브이로그

0개의 댓글