Light Blue Pointer
본문 바로가기
Developing/개발일지

2023-12-08, Today I Learned

by Greedy 2023. 12. 8.

오늘 한 일

주문 승인처리, 배달 완료처리

Response타입 개선, 에러 처리, Price 형식 지정,

내가 찜한 아이템 보기 기능 구현, 로그아웃 기능 구현

 

오늘 개발하는데 레고 조립하는거같고 뭔가 엄청 재밌었다 ㅋㅋㅋㅋㅋㅋ

어떤건 글쓴이가 가능하면 안되고 어떤건 댓글 단 사람(주문 신청한 사람)만 가능해야 하고

어떤건 글쓴이만 가능해야 하고

구분하면서 코드짜는게 너무 재밌었음 ㅋㅋㅋ

로직 개발이 제일 재밌는 파트인 거 같다

 

 

요청 승인할때

이미 rejected/confirmed 된 거는 이제 변하면 안 되니까 boolean도 주고싶음

-> boolean으로 했다가 나중에 Status Enum으로 또 바꿨다

public void reject(){
        if(this.completed == true){
            throw new RejectedExecutionException("이미 처리된 요청입니다");
        }
        this.orderStatus = "REJECTED";
        this.completed = true;
    }

이렇게 해봄

유저 2로 거절해봄

{
    "status": 400,
    "message": "아이템 작성자만 구매요청을 승인/거절할 수 있습니다.",
    "data": null
}

유저 1로 거절해봄

한번 거절해봄

{
    "status": 200,
    "message": "구매 요청 거절 성공했습니다",
    "data": {
        "orderId": 1,
        "buyerName": "닉2",
        "address": "주소주소",
        "price": "가격가격",
        "orderStatus": "REJECTED"
    }
}

두번 거절해봄

{
    "status": 400,
    "message": "이미 처리된 요청입니다",
    "data": null
}

승인할때

item.complete();
order.confirm();

이 순서로 달아서

public void complete(){
        if(this.completed == true){
            throw new RejectedExecutionException("이미 주문이 완료된 아이템입니다");
        }
        this.completed = true;
    }

item이 이미 completed된 아이템이면 승인 못 하게 함!

{
    "status": 400,
    "message": "이미 주문이 완료된 아이템입니다",
    "data": null
}

잘 뜸

 

 

myorders신나게 만들었는데 에러뜸

@GetMapping("/myorders")
    public ApiResponse<List<OrderResponseDto>> getMyOrders(
            @AuthenticationPrincipal UserDetailsImpl userDetails
    ) {
        try {
            List<OrderResponseDto> responseDtos = orderService.getMyOrders(userDetails.getUser());
            return new ApiResponse<List<OrderResponseDto>>(HttpStatus.OK.value(),"구매 요청 조회에 성공했습니다",responseDtos);
        }catch (RejectedExecutionException | NullPointerException ex){
            return new ApiResponse<List<OrderResponseDto>>(HttpStatus.BAD_REQUEST.value(),ex.getMessage());
        }
    }
public List<OrderResponseDto> getMyOrders(User user){
        return orderRepository.findAllByUserId(user.getId()).orElseThrow(()-> new NullPointerException("주문 요청이 존재하지 않습니다"))
                .stream()
                .map(OrderResponseDto::new)
                .collect(Collectors.toList());
    }

java.util.Optional com.raincloud.sunlightmarket.order.repository.OrderRepository.findAllByUserId(java.lang.Long); Reason: Failed to create query for method public abstract java.util.Optional com.raincloud.sunlightmarket.order.repository.OrderRepository.findAllByUserId(java.lang.Long); No property 'userId' found for type 'Order’

쿼리가 잘못되었나봄

private Buyer buyer;

지금 보니 Order에는 User가 아니라 Buyer가 들어있었음

public List<OrderResponseDto> getMyOrders(User user){
        return orderRepository.findAllByBuyerId(user.getBuyer().getId()).orElseThrow(()-> new NullPointerException("주문 요청이 존재하지 않습니다"))
                .stream()
                .map(OrderResponseDto::new)
                .collect(Collectors.toList());
    }

User → Buyer로 수정해줌

됐음

 

 

요청이 수락된 Order들만 가져와서 보여주고 싶은데

List를 가져와서 orderStatus가 confirmed인 것들만 출력하고 싶어짐

public List<OrderResponseDto> getMyConfirmedOrders(User user){
        List<Order> orders = orderRepository.findAllByBuyerId(user.getBuyer().getId()).orElseThrow(()-> new NullPointerException("주문 요청이 존재하지 않습니다"));
        
        return orders.stream()
                .map(OrderResponseDto::new)
                .collect(Collectors.toList());
    }

Service단에서 orders List 순회하면서 confirmed가 아니면 다 지워버리고 리턴하려고 했었으나

그냥 db에서 쿼리로 할 수 있지 않을까 싶어짐

Optional<List<Order>> findAllByBuyerIdWhereOrderStatus(Long buyerId, String orderStatus);

이렇게 바꿔봄

public List<OrderResponseDto> getMyConfirmedOrders(User user){
        List<Order> orders = orderRepository.findAllByBuyerIdWhereOrderStatus(user.getBuyer().getId(),"CONFIRMED").orElseThrow(()-> new NullPointerException("주문 요청이 존재하지 않습니다"));
        return orders.stream()
                .map(OrderResponseDto::new)
                .collect(Collectors.toList());
    }

Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.Optional com.raincloud.sunlightmarket.order.repository.OrderRepository.findAllByBuyerIdWhereOrderStatus(java.lang.Long,java.lang.String); Reason: Failed to create query for method public abstract java.util.Optional com.raincloud.sunlightmarket.order.re

Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.Optional com.raincloud.sunlightmarket.order.repository.OrderRepository.findAllByBuyerIdWhereOrderStatus(java.lang.Long,java.lang.String); No property 'whereOrderStatus' found for type 'Long'; Traversed path: Order.buyer.id

쿼리가 잘못된 거 같음

어떻게 쓰면 되는지 찾아봄

https://www.baeldung.com/spring-data-derived-queries

where이 아니라 equals를 써야하나봄

List<User> findByNameEquals(String name);

근데 조건을 두개 하고 싶어

findAllByBuyerIdAndOrderStatusEquals

이렇게 해봄

오 Confirmed만 잘 넘어옴

{
    "status": 200,
    "message": "구매 요청 조회에 성공했습니다",
    "data": [
        {
            "orderId": 3,
            "buyerName": "닉2",
            "address": "주소주소2",
            "price": "가격가격2",
            "orderStatus": "CONFIRMED"
        }
    ]
}

이건 전체를 조회한 것

{
    "status": 200,
    "message": "구매 요청 조회에 성공했습니다",
    "data": [
        {
            "orderId": 1,
            "buyerName": "닉2",
            "address": "주소주소",
            "price": "가격가격",
            "orderStatus": "REJECTED"
        },
        {
            "orderId": 2,
            "buyerName": "닉2",
            "address": "주소주소2",
            "price": "가격가격2",
            "orderStatus": "REJECTED"
        },
        {
            "orderId": 3,
            "buyerName": "닉2",
            "address": "주소주소2",
            "price": "가격가격2",
            "orderStatus": "CONFIRMED"
        }
    ]
}
@Transactional
    public OrderResponseDto confirmDelivery(Long orderId,User user){
        Order order = getUserOrderById(orderId,user);
        Item item = order.getItem();
        item.confirmDelivery();
        order.confirmDelivery();
        return new OrderResponseDto(order);
    }

이렇게 하고 생각해보니

Confirmed인것만 승인해줘야 함

public void confirmDelivery(){
        if(this.orderStatus.equals("CONFIRMED")){
            this.orderStatus = "DELIVERED";
        }else{
            throw new RejectedExecutionException("유효하지 않은 요청입니다.");
        }
    }

이렇게 수정해봄

 

💡개선 : status String으로 쓰던거 Enum화 하기

https://www.w3schools.com/java/java_enums.asp

https://www.geeksforgeeks.org/enum-in-java/

A Java enumeration is a class type. Although we don’t need to instantiate an enum using new, it has the same capabilities as other classes. This fact makes Java enumeration a very powerful tool. Just like classes, you can give them constructors, add instance variables and methods, and even implement interfaces.

public enum OrderStatusEnum {
    PENDING,
    CONFIRMED,
    REJECTED,
    DELIVERED
}

내가 저거 보고 필요한거 짜면 이렇게 됨

package com.raincloud.sunlightmarket.global.entity;

public enum UserRoleEnum {
    USER(Authority.USER),  // 사용자 권한
    ADMIN(Authority.ADMIN);  // 관리자 권한

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {

        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}

팀원이 짠건 이럼 static으로 저걸 하면 좋은점이 있나?

if(this.orderStatus.equals("CONFIRMED")||this.orderStatus.equals("REJECTED")){

근데 나 이거 해야해서 Enum 끼리 비교해야 하는데 그거 가능?

db에 Enum이 안 들어가서 String으로 넣으려고 저랬나?

Comparing Java enum members: == or equals()?

another argument to use == instead of equals ist compile-time checking of types. myEnum.MY_CONSTANT.equals("Some String") will compile and myEnum.MY_CONSTANT == "Some String" will not, as "Some String" is not of the same type and the compiler can determin it upfront

오 팀원이 한건 String으로 들어가 지는데 나는 int로 됨

db에 0,1,2,3이 들어가 있다

pending 0

rejected 2

confirmed 1

delivered 3

{
    "status": 200,
    "message": "나의 구매 요청 조회에 성공했습니다",
    "data": [
        {
            "orderId": 1,
            "buyerName": "닉2",
            "address": "주소주소3",
            "price": "가격가격4",
            "orderStatus": "REJECTED"
        },
        {
            "orderId": 2,
            "buyerName": "닉2",
            "address": "주소주소3",
            "price": "가격가격4",
            "orderStatus": "CONFIRMED"
        },
        {
            "orderId": 3,
            "buyerName": "닉2",
            "address": "주소주소3",
            "price": "가격가격4",
            "orderStatus": "PENDING"
        }
    ]
}

all은 잘 불러서 읽어지는데

confirm만 불러오라 하면 에러남

String을 주면 안되나봄

List<Order> orders = orderRepository.findAllByBuyerIdAndOrderStatusEquals(buyerId,"CONFIRMED").orElseThrow(()-> new NullPointerException("주문 요청이 존재하지 않습니다"));

->
List<Order> orders = orderRepository.findAllByBuyerIdAndOrderStatusEquals(buyerId, OrderStatus.CONFIRMED).orElseThrow(()-> new NullPointerException("주문 요청이 존재하지 않습니다"));

public List<OrderResponseDto> getMyConfirmedOrders(User user){
        Long buyerId = user.getBuyer().getId();
        List<Order> orders = orderRepository.findAllByBuyerIdAndOrderStatusEquals(buyerId, OrderStatus.CONFIRMED).orElseThrow(()-> new NullPointerException("주문 요청이 존재하지 않습니다"));
        return orders.stream()
                .map(OrderResponseDto::new)
                .collect(Collectors.toList());
    }
{
    "status": 200,
    "message": "나의 구매 요청 조회에 성공했습니다",
    "data": [
        {
            "orderId": 2,
            "buyerName": "닉2",
            "address": "주소주소3",
            "price": "가격가격4",
            "orderStatus": "CONFIRMED"
        }
    ]
}

오호 저 부분 바꿔주니까 잘 동작함

Item의 completed, delivered도 하나의 Status Enum으로 만들어봄

completed전 후

ON_SALE

SOLD

DELIVERED

price의 값 지정해보기

gradle에 이거 추가하기

// Validation
    implementation 'org.springframework.boot:spring-boot-starter-validation'
@Getter
@RequiredArgsConstructor
public class ItemRequestDto {
    private String title;
    private String image;
    @Pattern(regexp = "^[0-9]*$", message = "가격은 숫자로만 입력가능합니다.")
    private String price;
    private String address;
    private String content;
}
}

log 빨간줄 →

import lombok.extern.slf4j.Slf4j;

@Slf4j

이걸 가져와줌

//상품 등록
    @PostMapping("/add")
    public ApiResponse<ItemResponseDto> addItem(
            @Valid @RequestBody ItemRequestDto requestDto,
            BindingResult bindingResult,
            @AuthenticationPrincipal UserDetailsImpl userDetails
    ) {
        try {
            // Validation 예외 처리
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            if (fieldErrors.size() > 0) {
                for (FieldError fieldError : bindingResult.getFieldErrors()) {
                    log.error(fieldError.getField() + " 필드 : " + fieldError.getDefaultMessage());
                }
                throw new IllegalArgumentException("입력 형식이 정확하지 않습니다");
            }
            ItemResponseDto responseDto = itemService.addItem(requestDto, userDetails.getUser());
            return new ApiResponse<ItemResponseDto>(HttpStatus.CREATED.value(),"아이템 추가 성공했습니다",responseDto);
        }catch (RejectedExecutionException | IllegalArgumentException | NullPointerException ex){
            return new ApiResponse<ItemResponseDto>(HttpStatus.BAD_REQUEST.value(),ex.getMessage());
        }
    }
{
    "status": 400,
    "message": "입력 형식이 정확하지 않습니다",
    "data": null
}

이렇게 출력됨

 

Response 타입 개선해보기, global하게 에러 처리

ApiResponse개선해보기!

package com.raincloud.sunlightmarket.global.dto;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatusCode;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ApiResponse<T> {

    private int status;
    private String message;
    private T data;

    public ApiResponse(int status, String message, T data){
        this.status = status;
        this.message = message;
        this.data = data;
    }

    public ApiResponse(int status, String message){
        this.status = status;
        this.message = message;
        this.data = null;
    }

    public ApiResponse(T data){
        this.data = data;
    }
}

지금 활용법

@PutMapping("")
    public ApiResponse<OrderResponseDto> updateOrder(
            @RequestBody OrderRequestDto requestDto,
            @RequestParam Long orderId,
            @AuthenticationPrincipal UserDetailsImpl userDetails
    ) {
        try {
            OrderResponseDto responseDto = orderService.updateOrder(requestDto,orderId,userDetails.getUser());
            return new ApiResponse<OrderResponseDto>(HttpStatus.OK.value(),"구매 요청 업데이트 성공했습니다",responseDto);
        }catch (RejectedExecutionException | NullPointerException ex){
            return new ApiResponse<OrderResponseDto>(HttpStatus.BAD_REQUEST.value(),ex.getMessage());
        }
    }

메시지를 넣지 말고 그냥 에러를 넣어서 처리하게 해봄!

 

Q. 여기에 저 blindingresult 는 validation pattern에도 나오는데 저게 뭐지?

 

@ExceptionHandler({IllegalArgumentException.class})
    public ApiResponse<?> handleException(IllegalArgumentException ex){
        return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(),ex.getMessage());
    }

<?> 를 쓰면 제너릭일때 타입에 상관없이 쓸수있단걸 알았다!

package com.raincloud.sunlightmarket.global.exception;

import com.raincloud.sunlightmarket.global.dto.ApiResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.concurrent.RejectedExecutionException;

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler({IllegalArgumentException.class})
    public ApiResponse<?> handleIllegalArgumentException(IllegalArgumentException ex){
        return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(),ex.getMessage());
    }
    @ExceptionHandler({NullPointerException.class})
    public ApiResponse<?> handleNullPointerException(NullPointerException ex){
        return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(),ex.getMessage());
    }

    @ExceptionHandler({RejectedExecutionException.class})
    public ApiResponse<?> handleRejectedExecutionException(RejectedExecutionException ex){
        return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(),ex.getMessage());
    }

}

이거 만들고 try catch문 다 빼봄!

//상품 등록
    @PostMapping("/add")
    public ApiResponse<ItemResponseDto> addItem(
            @Valid @RequestBody ItemRequestDto requestDto,
            BindingResult bindingResult,
            @AuthenticationPrincipal UserDetailsImpl userDetails
    ) {
        try {
            // Validation 예외 처리
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            if (fieldErrors.size() > 0) {
                for (FieldError fieldError : bindingResult.getFieldErrors()) {
                    log.error(fieldError.getField() + " 필드 : " + fieldError.getDefaultMessage());
                }
                throw new IllegalArgumentException("입력 형식이 정확하지 않습니다");
            }
            ItemResponseDto responseDto = itemService.addItem(requestDto, userDetails.getUser());
            return new ApiResponse<ItemResponseDto>(HttpStatus.CREATED.value(),"아이템 추가 성공했습니다",responseDto);
        }catch (RejectedExecutionException | IllegalArgumentException | NullPointerException ex){
            return new ApiResponse<ItemResponseDto>(HttpStatus.BAD_REQUEST.value(),ex.getMessage());
        }
    }

//상품 등록
    @PostMapping("/add")
    public ApiResponse<ItemResponseDto> addItem(
            @Valid @RequestBody ItemRequestDto requestDto,
            BindingResult bindingResult,
            @AuthenticationPrincipal UserDetailsImpl userDetails
    ) {
        
        // Validation 예외 처리
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        if (fieldErrors.size() > 0) {
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                log.error(fieldError.getField() + " 필드 : " + fieldError.getDefaultMessage());
            }
            throw new IllegalArgumentException("입력 형식이 정확하지 않습니다");
        }
        ItemResponseDto responseDto = itemService.addItem(requestDto, userDetails.getUser());
        return new ApiResponse<ItemResponseDto>(HttpStatus.CREATED.value(),"아이템 추가 성공했습니다",responseDto);
    }
{
    "status": 400,
    "message": "입력 형식이 정확하지 않습니다",
    "data": null
}

오 catch문 뺐는데 에러 잘 받아서 처리해줌!

 

 

내가 찜한 아이템 보는 기능

//전체 상품 조회, 로그인
    @GetMapping("")
    public ApiResponse<List<ItemResponseDto>> getItems(
            @RequestParam String type,
            @AuthenticationPrincipal UserDetailsImpl userDetails
    ) {
        if(type.equals("All")){
            return getAllItems();
        }
        else if(type.equals("Myselect")){
            return getAllLikedItems(userDetails.getUser());
        }
        else{return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(),"올바르지 않은 요청입니다");}
    }

    public ApiResponse<List<ItemResponseDto>> getAllItems()
    {
        List<ItemResponseDto> responseDto = itemService.getAllItems();
        return new ApiResponse<>(HttpStatus.OK.value(),"모든 아이템 조회에 성공했습니다",responseDto);
    }

    public ApiResponse<List<ItemResponseDto>> getAllLikedItems(User user)
    {
        List<ItemResponseDto> responseDto = itemService.getAllLikedItems(user);
        return new ApiResponse<>(HttpStatus.OK.value(),"좋아요한 아이템 조회에 성공했습니다",responseDto);
    }

 

public List<ItemResponseDto> getAllLikedItems(User user) {
    List<Item> items =  itemRepository.findAllByOrderByModifiedAtDesc();
    List<ItemResponseDto> responseDtos = new ArrayList<>();
    for(Item item : items){
        if(likeRepository.existsByUserAndItem(user,item)){
            responseDtos.add(new ItemResponseDto(item));
        }
    }
    return  responseDtos;
}

간단하게 끝났다!

 

로그아웃 기능 구현

팀원이 로그아웃을 해서 올린 순간부터 로그아웃이 안 되길래

내가 다른 방식으로 개발해봄

  1. 쿠키방식으로 바꾸기
  2. 쿠키를 delete하기

이렇게 하는게 좋겠음

예전

// HttpServletRequest 에서 Cookie Value : JWT 가져오기
    public String getTokenFromRequest(HttpServletRequest req) {
        Cookie[] cookies = req.getCookies();
        if(cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(AUTHORIZATION_HEADER)) {
                    try {//FU//
                        return URLDecoder.decode(cookie.getValue().replaceAll("Bearer%20", ""), "UTF-8"); // Encode 되어 넘어간 Value 다시 Decode
                    } catch (UnsupportedEncodingException e) {
                        return null;
                    }
                }
            }
        }
        return null;
    }

예전

public String createToken(String userId) {
        Date date = new Date();

        // 토큰 만료시간 60분
        long TOKEN_TIME = 60 * 60 * 1000;
        return BEARER_PREFIX +
                Jwts.builder()
                        .setSubject(userId)
                        .setExpiration(new Date(date.getTime() + TOKEN_TIME))
                        .setIssuedAt(date)
                        .signWith(key, signatureAlgorithm)
                        .compact();
    }

지금

public String createToken(String username, UserRoleEnum roleEnum) {
        Date date = new Date();

        return BEARER_PREFIX +
            Jwts.builder()
                .setSubject(username)
                .claim(AUTHORIZATION_KEY, roleEnum)
                .setExpiration(new Date(date.getTime() + TOKEN_TIME))
                .setIssuedAt(date)
                .signWith(key, signatureAlgorithm)
                .compact();
    }

UserController

@PostMapping("/login")
    public ResponseEntity<CommonResponseDto> login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
        try {
            userService.login(loginRequestDto);
            jwtUtil.addJwtToCookie(jwtUtil.createToken(loginRequestDto.getEmail(), UserRoleEnum.USER), response);
            return ResponseEntity.ok().body(new CommonResponseDto("로그인 성공", HttpStatus.OK.value()));
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(new CommonResponseDto(e.getMessage(), HttpStatus.BAD_REQUEST.value()));
        }
    }

UserService

public void login(LoginRequestDto loginRequestDto) {
        String email = loginRequestDto.getEmail();
        String password = loginRequestDto.getPassword();
        // userId 검색
        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> new IllegalArgumentException("등록된 유저가 없습니다."));
        // 비밀번호 확인
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
        }
    }

authorization 쿠키까지 만드는데는 성공함

근데 이제 Post가 안됨 ㅎㅎ

@AuthenticationPrincipal UserDetailsImpl userDetails으로 넘겨주는 부분은 어디일까?

거긴 문제없었음

Filterchain에서 이 부분으로 아직 하고있었어서 안됐나봄

String tokenValue = jwtUtil.getJwtFromHeader(request);

@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
        FilterChain chain) throws ServletException, IOException {

        String tokenValue = jwtUtil.getJwtFromHeader(request);
        if (StringUtils.hasText(tokenValue)) {
            if (!jwtUtil.validateToken(tokenValue)) {
                log.error("token 에러");
                return;
            }
            Claims info = jwtUtil.getUserInfoFromToken(tokenValue);
            try {
                setAuthentication(info.getSubject());
            } catch (Exception e) {
                log.error(e.getMessage());
                return;
            }
        }
        chain.doFilter(request, response);
    }

오 쿠키로 로그인 바꾸는데 성공함

이제 쿠키를 삭제하기만 하면 됨

 

@RequestMapping("/logout")
    public ResponseEntity<CommonResponseDto> logout(HttpServletRequest req) {
        try {
            userService.logout(req);
            return ResponseEntity.ok().body(new CommonResponseDto("로그아웃 성공", HttpStatus.OK.value()));
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(new CommonResponseDto(e.getMessage(), HttpStatus.BAD_REQUEST.value()));
        }
    }

로그아웃 이렇게 해봄

Service단에 가져가는게 웃기긴 한데…

public void logout(HttpServletRequest req) {
        Cookie[] cookies = req.getCookies();
        if(cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("Authorization")) {
                    cookie.setMaxAge(0);
                }
            }
        }
    }

근데 로그아웃이 안됨 ㅋㅋㅋㅋ

아 쿠키cookie.setMaxAge(0)

해서 response에다가 다시 돌려보내야 되나봄

public void logout(HttpServletRequest req, HttpServletResponse res) {
        Cookie[] cookies = req.getCookies();
        if(cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("Authorization")) {
                    cookie.setMaxAge(0);
                    res.addCookie(cookie);
                }
            }
        }
    }

이렇게 해도 쿠키가 그냥 그대로 남아있음

그럼 내용을 빈칸으로 해보는건 어때

cookie.setValue("");

빈칸으로 날려보내봄

흠 아닌가봄 저 함수를 controller단 아니고

jwtUtil같은데다 잘 위치시키면 될거같기도

@RequestMapping("/logout")
    public ResponseEntity<CommonResponseDto> logout(HttpServletRequest req, HttpServletResponse res) {
        try {
            userService.logout(req,res);
            return ResponseEntity.ok().body(new CommonResponseDto("로그아웃 성공", HttpStatus.OK.value()));
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(new CommonResponseDto(e.getMessage(), HttpStatus.BAD_REQUEST.value()));
        }
    }
public void logout(HttpServletRequest req, HttpServletResponse res) {
        Cookie[] cookies = req.getCookies();
        if(cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("Authorization")) {
                    cookie.setMaxAge(0);
                    cookie.setValue("");
                    res.addCookie(cookie);
                }
            }
        }
    }

근데 지금보니 login도 jwtUtil을 부르고있음

@PostMapping("/login2")
    public ResponseEntity<CommonResponseDto> login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
        try {
            userService.login(loginRequestDto);
            jwtUtil.addJwtToCookie(jwtUtil.createToken(loginRequestDto.getEmail(), UserRoleEnum.USER), response);
            return ResponseEntity.ok().body(new CommonResponseDto("로그인 성공", HttpStatus.OK.value()));
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(new CommonResponseDto(e.getMessage(), HttpStatus.BAD_REQUEST.value()));
        }
    }

이렇게 바꿔봄, 여전히 안 됨

HttpServletResponse는 어디로 가는거지??

cookie를 넣었는데 cookie가 cookies에 들어가지긴 하는데

로그인 에러가 뜸

콘솔 확인해보니 이러고 있다

2023-12-09T21:29:55.751+09:00 ERROR 11144 --- [nio-8080-exec-2] c.r.sunlightmarket.global.jwt.JwtUtil    : 만료된 JWT token 입니다.
2023-12-09T21:29:55.752+09:00 ERROR 11144 --- [nio-8080-exec-2] JWT 검증 및 인가                              : token 에러
2023-12-09T21:30:08.657+09:00 ERROR 11144 --- [nio-8080-exec-3] c.r.sunlightmarket.global.jwt.JwtUtil    : 만료된 JWT token 입니다.
2023-12-09T21:30:08.657+09:00 ERROR 11144 --- [nio-8080-exec-3] JWT 검증 및 인가                              : token 에러
2023-12-09T21:30:44.619+09:00 ERROR 11144 --- [nio-8080-exec-8] c.r.sunlightmarket.global.jwt.JwtUtil    : 만료된 JWT token 입니다.
2023-12-09T21:30:44.619+09:00 ERROR 11144 --- [nio-8080-exec-8] JWT 검증 및 인가                              : token 에러
@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
        FilterChain chain) throws ServletException, IOException {

        String tokenValue = jwtUtil.getTokenFromRequest(request);
        if (StringUtils.hasText(tokenValue)) {
            if (!jwtUtil.validateToken(tokenValue)) {
                log.error("token 에러");
                return;
            }
            Claims info = jwtUtil.getUserInfoFromToken(tokenValue);
            try {
                setAuthentication(info.getSubject());
            } catch (Exception e) {
                log.error(e.getMessage());
                return;
            }
        }
        chain.doFilter(request, response);
    }

저부분에서 validateToken이 잘 안 되고 있음

public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException | SignatureException e) {
            log.error("유효하지 않는 JWT token 입니다.");
        } catch (ExpiredJwtException e) {
            log.error("만료된 JWT token 입니다.");
        } catch (UnsupportedJwtException e) {
            log.error("지원되지 않는 JWT token 입니다.");
        } catch (IllegalArgumentException e) {
            log.error("잘못된 JWT token 입니다.");
        }
        return false;
    }

이부분은 전이랑 똑같은데?

public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException e) {
            log.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
        } catch (ExpiredJwtException e) {
            log.error("Expired JWT token, 만료된 JWT token 입니다.");
        } catch (UnsupportedJwtException e) {
            log.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
        } catch (IllegalArgumentException e) {
            log.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
        }
        return false;
    }
public Claims getUserInfoFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    }

헤더에 있는 Cookies의 cookie지우고 하니까 되긴 함

근데 로그인을 유저 2로 새로 할때도 Cookie를 삭제해줘야 될 거 같은데 그게 안되는듯 지금?

어 근데 login을 새로 하면 Authorization 쿠키가 덮어씌워지는 거 같음!!

그렇다면 login이랑 똑같은 방법으로

Authorization 쿠키를 만들어서 넣어주면 로그아웃이 되지 않을까..?

jwtUtil.logout(res,req);

jwtUtil.logout(res);
public void logout(HttpServletResponse res) {

        Cookie cookie = new Cookie(AUTHORIZATION_HEADER, null);
        cookie.setPath("/");

        // Response 객체에 Cookie 추가
        res.addCookie(cookie);
    }

오 로그아웃 기능 구현 성공함!!

 

'Developing > 개발일지' 카테고리의 다른 글

2023-12-12, Today I Learned  (0) 2023.12.12
2023-12-11, Today I Learned  (0) 2023.12.11
2023-12-07, Today I Leanred  (1) 2023.12.07
2023-12-06, Today I Leanred  (2) 2023.12.06
2023-12-05, Today I Learned  (0) 2023.12.05