java 스프링부트 ( spring boot ) / 로그인 상태 유지 ( Cookie, Session, Filter, Interceptor )

김동명·2022년 12월 1일
0

스프링부트

목록 보기
10/19

프로젝트 설정

  • application.properties
#port
server.port=9090

#thymeleaf cache
spring.thymeleaf.cache=false

#encoding
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.force=true
server.servlet.encoding.enabled=true

# 타임리프에서 url을 통해 세션을 유지하는 jsessionid가 생성되는 것을 방지
server.servlet.session.tracking-modes=cookie


시작

1. 쿠키(Cookie)를 이용한 방법

1-1. 기본

  • LoginController.java 수정
    • HttpServletResponse response 추가
    • Cookie 객체 생성
@Controller
@RequiredArgsConstructor
public class LoginController {
	
    	@PostMapping("/login")
	public String login(@ModelAttribute LoginForm form, Model model, RedirectAttributes redirectAttributes , HttpServletResponse response) {
		Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
		
		if( loginMember == null) {
			// 로그인실패
			model.addAttribute("msg", "로그인실패");
			return "login/loginForm";
		}
		// 로그인 성공
        Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
		response.addCookie(idCookie);
        
		redirectAttributes.addFlashAttribute("msg","로그인 성공");
		return "redirect:/";
	}

}

  • HomeController.java 수정
    • @RequiredArgsConstructor 어노테이션 작성
    • Repository 선언
    • homev2 생성
    • 쿠키값을 가지고 화면 표시하도록 하되, required = fales를 통해 필수 값은 아니게끔 설정
@Controller
@RequiredArgsConstructor
public class HomeController {

	private final MemberRepository memberRepository;
    
    ...
    ...
    
	@GetMapping
	public String homev2(@CookieValue(name = "memberId", required = false)Long memberId, Model model) {
		// 로그인한 사용자가 아니라면 home으로 보낸다.
		if ( memberId == null) {
			return "home";
		}
        
		// db 조회
		Member loginMember = memberRepository.findById(memberId);
		// 사용자가 없으면 null 처리 필요
		if(loginMember == null) {
			return "home";
		}
		
		// loginHome : 로그인에 성공한 사람만이 볼 수 있는 화면
		model.addAttribute("member", loginMember);
		return "loginHome";			
	}

}

  • loginHome.html 생성
    • th:text="|로그인 : ${member.name}|"
    • th:onclick="|location.href='@{/items}'|"
    • th:action="@{/logout}"
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<title>Insert title here</title>
</head>
<body>
	<div class="container" style="max-width: 600px">
		<div class="py-5 text-center">
			<h2>홈 화면</h2>
		</div>
		<h4 class="mb-3" th:text="|로그인 : ${member.name}|">로그인 사용자 이름</h4>
		<hr class="my-4">
		<div class="row">
			<div class="col">
				<button class="w-100 btn btn-secondary btn-lg" type="button" th:onclick="|location.href='@{/items}'|">상품 관리</button>
			</div>
			<div class="col">
				<form method="post" th:action="@{/logout}">
					<button class="w-100 btn btn-dark btn-lg" onclick="location.href='items.html'" type="submit"> 로그아웃	</button>
				</form>
			</div>
		</div>
		<hr class="my-4">
	</div>
	<!-- /container -->
</body>
</html>

  • 출력 localhost:9090



1-2. 로그아웃

  • LoginController.java 수정
    • Cookie 값을 null로 처리
    • setMaxAge() : 쿠키 시간 설정
...
    @PostMapping("/logout")
	public String logout(HttpServletResponse response) {
		Cookie cookie = new Cookie("memberId", null);
		cookie.setMaxAge(0);
		response.addCookie(cookie);
		return "redirect:/";
	}
...
  • 출력 ( 로그아웃 클릭 시 )



2. 세션(Session)을 이용한 방법

2-1. 기본

  • mylogin.loginweb.session 패키지 생성 > SessionConst.java 생성
    • 세션에 대한 키값을 정의해주는 class
    • loginMember라는 키값을 정의
    • 키값을 찾아 여기저기 헤맬 필요가 없게 하기 위함
public class SessionConst {
	
	public static final String LOGIN_MEMBER = "loginMember";
}

  • LoginController.java 수정
    • loginv2/ logoutv2 추가
    • loginv2
      - HttpServletRequest request
      - HttpSession session = request.getSession();
    • logoutv2
      - HttpServletRequest request
      - request.getSession(true) - 세션이 있으면 기존 세션을 반환한다. 세션이 없으면 새로운 세션을 생성해서 반환한다.
      - request.getSession(false) - 세션이 있으면 기존 세션을 반환한다. 세션이 없으면 새로운 세션을 생성하지 않고, null을 반환
      - session.invalidate(); : 세션 종료
...    
    @PostMapping("/login") // 세션에 담아주기
	public String loginv2(@ModelAttribute LoginForm form, Model model, RedirectAttributes redirectAttributes , HttpServletRequest request) {
		Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
		
		System.out.println(loginMember);
		
		if( loginMember == null) {
			// 로그인실패
			model.addAttribute("msg", "로그인실패");
			return "login/loginForm";
		}
		// 로그인 성공
		HttpSession session = request.getSession();
		//세션에 로그인 회원정보 보관
		session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);		
		redirectAttributes.addFlashAttribute("msg","로그인 성공");
		return "redirect:/";
	}
	
    ...
    
    	@PostMapping("/logout")
	public String logoutv2(HttpServletRequest request) {
		//세션을 삭제
		HttpSession session = request.getSession(false); 
		if(session != null) {
			session.invalidate();
		}
		return "redirect:/";
	}
}    

  • HomeController.java
    • homev3 추가
      - HttpServletRequest request
      - (Member)session.getAttribute(SessionConst.LOGIN_MEMBER); : 세션 키값 가져오기
...
    	@GetMapping
	public String homev3(HttpServletRequest request, Model model) {
		HttpSession session = request.getSession(false);
		
		// 로그인한 사용자가 아니라면 home으로 보낸다.
		if ( session == null) {
			return "home";
		}
		
		Member loginMember = (Member)session.getAttribute(SessionConst.LOGIN_MEMBER);
		// 사용자가 없으면 null 처리 필요
		if(loginMember == null) {
			return "home";
		}
		
		// loginHome : 로그인에 성공한 사람만이 볼 수 있는 화면
		model.addAttribute("member", loginMember);
		return "loginHome";		
	}
    
}

결과 > 브라우저 내에서 로그인 유지




2-2. @SessionAtrribute 사용

  • HomeController.java
    - homev4 추가
    - @SessionAttribute : Sessionattribute 안에서 member에 값을 넣어줄 값을 찾아서 넣어준다.
    ->
...
    @GetMapping
	public String homev4(@SessionAttribute(name=SessionConst.LOGIN_MEMBER, required= false)Member loginMember , Model model) {
		
		// 사용자가 없으면 null 처리 필요
		if(loginMember == null) {
			return "home";
		}
		
		// loginHome : 로그인에 성공한 사람만이 볼 수 있는 화면
		model.addAttribute("member", loginMember);
		return "loginHome";
	}
}



2-3. 세션유지시간 설정

  • application.properties 수정
    • 시간 : 초단위, 기본은 1800초(30분), 분단위로 설정, 60초보다 작은 값은 안된다.
server.servlet.session.timeout=시간



3. Filter를 이용한 방법

  • 사이트의 기능을 숨길 필요가 있을 경우 조건을 만들어 사용
  • HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 ->컨트롤러

초기 세팅


  • java 파일
    • ItemController.java
    • Item.java
    • ItemRepository.java
    • item 프로젝트에서 가져오기

  • item 프로젝트의 templates.baisc 폴더 복사 붙여넣기 ( 폴더이름 items 변경 )
    • addForm.html
    • eidtForm.html
    • item.html
    • items.html

  • ItemController.java 수정
    • @RequestMapping > ("/items") 수정
    • basic > items 수정
    • 데이터 저장, 종료 메서드 삭제
@Controller
@RequestMapping("/items")
@RequiredArgsConstructor
public class ItemController {

	...
    ...
    
}

  • TestDataInit.java 수정

    • 테스터용 데이터 추가
    • private final ItemRepository itemRepository;
    • itemRepository.save(new Item("testA", 10000, 10));
      itemRepository.save(new Item("testB", 20000, 20));
@Component
@RequiredArgsConstructor
public class TestDataInit {
	
    	...
        
	@PostConstruct
	public void init() {
		// 테스트 데이터 추가	
		itemRepository.save(new Item("testA", 10000, 10));
		itemRepository.save(new Item("testB", 20000, 20));
		
        ...
	}
}



3-1. 기본

컨트롤러에 일일이 조건을 걸어줄 필요 없이, 필터를 사용해서 모든 url에 적용하기


  • loginweb.filter 패키지 생성 > LogFilter.java 생성
    - 필터 적용 확인
import javax.servlet.Filter;

public class LogFilter implements Filter {
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest httpServletRequest = (HttpServletRequest)request;
		String requestURI = httpServletRequest.getRequestURI();
		
		System.out.println("requestURI : " + requestURI); // 필터 전 url값
		
		chain.doFilter(request, response);
		
		System.out.println("responseURI : " + requestURI); // 필터 후 url 값
	}

}

  • loginweb.filter 패키지 > WebConfig.java 생성
    • @Component : 스프링컨테이너에 @Bean을 등록하기 위함
    • setFilter : 필터 등록
    • setOrder : 필터 적용 순서
    • addUrlPatterns : 적용할 URL
@Component
public class WebConfig {

	@Bean
	public FilterRegistrationBean logFilter() {
		FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<Filter>();
		
		filterRegistrationBean.setFilter(new LogFilter()); // LogFilter 등록
		filterRegistrationBean.setOrder(1);
		filterRegistrationBean.addUrlPatterns("/*");		// 모든 url 다 적용
		return filterRegistrationBean;
		
	}
}



3-2. 로그인 필터

필터를 사용해서 로그인해야만 메뉴가 보이게 설정


  • loginweb.filter 패키지 > LoginCheckFilter.java 생성

    • httpResponse.sendRedirect("/login"); : 로그인으로 redirect
public class LoginCheckFilter implements Filter{

	private static final String[] whitelist = {"/", "/members/add", "/login", "/logout", "/css/*"};
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest httpRequest = (HttpServletRequest)request;
		String requestURI = httpRequest.getRequestURI();
		HttpServletResponse httpResponse = (HttpServletResponse)response;
		
		System.out.println("인증 체크 필터 시작");

		if(isLoginCheckPath(requestURI)) {
			System.out.println("인증 체크 로직 실행 : " + requestURI);
			HttpSession session = httpRequest.getSession(false);
			if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
				System.out.println("미 인증 사용자 요청");
				httpResponse.sendRedirect("/login"); 
				return; // 미인증 사용자는 다음으로 진행하지 않고 끝낸다.
			}
		}
		/* 다음 단계로 넘어간다. */
		chain.doFilter(request, response);
	}
    
    /*
    whitelist에 해당하는 url인 경우 인증 체크 x	
    simpleMatch 	: 파라미터 문자열이 특정 패턴에 매칭되는지를 검사함.
    */
    
	private boolean isLoginCheckPath(String requestURI) {
		return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
		
	}

  • WebConfig.java 수정
    • loginCheckFilter() 등록
...
	@Bean
	public FilterRegistrationBean loginCheckFilter() {
		FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<Filter>();
		
		filterRegistrationBean.setFilter(new LoginCheckFilter()); 
		filterRegistrationBean.setOrder(2); 
		filterRegistrationBean.addUrlPatterns("/*");
		return filterRegistrationBean;
	}
 }



4. 사용자 편의 기능

4-1. 로그인 시 홈 화면이 아닌 접근하려고 했던 url로 돌아가기

  • loginCheckFilter.java 수정
    • httpResponse.sendRedirect("/login?redirectURL=" + requestURI) > 파라미터로 받아온 url를 보내주기
public class LoginCheckFilter implements Filter{

	private static final String[] whitelist = {"/", "/members/add", "/login", "/logout", "/css/*"}; 
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
            
            ...
            
            		if(isLoginCheckPath(requestURI)) {
			System.out.println("인증 체크 로직 실행 : " + requestURI);
			HttpSession session = httpRequest.getSession(false);
			if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
				System.out.println("미 인증 사용자 요청");
				httpResponse.sendRedirect("/login?redirectURL=" + requestURI); 
				return; // 미인증 사용자는 다음으로 진행하지 않고 끝낸다.
			}
			
		}
        
        ...
        
}

  • url


  • LoginController.java 추가
    • loginv3 추가
      - > 파라미터에 @RequestParam(defaultValue = "/")String redirectURL 추가
      - > 리턴 return "redirect:" + redirectURL; 수정
      - > redirectURL이 있으면 해당값으로, 없으면 / 로 이동
...    
    @PostMapping("/login") // 세션에 담아주기
	public String loginv3(@ModelAttribute LoginForm form, Model model, RedirectAttributes redirectAttributes , HttpServletRequest request,
			@RequestParam(defaultValue = "/")String redirectURL ) {
		Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
		
		System.out.println(loginMember);
		
		if( loginMember == null) {
			// 로그인실패
			model.addAttribute("msg", "로그인실패");
			return "login/loginForm";
		}
		// 로그인 성공
		HttpSession session = request.getSession();
		//세션에 로그인 회원정보 보관
		session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
		
		redirectAttributes.addFlashAttribute("msg","로그인 성공");
		return "redirect:" + redirectURL;
	}
...



5. 인터셉터

5-1. 기본


- 스프링 인터셉터도 서블릿 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결 할 수 있는 기술이다. 서블릿 필터가 서블릿이 제공하는 기술이라면, 스프링 인터셉터는 스프링 MVC가 제공하는 기술이다.
- 둘다 웹과 관련된 공통 관심 사항을 처리하지만, 적용되는 순서와 범위, 사용방법이 다르다.

- HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 - > 컨트롤러
- 스프링 인터셉터는 디스패처서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출된다.
- 스프링 인터셉터는 스프링 MVC가 제공하는 기능이기 때문에 결국 디스패처 서플릿 이후에 등장하게 된다.
- 정밀한 URL패턴을 적용 할 수 있다.

- HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 - > 컨트롤러
- HandlerInerceptor 인터페이스 사용
- 컨트롤러 호출 전 : preHandle
- 컨트롤러 호출 후 : postHandle
- 요청 완료 이후 : afterCompletion, 뷰가 렌더링 된 이후에 호출된다.


  • loginweb.interceptor 패키지 생성 > LogInterceptor.java 생성
public class LogInterceptor implements HandlerInterceptor{

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		String requestURI = request.getRequestURI();
		System.out.println("[interceptor] requestURI : " + requestURI);
		
		return true; // false -> 진행 x
	}
	
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("[interceptor] postHandle");
	}
	
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("[interceptor] afterCpmpletion");
	}
}

  • WebConfig.java 수정
    • addInterceptor : 인터셉터 등록
    • order() : 인터셉터의 호출 순서를 지정한다. 낮을수록 먼저 호출
    • addPathPatterns( "/**" ) : 인터셉터를 적용할 URL 패턴을 지정한다.
    • excludePathPatterns("/error") : 인터셉터에서 제외할 패턴을 지정한다.
@Component
public class WebConfig implements WebMvcConfigurer{

	@Override
		public void addInterceptors(InterceptorRegistry registry) {
			registry.addInterceptor( new LogInterceptor())
				.order(1)
				.addPathPatterns("/**")
				.excludePathPatterns("/error");
		}
        
}

  • url 패턴
    - addPathPatterns("/sub1/test1", "/sub1/test2")
    - 1개의 "어떠한" 경로에 상관없이 사용하려면 / -> /sub1/
    - 여러개의 경로에 사용하려면 -> sub1/**



5-2. 인터셉터 활용

  • 로그인 되지 않았을 경우 로그인 페이지로 이동 ( 로그인 시 요청한 페이지로 다시 이동 )

  • loginweb.interceptor 패키지 > LoginCheckInterceptor.java 생성
public class LoginCheckInterceptor  implements HandlerInterceptor{

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		String requestURI = request.getRequestURI();
		System.out.println("[interceptor] : " + requestURI);
		HttpSession session = request.getSession(false);
		if( session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
			System.out.println("[미인증 사용자 요청]");
			// 로그인으로 redirect
			response.sendRedirect("/login?redirectURL=" + requestURI);
			return false;
		}
		
		return true;
	}
}


  • WebConfig.java 수정
@Component
public class WebConfig implements WebMvcConfigurer{

	@Override
		public void addInterceptors(InterceptorRegistry registry) {
			registry.addInterceptor( new LogInterceptor())
				.order(1)
				.addPathPatterns("/**")
				.excludePathPatterns("/error");
			
			registry.addInterceptor( new LoginCheckInterceptor())
                .order(2)
                .addPathPatterns("/**") 
                .excludePathPatterns("/", "/members/add", "/login", "/logout", "/css/**");
		}
 }
profile
코딩공부

0개의 댓글