Spring Framework (10) - 마이페이지 구현 57

jaehoon ahn·2025년 12월 9일

Spring boot

목록 보기
8/20
post-thumbnail

⇒ 위와 같은 주소 기능을 다음과 같이 구현

다음 주소 API

Daum 우편번호 서비스

예제 메뉴

⇒ 다양한 예제가 있지만, 사용자가 선택한 값 이용하기 적용

  • 코드
    <input type="text" id="sample6_postcode" placeholder="우편번호">
    <input type="button" onclick="sample6_execDaumPostcode()" value="우편번호 찾기"><br>
    <input type="text" id="sample6_address" placeholder="주소"><br>
    <input type="text" id="sample6_detailAddress" placeholder="상세주소">
    <input type="text" id="sample6_extraAddress" placeholder="참고항목">
    
    <script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
    <script>
        function sample6_execDaumPostcode() {
            new daum.Postcode({
                oncomplete: function(data) {
                    // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
    
                    // 각 주소의 노출 규칙에 따라 주소를 조합한다.
                    // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
                    var addr = ''; // 주소 변수
                    var extraAddr = ''; // 참고항목 변수
    
                    //사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
                    if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
                        addr = data.roadAddress;
                    } else { // 사용자가 지번 주소를 선택했을 경우(J)
                        addr = data.jibunAddress;
                    }
    
                    // 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
                    if(data.userSelectedType === 'R'){
                        // 법정동명이 있을 경우 추가한다. (법정리는 제외)
                        // 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
                        if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
                            extraAddr += data.bname;
                        }
                        // 건물명이 있고, 공동주택일 경우 추가한다.
                        if(data.buildingName !== '' && data.apartment === 'Y'){
                            extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                        }
                        // 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
                        if(extraAddr !== ''){
                            extraAddr = ' (' + extraAddr + ')';
                        }
                        // 조합된 참고항목을 해당 필드에 넣는다.
                        document.getElementById("sample6_extraAddress").value = extraAddr;
                    
                    } else {
                        document.getElementById("sample6_extraAddress").value = '';
                    }
    
                    // 우편번호와 주소 정보를 해당 필드에 넣는다.
                    document.getElementById('sample6_postcode').value = data.zonecode;
                    document.getElementById("sample6_address").value = addr;
                    // 커서를 상세주소 필드로 이동한다.
                    document.getElementById("sample6_detailAddress").focus();
                }
            }).open();
        }
    </script>

다음 주소 api 추가하기

<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>

js코드 추가

// 다음 주소 API
function execDaumPostcode() {
  new daum.Postcode({
    oncomplete: function (data) {
      // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

      // 각 주소의 노출 규칙에 따라 주소를 조합한다.
      // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
      var addr = ""; // 주소 변수

      //사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
      if (data.userSelectedType === "R") {
        // 사용자가 도로명 주소를 선택했을 경우
        addr = data.roadAddress;
      } else {
        // 사용자가 지번 주소를 선택했을 경우(J)
        addr = data.jibunAddress;
      }

      // 우편번호와 주소 정보를 해당 필드에 넣는다.
      document.getElementById("postcode").value = data.zonecode;
      document.getElementById("address").value = addr;
      // 커서를 상세주소 필드로 이동한다.
      document.getElementById("detailAddress").focus();
    },
  }).open();
}

⇒ 참고항목은 구현하지 않았기 때문에, 참고항목 관련 코드를 삭제하였다.

⇒ 위와 같이 반영되는 것을 확인할 수있다.

@SessionAttributes

@SessionAttributes의 역할
 * - Model에 추가된 속성 중 key 값이 일치하는 속성을 session scope로 변경하는 어노테이션
 * - 클래스 상단에 @SessionAttributes({ "loginMember" })

@SessionAttribute

@SessionAttribute의 역할
 * - @SessionAttributes를 통해 session에 등록된 속성을 꺼내올 때 사용하는 어노테이션
 * - 메서드의 매개변수
// ex)
@GetMapping("info") // /myPage/info GET 방식 요청 매핑
	public String info(@SessionAttribute("loginMember") Member loginMember, Model model) {
.
.
}

로그인 되어서 마이페이지로 갔을 때, 새로고침하면 에러 발생

⇒ 새로고침이 되면서 session이 초기화 되면서 session이 사라짐

⇒ 또한, 다른 사용자가 접근할 수도 있기 때문에 막아줘야 함

  • 필터
    • 걸래내는 것 + 추가하는 것도 가능한 것을 의미
  • 스프링에도 Filter가 존재
    • 스프링에만 있는게 아니라, servlet단에 존재하는 것
    • 즉, Filter는 Spring스펙이 아닌 Servlet 스펙을 의미
  • Filter는 1번과 7번 사이에서 동작함
    • Client에서 요청이 들어와서 Dispatcher Servlet에 들어가기 전,
    • 그리고 Dispatcher Servlet에서 Client로 가기 전에 동작함
  • 로그인이 안되어있을 때, 특정 주소로 요청을 할 때, 막아줘야 하는 LoginFilter를 만들 것

Filter

LoginFilter.java

package edu.kh.project.common.filter;

import java.io.IOException;

import edu.kh.project.member.model.dto.Member;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

/*
 * Filter : 요청, 응답 시 걸러내거나 추가할 수 있는 객체
 * 
 * [필터 클래스 생성 방법]
 * 1. jakarta.servlet.Filrer 인터페이스 상속받기
 * 2. doFilter() 메서드 오버라이딩
 * 
 * */

// 로그인이 되어있지 않은 경우, 특정 페이지를 접근할 수 없도록 필터링하기
public class LoginFilter implements Filter {

	// 필터 동작을 정의하는 메서드
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// ServletRequest : HttpServletRequest의 부모타입
		// ServletResponse : HttpServletResponse 의 부모타입

		// session이 필요함 -> 왜 필요한가? -> loginMember가 session에 담기기 때문

		// HttpServletRequest 형태(자식형태)로 다운캐스팅

		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;

		// 현재 요청의 URI를 가져옴
		String path = req.getRequestURI(); // /myPage/profile

		// 요청 URI가 "/myPage/profile"로 시작하는지 확인
		if (path.startsWith("/myPage/profile/")) {
			// 필터를 통과하도록 함
			// FilterChain : 다음 필터로 체이닝, 또는 다음 필터가 없다면 DispatcherServler로 보냄

			// 요청 URI 값이 "/myPage/profile"로 시작하는지 확인
			// 해당 요청은 막아주지 않고 통과시킬 것
			// 파일 업로드할 때 문제가 발생함
			// 마이페이지에서 각종 이미지는 서버에 요청을 보내서 가져오는 것
			// 그리고나서 화면에 뿌려주는 것
			// 근데, 프로필 이미지는 서버에 요청을 보낼 때, /myPage/profile/스폰지밥.jpg을 사용할 것
			// 로그인을 하지 않았어도, 게시판 글을 확인할 수 있고, 이미지를 볼 수 있어야 함
			// 하지만, 로그인이 되어있지 않은 상태에서 다른 유저의 프로필 이미지가 다 깨짐
			// 이미지를 가져오는 서버 매핑이 /myPage/profile...로 시작함

			chain.doFilter(request, response);
			// DispatcherServler로 보냄
			// 즉, 다음 필터가 없다는 것
			// 필터를 통과한 후 아래에 문장이 실행되지 않도록 return해줌
			return;
		}
		// session 객체 얻어오기

		HttpSession session = req.getSession();

		// 세션에서 로그인한 회원 정보를 꺼내옴
		// loginMember가 있는지, null인지 확인하기
		if (session.getAttribute("loginMember") == null) {
			// 로그인이 되어있지 않은 상태를 의미
			resp.sendRedirect("/loginError");
		} else {
			// -> 로그인이 되어 있는 상태
			
			// 다음 필터로 이동하던지, 혹은 다음 필터가 없다면 DispatcherServlet로 요청과 응답객체를 전달
			chain.doFilter(request, response);
		}

	}

}

FilterConfig.java

package edu.kh.project.common.config;

import java.util.Arrays;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import edu.kh.project.common.filter.LoginFilter;

@Configuration // 서버가 켜질 때 클래스 내 모든 메서드가 실행되도록 하는 어노테이션 + 빈등록
public class FilterConfig {
	// 언제 LoginFilter가 동작할지 정의해주면 됌

	// 만들어 놓은 LoginFilter 클래스가 언제 적용될 지 설정하는 것
	@Bean // 반환된 객체를 Bean으로 등록 : 
	// LoginFilter로 타입을 제한한 객체를 Bean으로 등록한다.
	public FilterRegistrationBean<LoginFilter> loginFilter() {
		// FilterRegistrationBean : 필터를 Bean으로 등록하는 객체
		FilterRegistrationBean<LoginFilter> filter = new FilterRegistrationBean<>();
		// 사용할 필터 객체 세팅
		filter.setFilter(new LoginFilter());
		
		// 필터가 동작할 URL을 세팅
		// /myPage/*  : /myPage로 시작하는 모든 요청
		String[] filteringURL = {"/myPage/*", "/chatting/*", "/editBoard/*"};
		
		// String[]을 List 변환
		// Arrays.asList(배열) -> List 변환
		filter.setUrlPatterns(Arrays.asList(filteringURL));
		
		// 필터 이름 지정
		filter.setName("loginFilter");
		
		// 필터 순서 지정
		filter.setOrder(1);
		
		return filter; // 반환된 객체가 Bean 등록
	}

}

⇒ Filter를 통해 막아놓은 곳을 접속하면 위와 같이 alert창이 뜨면서 막아준다.

이미지 요청 경로 : /myPage/profile/2025
로그인 여부 관련이 없는 요청 /myPage/profile/(통과)

프로필 변경 화면 이동 요청 : /myPage/profile → /myPage/* (LoginFilter에 의해 걸러짐)

0개의 댓글