스파르타 강의/스프링 강의

스프링 JWT 구현-5

SHsus 2022. 12. 4. 20:09

이번에는 Repository 부분과 마지막이 될 Service 부분에 대한 내용이다.

정말 너무 알아야할게 많은데 머리에는 안들어온다...

 

 

 

 

 

 

Repository 

Entity 에 의해 생성된 DB에 접근하는 메서드 들을 사용하기 위한 인터페이스
@Entity 으로 DB 구조를 만들고 여기에 CRUD 를 할 때 어떻게 할 것인지 정의해주는 계층이다.

 

 

 

 

 

 

 

 

ProductRepository


package com.sparta.myselectshop.repository;

import com.sparta.myselectshop.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface ProductRepository extends JpaRepository<Product, Long> {

    // 유저 ID 가 동일한 product 를 가져오는 메서드
    List<Product> findAllByUserId(Long userId);

    // product id 와 user id 가 일치하는 product 를 가져오는 메서드
    Optional<Product> findByIdAndUserId(Long id, Long userId);
}

 

UserRepository


package com.sparta.myselectshop.repository;

import com.sparta.myselectshop.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

 

Repo 에서는 쿼리문의 그대로 읽듯이 해석하면 이해하기가 쉽기 때문에 위 처럼 사용한다는 정도만 알고 넘어가자.

 

 

 

 

 

 

 

 

 

 

Service

 

컨트롤러의 요청을 받아 알맞은 정보를 가공하고 컨트롤러에 재전달 한다.
이를 비즈니스 로직을 수행한다고 한다.
비즈니스 로직을 수행하고 DB 에 접근하는 DTO 를 이용해서 결과값을 받아온다.

 

 

 

UserService


package com.sparta.myselectshop.service;

import com.sparta.myselectshop.dto.LoginRequestDto;
import com.sparta.myselectshop.dto.SignupRequestDto;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;

    // ADMIN_TOKEN
    private static final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";

    // 회원 가입 처리 부분
    @Transactional
    public void signup(SignupRequestDto signupRequestDto) {
        String username = signupRequestDto.getUsername();
        String password = signupRequestDto.getPassword();

        // 회원 중복 확인
        Optional<User> found = userRepository.findByUsername(username);
        if (found.isPresent()) {
            throw new IllegalArgumentException("중복된 사용자가 존재합니다.");
        }

        String email = signupRequestDto.getEmail();
        
        // 사용자 ROLE 확인
        UserRoleEnum role = UserRoleEnum.USER;
        
        if (signupRequestDto.isAdmin()) {
            if (!signupRequestDto.getAdminToken().equals(ADMIN_TOKEN)) {
                throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가능합니다.");
            }
            role = UserRoleEnum.ADMIN;
        }

        User user = new User(username, password, email, role);
        userRepository.save(user);
    }


    // 로그인 처리 부분
    @Transactional(readOnly = true)
    public void login(LoginRequestDto loginRequestDto, HttpServletResponse response) {
        String username = loginRequestDto.getUsername();
        String password = loginRequestDto.getPassword();

        // 사용자 확인
        User user = userRepository.findByUsername(username).orElseThrow(
                () -> new IllegalArgumentException("등록된 사용자가 없습니다.")
        );

        // 비밀번호 확인
        if(!user.getPassword().equals(password)){
            throw  new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
        }
        
        response.addHeader(JwtUtil.AUTHORIZATION_HEADER, jwtUtil.createToken(user.getUsername(), user.getRole()));
    }
}

 

 

관리자 계정 생성용 암호

회원가입시 관리자 계정의 생성에 필요한 암호이다.
해당 문자열의 값을 입력하고 회원가입시 관리자 권한을 가진채로 시작하게 된다.
private static final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";

 

 

 

회원 가입

@Transactional
public void signup(SignupRequestDto signupRequestDto) {
    String username = signupRequestDto.getUsername();   // username 변수에 유저가 입력한 값 가져와서 담는다.
    String password = signupRequestDto.getPassword();   // password 변수에도 똑같이 해준다.
    
    // 회원 중복 확인
    // DB 에 접근해서 클라이언트가 입력한 username 과 동일한 녀석이 있는지 찾는다.
    // isPresent 메소드의 역할은 Optional 객체가 값을 가지고 있다면 true, 없다면 false 를 리턴한다.
    Optional<User> found = userRepository.findByUsername(username);
    if (found.isPresent()) {
        throw new IllegalArgumentException("중복된 사용자가 존재합니다.");
  
    String email = signupRequestDto.getEmail();
    
    // 사용자 ROLE 확인
    // 기본적으로 유저의 권한 부분에 일반 USER 로 지정한다.
    UserRoleEnum role = UserRoleEnum.USER;
    
    // boolean 타입의 경우에는 getter 나 setter 에서 get, set 으로 호출이 아닌 is 가 앞에 붙은 형태로 호출한다.
    // 회원가입을 하는 유저의 관리자 권한을 판별하는 부분
    // 만약 유저가 어드민 권한부분을 체크하고 코드를 입력하면 판별하는 부분이다.
    if (signupRequestDto.isAdmin()) {
        if (!signupRequestDto.getAdminToken().equals(ADMIN_TOKEN)) {
            throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가능합니다.");
        }
        role = UserRoleEnum.ADMIN;
  
    // 위의 모든 과정이 잘 통과 되면 이제 새로운 객체를 만들어서 값을 넣어주고
    // save 함수를 이용해서 새롭게 DB 에 전달해서 등록완료
    User user = new User(username, password, email, role);
    userRepository.save(user);
}

 

 

 

 

로그인

// 로그인 처리 부분
@Transactional(readOnly = true)
public void login(LoginRequestDto loginRequestDto, HttpServletResponse response) {
    String username = loginRequestDto.getUsername();
    String password = loginRequestDto.getPassword();

    // 사용자 확인
    // orElseThrow 는 Optional 의 인자가 null 일 경우 예외처리를 한다.
    // user 변수에 DB 에서 입력한 유저의 이름을 찾아서 가져오는 것이 기본이다.
    // 여기서 추가로 orElseThrow 를 사용해서 검증을 거치는 것도 추가해 준다.
    User user = userRepository.findByUsername(username).orElseThrow(
            () -> new IllegalArgumentException("등록된 사용자가 없습니다.")
    );

    // 비밀번호 확인
    // 위에서 이미 user 에다가 가져온 정보에 비밀번호 확인작업을 거친다.
    if(!user.getPassword().equals(password)){
        throw  new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
    }

    // addHeader 함수는 클라이언트로의 응답 중 헤더부분에 원하는 내용을 추가해주는 함수
    // 이를 이용해서 JwtUtil 의 토큰을 가져와서 넣어준다.
    response.addHeader(JwtUtil.AUTHORIZATION_HEADER, jwtUtil.createToken(user.getUsername(), user.getRole()));
}

 

 

 

 

 

 

 

 

 

 

ProductService

위의 과정들을 이해했으면 아래에서는 비슷하게 반복이기 때문에
코드 안의 주석으로 대체한다.

 

package com.sparta.myselectshop.service;

import com.sparta.myselectshop.dto.ProductMypriceRequestDto;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.dto.ProductResponseDto;
import com.sparta.myselectshop.entity.Product;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.naver.dto.ItemDto;
import com.sparta.myselectshop.repository.ProductRepository;
import com.sparta.myselectshop.repository.UserRepository;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
public class ProductService {

    private final ProductRepository productRepository;
    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;

    // 관심 상품 추가 부분
    @Transactional
    public ProductResponseDto createProduct(ProductRequestDto requestDto, HttpServletRequest request) {
        // Request에서 Token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        // 토큰이 있는 경우에만 관심상품 추가 가능
        if (token != null) {
            if (jwtUtil.validateToken(token)) {
                // 토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            // 요청받은 DTO 로 DB에 저장할 객체 만들기
            // product 에 userid 가 들어가는데 이를 말로하면 product 와 user 가 연관관계가 있다고 한다.
            // 결론은 새롭게 만들어지는 product 에 만들 때 userid 를 같이 넣어준다.
            Product product = productRepository.saveAndFlush(new Product(requestDto, user.getId()));

            return new ProductResponseDto(product);
        } else {
            return null;
        }
    }


    // 관심 상품 조회 부분
    // 아래의 코드가 긴 이유는 토큰을 통해서 검증이 된 유저만 조회가 가능하게 되어있다.
    @Transactional(readOnly = true)
    public List<ProductResponseDto> getProducts(HttpServletRequest request) {
        // Request 에서 Token 가져오기
        String token = jwtUtil.resolveToken(request);

        // Claims 는 JWT 안에 들어있는 정보들을 담을 수 있는 객체
        Claims claims;

        // 토큰이 있는 경우에만 관심상품 조회 가능
        if (token != null) {
            // Token 검증
            if (jwtUtil.validateToken(token)) {
                // 토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            // claims.getSubject() 이 안에 유저의 이름이 들어있다.
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            // 사용자 권한 가져와서 ADMIN 이면 전체 조회, USER 면 본인이 추가한 부분 조회
            // 위의 user 에서 가져온 권한을 userRoleEnum 에 담아준다.
            UserRoleEnum userRoleEnum = user.getRole();
            System.out.println("role = " + userRoleEnum);

            // 반환을 하기 위한 List 를 새롭게 생성해준다.
            List<ProductResponseDto> list = new ArrayList<>();
            List<Product> productList;

            if (userRoleEnum == UserRoleEnum.USER) {
                // 사용자 권한이 USER일 경우
                // 유저의 아이디와 동일한 상품만을 가져와서 담아준다.
                productList = productRepository.findAllByUserId(user.getId());
            } else {
                // admin 계정이면 전부 다 가져와준다.
                productList = productRepository.findAll();
            }

            // 가지고온 product 들을 list 에 담아서 반환한다.
            for (Product product : productList) {
                list.add(new ProductResponseDto(product));
            }

            return list;

        } else {
            return null;
        }
    }


    // 관심 상품 최저가
    @Transactional
    public Long updateProduct(Long id, ProductMypriceRequestDto requestDto, HttpServletRequest request) {
        // Request에서 Token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        // 토큰이 있는 경우에만 관심상품 최저가 업데이트 가능
        if (token != null) {
            // Token 검증
            if (jwtUtil.validateToken(token)) {
                // 토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            // 위에서 product 에 userid 가 추가되었기 때문에 아래의 과정이 필요하다.
            // 현재 로그인한 유저가 선택한 product 가 맞는지 확인하는 과정이다.
            Product product = productRepository.findByIdAndUserId(id, user.getId()).orElseThrow(
                    () -> new NullPointerException("해당 상품은 존재하지 않습니다.")
            );

            product.update(requestDto);

            return product.getId();

        } else {
            return null;
        }
    }

    @Transactional
    public void updateBySearch (Long id, ItemDto itemDto){
        Product product = productRepository.findById(id).orElseThrow(
                () -> new NullPointerException("해당 상품은 존재하지 않습니다.")
        );
        product.updateByItemDto(itemDto);
    }

}