[01.18] 내일배움캠프[Spring] TIL-56

박상훈·2023년 1월 18일
0

내일배움캠프[TIL]

목록 보기
56/72

[01.18] 내일배움캠프[Spring] TIL-56

1. Spring Project

  • 금일 Spring Security 를 적용해서 기본 기능을 구현했다.
  • 기본 기능
    1) 유저 회원가입 -> 역할 분배 ( Customer.Seller,Admin )
    2) 역할에 따른 작업 -> Seller등록, 등록에 따른 물품판매 등록 등..

오늘의 문제점1

  • Spring Security 의 401,403 Error Handling
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.authorizeHttpRequests()
                .requestMatchers("/users/sign").permitAll()
                .requestMatchers("/users/login").permitAll()
                .requestMatchers("/admin/**").hasAnyRole("ADMIN")
                .requestMatchers("/sellers/**").hasAnyRole("SELLER") // Enum형태로 넣으면 인식 못함!
                .requestMatchers("/products/list").permitAll()
                .requestMatchers("/products/list/seller/**").permitAll()
                .anyRequest().authenticated()
                .and().addFilterBefore(new JwtAuthFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class);

        //401 인증과정 실패시 에러처리
        http.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint);
        //403
        http.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);

        return http.build();
    }
  • 역할에 따른 403오류를 터트려 주기 위해서 hasAnyRole을 걸어놨는데, Enum타입으로 그냥 넣었더니 인식을 못했다 -> String Type으로 바꿔주니 인식했다.

CustomAuthenticationEntryPoint

package com.sparta.morningworkout.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.morningworkout.dto.StatusResponseDto;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.OutputStream;

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    private static final StatusResponseDto statusResponseDto =
            new StatusResponseDto(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {

        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.UNAUTHORIZED.value());

        try (OutputStream os = response.getOutputStream()) {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(os, statusResponseDto);
            os.flush();
        }
    }
}

customAccessDeniedHandler

package com.sparta.morningworkout.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.OutputStream;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    private static final SecurityExceptionDto exceptionDto =
            new SecurityExceptionDto(HttpStatus.FORBIDDEN.value(), "접근이 거부되었습니다. 권한이 없습니다");

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, org.springframework.security.access.AccessDeniedException accessDeniedException) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.FORBIDDEN.value());

        try (OutputStream os = response.getOutputStream()) {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(os, exceptionDto);
            os.flush();
        }
    }
}

오늘의 문제점2

  • 페이징 처리를 하는 중, TotalPage의 카운트가 제대로 도출되지 않았다.

해결1

  • 반환 타입이 return new PageImpl<>()형태로 반환해줬기 때문에, PageImple코드를 까보면 pageable,totalPage를 받아야 하는 형태로 구성되어 있었고, 변수로 주니 해결이 됐다.
return new PageImpl<>(products.stream().map(ProductResponseDto::new).collect(Collectors.toList()),pageable,products.getTotalPages());

해결2

  • PageImpl로 굳이 감싸서 반환해야할까? -> 어차피 페이징 처리해서 Page<>형태로 가져오는데 왜?
 Page<Product> products = productRepository.findAllByUserId(user.getId(),pageable);

 return products.map(ProductResponseDto::new);
  • 그냥 어차피 페이징 처리되서 그 안에 total로 count()한 값을 갖고있기 때문에, 이걸 반환해도 된다.

2. 테스트 코드 맛보기

  • 아직 Mock 객체 사용에 서툴기 때문에, 특정 URL요청에 따른 Status.OK가 잘 나오는지 확인했다.

    @Autowired
    MockMvc mockMvc;
    
    
    @Test
    @DisplayName("requestBuyProducts")
    void requestBuyProducts() throws Exception {


        mockMvc.perform(post("/customers/products/1")
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .with(csrf()))
                        .andExpect(status().isOk());
    }

    @Autowired
    private MockMvc mockMvc;
    
    @Test
    @DisplayName("커스터머 불러오기")
    void showCustomerList() throws Exception {

        MultiValueMap<String,String> requestParam = new LinkedMultiValueMap<>();
        requestParam.set("page","1");

        mockMvc.perform(MockMvcRequestBuilders.get("/sellers/customers").params(requestParam)
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .with(csrf()))
                        .andExpect(status().isOk());
    }
profile
기록하는 습관

0개의 댓글