본문 바로가기
Spring Boot

230223 Spring Boot 세션-인터셉터

세션


로그인->

1.인터셉터
2.스프링 security모듈

security는 내용이 방대하다. 공부하는데 시간이 오래 걸림.
비밀번호 암호화하는 기능도 있다.


인터셉터

 


웹 서비스는 HTTP 프로토콜을 기반으로 하는데, 요청에 대한 응답 후 관계를 끊는다.
즉, 인증되지 않은 사용자는 모든 페이지에서 인증 과정을 거쳐야 한다.
이런 불편함 때문에 지속적인 인증 수단으로 세션과 쿠키를 사용한다.

스프링MVC의 세션을 사용하는 방법
1.HttpServletRequest이용
2.HttpSession 이용

세션의 주요 기능

setAttribute() : 세션 객체에 속성을 저장한다.
getAttribute() : 세션 객체에 저장된 속성을 반환한다.
removeAttribute() : 세션 객체에 저장된 속성을 제거한다.
invalidate() : 세션 객체의 모든 정보를 삭제한다.



타임리프는
session.이름으로 바로 사용 가능
[[${session.이름}]]



비밀번호 암호화
pw를 키로 사용. 
복호화 불가능하게.
암호화된 키를 저장.

로그인할때 똑같은 방식으로 암호화를 해서 똑같은 pw면 똑같이 암호화된 키일 테니 동일한 문자열이 됨.



요즈음 백과 프론트를 연결할 땐 세션이 아닌 토큰 사용.
클라이언트 서버에서 사용할 수 있는 json웹토큰을 header에 실어서 보냄.


mypage와 info는 특정 유저만 접근 가능하도록

세션검사

if(session.getAttribute("user_id")==null) {
	return "redirect:/user/login";
}



그런데 페이지가 수십개라면 수십개의 컨트롤러 메서드에서 매번 이걸 해야 한다.
->filter사용 or 인터셉터 사용

filter는 아예 맨 처음에 진입장벽을 세움.
spring의 security와 함께 사용한다.

 

filter-디스패쳐 서블릿을 거치기 전에 처리
Intercepter-컨트롤러 전 후로 요청을 처리


더 쉬운 방법으로 Intercepter 제공
컨트롤러로 들어가기 직전에 가로채서 검사를 한다.
장벽의 위치가 다름. filter는 아예 앞, Intercepter는 컨트롤러에 진입할 때.


+)AOP
메서드나 컨트롤러가 실행될 때 마다 검사같은걸 하고 싶을 때 사용하는 기능.
비즈니스 로직(메서드)에서 세밀하게 처리
로그와 Transaction이 이 개념과 연결됨.


인터셉터의 사용
스프링에서 제공하는 HandlerInterceptor인터페이스를 상속 받아 사용

 

  • preHandle() : 컨트롤러에 진입하기 전에 요청을 가로챔. false를 리턴하면 다음 내용을 수행하지 않음
  • postHandle() : 컨트롤러가 수행되고 나서 실행.
  • afterCompletion() : 최종 view 화면으로 넘어가기 전에. 리졸버뷰에 뷰를 전송한 후 실행됨. 굳이 사용되진 않음. 

인터셉터 클래스 선언
implements HandlerInterceptor-반드시 상속받는다.

이 인터페이스의 메서드들은 추상메서드가 아니라 default메서드이다. 모든 메서드를 상속할 필요 없다.

 


UserController.java

package com.simple.basic.controller;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.simple.basic.command.UsersVO;

@Controller
@RequestMapping("/user")
public class UserController {

	@GetMapping("/login")
	public String login() {
		return "user/login";
	}
	
	//로그인기능
	@PostMapping("/login")
	public String loginForm(UsersVO vo, HttpSession session) {
		//select * from 유저 where id=? and pw=?
		
		//서비스영역 호출(로그인 성공). 없으면 null값을 나타냄
		//UsersVO vo = 서비스~~
		//성공이라고 가정. 비밀번호는 가지고 나올 필요도 없다.
		UsersVO userVo = new UsersVO();
		userVo.setId("aaa");
		userVo.setName("1234");
		
		
		//로그인 성공-세션을 이용해서 인증값 
		if(userVo!=null) {
			session.setAttribute("user_id", userVo.getId()); //토큰
			return "redirect:/user/mypage"; //로그인성공
		}
		
		return "user/login"; //로그인실패
	}
	
	
	//특정 유저들만 접근할 수 있는 페이지
	@GetMapping("/mypage")
	public String mypage(/*HttpSession session*/) {
		//세션검사
		//if(session.getAttribute("user_id")==null) {
		//	return "redirect:/user/login";
		//}
		System.out.println("컨트롤러실행");
		return "user/mypage";
	}
	
	//특정 유저들만 접근할 수 있는 페이지
	@GetMapping("/info")
	public String info() {
		return "user/info";
	}
}

login페이지에서 loginForm으로 요청을 보낸다. 

원래는 db에 회원정보들을 저장하고 서비스-매퍼를 거쳐 select구문으로 vo값을 가져온다. 지금은 간단하게 UsersVO에 setter로 지정.

 

vo가 null이 아니다. 즉 로그인을 성공해서 select해서 가져온 내용이 담겨 있으면 세션을 보낸다. return은 redirect로.

로그인에 실패하면 로그인페이지로 다시 return.

 

세션검사를 컨트롤러에서 할 수도 있지만, 페이지가 수십개라면 검사하는 구문을 전부 적어야 한다. 때문에 인터셉트 이용.


UserAuthHandler.java --(preHandler : 컨트롤러 진입 전에 실행)

package com.simple.basic.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;

public class UserAuthHandler implements HandlerInterceptor{

	/*
	 * 1. HandlerInterceptor를 상속받습니다.
	 * 
	 * preHandle()-컨트롤러 진입전에 실행
	 * postHandle()-컨트롤러 수행 후에 실행
	 * afterCompletion()-화면으로 가기 직전에 수행
	 * 
	 * 2. 인터셉터클래스를 bean으로 등록
	 * 
	 */

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("인터셉터실행");
		//현재세션을 얻음
		HttpSession session=request.getSession();
		String user_id=(String)session.getAttribute("user_id");
		
		if(user_id==null) {//로그인이 안됨
			response.sendRedirect(request.getContextPath()+"/user/login"); //로그인페이지로 리다이렉션. 절대경로로 적는다.
			return false;//컨트롤러를 실행하지 않음
		}
		
		return true; //컨트롤러가 그대로 실행
	}
	
}

public boolean preHandler(){} 메서드 오버라이딩.

return이 true면 컨트롤러가 그대로 실행, false면 컨트롤러를 실행하지 않음.

 

request, response가 매개변수로 지정되어 있다.

request를 사용해 현재 세션을 얻는다.

 

얻은 세션이 null이 아니라면 로그인페이지로 리다이렉션. 절대경로로 적는다.


UserSuccessHandler.java  --(postHandler : 컨트롤러 수행 후에 실행)

package com.simple.basic.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class UserSuccessHandler implements HandlerInterceptor{

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		
		//modelAndView는 컨트롤러에서 model에 담은 데이터도 확인가능.
		System.out.println("포스트핸들러:" + request.getRequestURI());
		
	}
	
}

postHandler()메서드는 오버라이딩하면 ModelAndView 변수가 있다. 이 변수를 통해 컨트롤러에서 model에 담은 데이터도 확인가능하다.

 


WebMvcConfigurer 인터페이스가 제공하는 addInterceptors메서드 오버라이딩 하고 내용 작성
스프링 내부적으로 사용할 수 있는 다양한 기능 제공. 이것들은 차자보면서 사용.

 

@Configuration, @Bean어노테이션
부가적으로 bean을 설정하고 싶으면 WebConfig클래스 사용. @Configuration으로 등록한다.

 

 

 

WebConfig.java

package com.simple.basic.config;

import java.util.Arrays;

//import org.springframework.context.ApplicationContext;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.simple.basic.util.UserAuthHandler;
import com.simple.basic.util.UserSuccessHandler;

@Configuration //개별적인 스프링 빈 설정 파일
public class WebConfig implements WebMvcConfigurer{
	
//	//빈을 보관하고 있는 장소(스프링 컨테이너)
//	@Autowired
//	ApplicationContext applicationContext;
	
	@Bean
	public UserAuthHandler userAuthHandler() {
		return new UserAuthHandler(); //빈으로 등록
	}
	
	@Bean
	public UserSuccessHandler userSuccessHandler() {
		return new UserSuccessHandler();
	}

	//webMvcConfigurer가 제공해주는 인터셉터 추가 메서드 오버라이딩
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(userAuthHandler())//반환이 자기 자신. 부가적인 설정들은 .~로 계속 연결할 수 있음.
		.addPathPatterns("/user/*")//패스경로포함
		.excludePathPatterns("/user/login");//패스경로제외
		//.addPathPatterns("경로").addPathPatterns("경로")로 계속 추가할 수 있다.
		//.addPathPatterns(Arrays.asList("경로","경로","경로"))
		
		
		//경로별로 인터셉트를 다르게 등록...
		//registry.addInterceptor();
		
		registry.addInterceptor(userSuccessHandler()).addPathPatterns("/user/*");
	}
}

@Configuration어노테이션을 통해 스프링 빈 설정파일로 만듦.

@Bean어노테이션으로 빈 호출.

 

 

WebMvcConfigurer를 상속받는다.

WebMvcConfigurer인터페이스가 제공해주는 "인터셉터 추가 메서드"를 오버라이딩한다.

->addIntercepotrs 메서드 상속.

 

매개변수에 InterceptorRegistry 변수 존재.

이 변수의 addInterceptor()함수 사용. 변수로 위에 선언한 Handler빈을 넣는다.

->registry.addInterceptor()함수는 반환이 자기 자신이다. 부가적인 설정들을 .~~로 계속 연결할 수 있다.

  • addPathPatterns() : 인터셉터를 탈 경로를 지정
  • excludePathPatterns() : 인터셉터를 타지 않도록. 패스 경로 제외

 

addPathPatterns().addPathPatterns().addPathPatterns로 계속 추가할 수 있다.

addPathPatterns(Arrays.asList("경로","경로","경로"))도 가능.

 

.addPathPatterns("/user/*");로 user로 시작되는 부분은 다 가능
그런데, login페이지는 세션이 없더라도 들어갈 수 있어야 함.
제외하기
.excludePathPatterns("/user/login");로 패스경로 제외.


인터셉터는 여러개 있어도 된다.
registry.addInterceptor()로 여러개 만들어도 됨.

 

 

 

login페이지와 mypage는 간단하게 구현하였다.

세션이 없으면 이 페이지로 다시 돌아온다.

 

컨트롤러에서 user_id를 세션으로 지정해서 보냈다.



preHandler와 postHandler를 통해 출력되는 콘솔출력문

포스트핸들러:/user/login
인터셉터실행
컨트롤러실행
포스트핸들러:/user/mypage