스파르타 강의/스프링 강의
스프링 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);
}
}