240820 내일배움캠프 백엔드 Java 6기 TIL : Spring 숙련강의 Summary : 회원가입&로그인 설계, RestTemplate

박대현·2024년 8월 20일
0

사용자 관리하기

회원가입 구현

회원가입 설계

  • @Enumerated(value = EnumType.STRING)
    • EnumType을 DB 컬럼에 저장할 때 사용하는 애너테이션
    • EnumType.STRING 옵션을 사용하면 Enum의 이름을 DB에 그대로 저장

패스워드 암호화 이해

  • 패스워드를 저장할때, '단방향' 암호 알고리즘 사용이 필요
    • 단방향 : 복호화 X
    • 양방향 : 복호화 O
  • 패스워드 확인절차 : 가입시 등록한 패스워드의 암호문 vs 로그인시 입력한 패스워드의 암호문
if(!passwordEncoder.matches("사용자가 입력한 비밀번호", "저장된 비밀번호")) {
		   throw new IllegalAccessError("비밀번호가 일치하지 않습니다.");
 }

Filter

Filter란?

  • Client로 부터 오는 요청과 응답에 대해 최초/최종 단계
    • 요청과 응답의 정보를 변경, 부가적인 기능을 추가 가능
  • 주로 범용적으로 처리해야 하는 작업에 활용(로깅, 보안 처리, 인증, 인가)
    • Filter를 사용하면 인증, 인가와 관련된 로직을 비즈니스 로직과 분리하여 관리할 수 있다는 장점

Filter Chain

  • 여러개의 필터가 체인형식으로 묶여서 처리될 수 있음

적용

  1. Request URL Logging
@Slf4j(topic = "LoggingFilter") // 터미널에 log.info가 찍힐때 구역이름?으로써 뜸
@Component
@Order(1) // 필터중 1순위로 실행
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 전처리
        HttpServletRequest httpServletRequest = (HttpServletRequest) request; // Casting
        String url = httpServletRequest.getRequestURI();
        log.info(url);

        chain.doFilter(request, response); // 다음 Filter 로 이동

        // 후처리
        log.info("비즈니스 로직 완료");
    }
}
  1. 인증 및 인가 처리 필터
@Slf4j(topic = "AuthFilter") // 터미널에 log.info가 찍힐때 구역이름?으로써 뜸
@Component
@Order(2) //필터중 2순위로 실행
public class AuthFilter implements Filter { //interface

    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;

    public AuthFilter(UserRepository userRepository, JwtUtil jwtUtil) {
        this.userRepository = userRepository;
        this.jwtUtil = jwtUtil;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
        HttpServletRequest httpServletRequest = (HttpServletRequest) request; //casting
        String url = httpServletRequest.getRequestURI();

        if (StringUtils.hasText(url) &&
                (url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
        ) {
            // 회원가입, 로그인 관련 API 는 인증 필요없이 요청 진행
            chain.doFilter(request, response); // 다음 Filter 로 이동
        } else {
            // 나머지 API 요청은 인증 처리 진행
            // 토큰 확인
            String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);
            //dispatcherservelet 뒷단에 있는 Controller에서는 @CookieValue로 쉽게 가져올수 있었지만,
            //앞단에 있는 filter에서는 메서드로 직접 가져와야함

            if (StringUtils.hasText(tokenValue)) { // 토큰이 존재하면 검증 시작
                // JWT 토큰 substring
                String token = jwtUtil.substringToken(tokenValue);

                // 토큰 검증
                if (!jwtUtil.validateToken(token)) {
                    throw new IllegalArgumentException("Token Error");
                }

                // 토큰에서 사용자 정보 가져오기
                Claims info = jwtUtil.getUserInfoFromToken(token);

				//사용자 존재하는지 확인(토큰 발급때는 있었어도, 검증할때는 사용자가 없을수 있음)
                User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
                        new NullPointerException("Not Found User")
                );

                request.setAttribute("user", user); //Controller에 필요한 user객체 할당
                chain.doFilter(request, response); // 다음 Filter 로 이동
            } else {
                throw new IllegalArgumentException("Not Found Token");
            }
        }
    }

}
// 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)) {//Cookie 목록에서 JWT가 저장된 Cookie 찾기
                try {
                    return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode 되어 넘어간 Value 다시 Decode
                } catch (UnsupportedEncodingException e) {
                    return null;
                }
            }
        }
    }
    return null;
}

RestTemplate

RestTemplate이란 무엇일까?

Server To Server

  • RestTemplate : 서버에서 다른 서버로 간편하게 요청할 수 있도록 하는 기능

RestTemplate의 Get 요청

Get 요청 방법(getForEntity)

  • Client 입장
  1. RestTemplate 주입
private final RestTemplate restTemplate;

// RestTemplateBuilder의 build()를 사용하여 RestTemplate을 생성합니다. 평범한 생성자와 약간 다름
public RestTemplateService(RestTemplateBuilder builder) {
    this.restTemplate = builder.build();
}
  1. 요청 받은 검색어를 Query String 방식으로 Server 입장의 서버로 RestTemplate를 사용하여 요청
public ItemDto getCallObject(String query) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder //uri쉽게 만드는 메서드
            .fromUriString("http://localhost:7070")
            .path("/api/server/get-call-obj")
            .queryParam("query", query)
            .encode()
            .build()
            .toUri();
    log.info("uri = " + uri);

	// RestTemplate의 getForEntity는 Get 방식으로 해당 URI의 서버에 요청을 진행합니다.
    // 첫 번째 파라미터에는 URI, 두 번째 파라미터에는 전달 받은 데이터와 매핑하여 인스턴스화할 클래스의 타입을 주면됩니다.
    // 요청의 결과값에 대해서 직접 JSON TO Object를 구현할 필요없이 RestTemplate을 사용하면 자동으로 처리 해줍니다.
    ResponseEntity<ItemDto> responseEntity = restTemplate.getForEntity(uri, ItemDto.class);

    log.info("statusCode = " + responseEntity.getStatusCode());

	// response.getBody() 를 사용하여 두 번째 파라미터로 전달한 클래스 타입으로 자동 변환된 객체를 가져올 수 있습니다.
    return responseEntity.getBody();
}

요청한 item이 여러 개라면?(다건)

  • Client 입장
public List<ItemDto> getCallList() {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/get-call-list")
            .encode()
            .build()
            .toUri();
    log.info("uri = " + uri);

	// 결과값이 다중 JSON으로 넘어오기 때문에 String으로 받음
    ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);

/*
{	//responseEntity내부
	"items":[
		{"title":"Mac","price":3888000},
		{"title":"iPad","price":1230000},
		{"title":"iPhone","price":1550000},
		{"title":"Watch","price":450000},
		{"title":"AirPods","price":350000}
	]
}
*/

    log.info("statusCode = " + responseEntity.getStatusCode());
    log.info("Body = " + responseEntity.getBody());

    return fromJSONtoItems(responseEntity.getBody()); //JSON처리를 도와주는 라이브러리 사용
}
public List<ItemDto> fromJSONtoItems(String responseEntity) {
    JSONObject jsonObject = new JSONObject(responseEntity); //문자열 정보를 JSONObject로
    JSONArray items  = jsonObject.getJSONArray("items"); // JSONObject에서 items 배열 꺼내기
    List<ItemDto> itemDtoList = new ArrayList<>();

	//JSONArray로 for문 돌면서 상품 하나씩 ItemDto로 변환하기
    for (Object item : items) {
        ItemDto itemDto = new ItemDto((JSONObject) item); //JSONObject에서 ItemDto로 변환하기
        itemDtoList.add(itemDto);
    }

    return itemDtoList;
}

RestTemplate의 Post 요청

Post 요청 방법(postForEntity)

  • Client 입장
public ItemDto postCall(String query) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/post-call/{query}") //@PathVariable방식
            .encode()
            .build()
            .expand(query) //{query}에 들어갈 데이터 동적처리
            .toUri();
    log.info("uri = " + uri);

    User user = new User("Robbie", "1234");

    ResponseEntity<ItemDto> responseEntity = restTemplate.postForEntity(uri, user, ItemDto.class); 

    log.info("statusCode = " + responseEntity.getStatusCode());

    return responseEntity.getBody();
}

RestTemplate의 exchange

요청 Header에 정보를 추가하고 싶다면?

  • RestTemplate으로 요청을 보낼 때 Header에 특정 정보를 같이 전달 하고 싶을 때 사용
  • Client 입장
public List<ItemDto> exchangeCall(String token) {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:7070")
            .path("/api/server/exchange-call")
            .encode()
            .build()
            .toUri();
    log.info("uri = " + uri);

    User user = new User("Robbie", "1234");

    RequestEntity<User> requestEntity = RequestEntity
            .post(uri)
            .header("X-Authorization", token)
            .body(user);

    ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);

    return fromJSONtoItems(responseEntity.getBody());
}

0개의 댓글