상세 컨텐츠

본문 제목

[스프링2] 13. MVC 3: 세션, 인터셉터, 쿠키

22-23/22-23 Spring 2

by YUZ 유즈 2023. 1. 5. 10:00

본문

728x90

13장의 키워드

세션 보관 클래스

HttpSession

인터셉터 - HandlerInterceptor

쿠키

 

* 이 장에서는 로그인 기능을 이용해 세션, 인터셉터, 쿠키에 대해 설명한다.

 

로그인 처리를 위한 코드 준비

AuthInfo 클래스 : 로그인 성공 후 인증 상태 정보(ID, Email, Name)를 세션에 보관할 때 사용

AuthService 클래스 : 이메일과 비밀번호가 일치하는지 확인 후, AuthInfo 객체를 생성

* 암호 일치 여부를 확인하기 위한 matchPassword() 메서드를 Member클래스에 추가한다.

public class AuthService {
	private MemberDao memberDao;
    
    public void setMemberDao(MemberDao memberDao) {
    	this.memberDao = memberDao;
    }
    
    public AuthInfo authenticate(String email, String password) {
    	Member member = memberDao.selectByEmail(email);
        if (member == null) {
        	throw new WrongIdPasswordException();
        }
        if (!member.matchPassword(password)) {
        	throw new WrongIdPasswordException();
        }
        return new AuthInfo(member.getId(),
        	member.getEmail(),
            member.getName());
    }
}

 

LoginController 클래스 : AuthService를 이용해서 로그인 요청을 처리하는 클래스

LoginCommand 클래스 : 폼에 입력한 값을 전달하는 클래스

LoginCommandValidator 클래스 : 폼에 입력된 값이 올바른지 검사하는 클래스

 

...생략

@Controller
@RequestMapping("/login")
public class LoginController {
	private AuthService authService;
    
    public void setAuthService(AuthService authService) {
    	this.authService = authService;
    }
    
    @GetMapping
    public String form(LoginCommand loginCommand) {
    	return "login/loginForm";
        
    }
    
    @PostMapping
    public String submit(LoginCommand loginCommand, Errors errors) {
    	new LoginCommandValidator().validate(loginCommand, errors);
        if (errors.hasErrors()) {     // 에러가 있을 경우
        	return "login/loginForm"; // 로그인 폼 뷰 리턴
        }
        try {                         // 에러가 없는 경우 
        	AuthInfo authInfo = authService.authenticate(
            	loginCommand.getEmail(),
               loginCommand.getPassword();
            )
            return "login/loginSuccess"; // 로그인 성공 뷰 리턴    
        } catch (WrongIdPasswordException e) {
       	 	errors.reject("idPasswordNotMatching");
            return "login/loginForm";
        }
    }
}
public class LoginCommandValidator implements Validator {
	@Override
    public boolean supports(Class<?> clazz) {
    	return LoginCommand.class.isAssignableFrom(clazz);
    }
    @Override
    public void validate(Object target, Errors errors) {
    	ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "required");
        ValidationIUtils.rejectIfEmpty(errors, "password", "required");
    }
}

 

 

컨트롤러와 서비스를 스프링 빈으로 등록하기

MemberConfig 설정 파일에 AuthService 클래스를, ControllerConfig 설정 파일에 LoginController 클래스를 빈으로 등록한다.

 

// MemberConfig.java

... 생략
@Bean
public AuthService authService() {
	AuthService authService = new HuthService();
    authService.setMemberDao(memberDao());
    return authService;
}
// ConreollerConfig.java

... 생략
@Bean
public LoginController loginController() {
	LoginController controller = new LoginController();
    controller.setAuthService(authService);
    return controller;
}

 

 

로그인 상태 유지 하는 방법

  • HttpSession 이용
  • 쿠키
  • 외부 데이터베이스에 보관

 

컨트롤러에서 HttpSession 사용하기

컨트롤러에서 HttpSession을 사용하려면 다음의 두 가지 방법 중 하나를 사용한다.

  1. 요청 매핑 애노테이션 적용 메서드에 HttpSession 파라미터를 추가한다.
  2. 요청 매핑 애노테이션 적용 메서드에 HttpServletRequest 파라미터를 추가하고 HttpServletRequest를 이용하여 HttpSession을 구한다.

* 첫 번째 방법은 항상 HttpSession을 생성하지만 두 번째 방법은 필요한 시점에만 HttpSession을 생성할 수 있다.

// 첫 번째 방법
@PostMapping
public String form(LoginCommand loginCommand, Errors errors, HttpSession session) {
// session을 사용하는 코드
}

요청 매핑 애노테이션 적용 메서드에 HttpSession 파라미터가

존재하는 경우 : 스프링 MVC는 컨트롤러의 메서드를 호출할 때 HttpSession 객체를 파라미터로 전달한다.

존재하지 않는 경우 (생성하기 전) : 새로운 HttpSession을 생성한다.

 

 

// 두 번째 방법
@PostMapping
public String submit(LoginCommand loginCommand, Errors errors, HttpServletRequest req) {
	HttpSession session = req.getSession();
    ..// session을 사용하는 코드
}

 

submit() 메서드는 인증 후에 인증 정보를 세션에 담도록 다음과 같이 수정한다.

 public String submit(LoginCommand loginCommand, Errors errors, HttpSession session) {
    	new LoginCommandValidator().validate(loginCommand, errors);
        if (errors.hasErrors()) {     // 에러가 있을 경우
        	return "login/loginForm"; // 로그인 폼 뷰 리턴
        }
        try {                         // 에러가 없는 경우 
        	AuthInfo authInfo = authService.authenticate(
            	loginCommand.getEmail(),
               loginCommand.getPassword();
            )
            session.setAttribute("authInfo", authInfo);  // HttpSession의 "authInfo" 속성에 인증 정보 객체(authInfo)저장
            return "login/loginSuccess"; // 로그인 성공 뷰 리턴    
        } catch (WrongIdPasswordException e) {
       	 	errors.reject("idPasswordNotMatching");
            return "login/loginForm";
        }
    }

 

로그아웃을 위한 컨트롤러 클래스(LogoutController.java) 작성

▶HttpSession을 제거한다.

 

 

main.jsp 수정

... 생략
<body>
    <c:if test="${empty authInfo}">  <!-- 로그인 하지 않은 상태-->
    ... 생략
    </c:if>
    <c:if test="${! empty authInfo}"> <!-- 로그인 성공 -->
    <p>${authInfo.name}님, 환영합니다.</p>
    ... 생략
    </c:if>
</body>

 

 

비밀번호 변경 기능 구현

비밀번호 변경 기능을 위해 구현해야할 코드 

  • ChangePwdCommand
  • ChangePwdCommandValidator
  • ChangePwdController
  • changePwdForm.jsp
  • changedPwd.jsp
  • label.properties에 메시지 추가
  • ControllerConfig 설정 클래스에 빈 설정 추가
ChangePwdController.java

@Controller
@RequestMapping("/edit/changePassword")
public class ChangePwdController {
    ... 생략
    
    @PostMapping
    public String submit(
    	@ModelAttribute("command") ChangePwdCommand pwdCmd, Errors erros, 
        HttpSession session) {
    new ChangePwdCommandValidator().validate(pwdCmd, errors);
    if(errors.hasErrors()) {
    	return "edit/changePwdForm";
    }
    AuthInfo authInfo = (AuthInfo) session.getAttribute("authInfo"); // 현재 로그인한 사용자 정보를 구한다.
    try {
    // changePasswordService에게 Email, 현재 패스워드, 변경된 패스워드를 넘긴다.
    // edit/changedPwd 뷰를 리턴
    } catch {
    }
   }
    
}

주의할 점

서버를 재시작하면 세션 정보가 유지되지 않기 때문에 세션이 보관된 "authInfo" 객체 정보가 사라진다. 즉 서버를 시작하지 않으면 

 AuthInfo authInfo = (AuthInfo) session.getAttribute("authInfo"); 가 null을 리턴한다.

따라서, 서버를 재시작했다면 로그인 과정부터 다시 시작해야 한다.

 

 

 

 

 

인터셉터 사용하기

로그인하지 않은 상태에서 비밀번호 변경 폼을 요청하면 로그인 화면으로 이동시켜야 한다.

다양한 상황에서 로그인 여부를 확인해야 한다

 

HandlerInterceptor 인터페이스 구현

다수의 컨트롤러에 대해 동일한 기능을 적용할 때 사용한다.

 

다음의 세 시점에 공통 기능을 넣을 수 있다.

  • 컨트롤러(핸들러) 실행 전
  • 컨트롤러(핸들러) 실행 후, 아직 뷰를 실행하기 전
  • 뷰를 실행한 이후

세 시점을 처리하기 위해 HandlerInterceptor 인터페이스는 다음 메서드를 정의한다.

  • boolean preHandle(
    HttpServletRequest request,
    HttpServletResponse response,
    Object handler) throws Exception;
  • void postHandle(
    HttpServletRequest request,
    HttpServletResponse response,
    ModelAndView modelAndView) throws Exception;
  • void afterCompletion(
    HttpServletRequest request,
    HttpServletResponse response,
    Object handler,
    Exception ex) throws Exception;

 

 

preHandle() 메서드

컨트롤러 객체를 실행하기 전에 필요한 기능을 구현할 때 사용하며, 다음 작업이 가능하다.

  • 로그인하지 않은 경우 컨트롤러를 실행하지 않는다.
  • 컨트롤러를 실행하기 전에 컨트롤러에서 필요로 하는 정보를 생성한다.

postHandle() 메서드

컨트롤러가 정상적으로 실행된 이후에 추가 기능을 구현할 때 사용한다.

 

afterCompletion() 메서드

뷰가 클라이언트에 응답을 전송한 뒤에 실행된다.

컨트롤러 실행 과정에서 익셉션이 발생하면 네 번째 파라미터로 전달되기 때문에,
예기치 않게 발생한 익셉션을 로그로 남긴다거나 실행 시간을 기록하는 등의 후처리를 하기에 적합하다.

 

10장 250p도 참고하길 바란다.

 

AuthCheckInterceptor 작성

▶HandlerInterceptor 인터페이스를 상속받고, 필요한 메서드만 재정의한 클래스

public class AuthCheckInterceptor implements HandlerInterceptor {

	@Override
    public boolean preHandle(
    	HttpServletRequest request,
		HttpServletResponse response,
    	Object handler) throws Exception {
    HttpSession session - request.getSession(false);
    if (session != null) {
    	Object authInfo = session.getAttribute("authInfo");
        if (authInfo != null) {   // authInfo가 존재한다.
        	return true;          // 컨트롤러를 실행한다.
        }
    }
    response.sendRedirect(request.getContextPath() + "/login");  //authInfo가 존재하지 않는 경우
    return false;                                                // 리다이렉트한다.
    }
}

 

HandlerInterceptor 설정하기

▶MvcConfig 설정 클래스에 추가한다.

... 생략

// 인터셉트 설정 메서드
@Override
public void addInterceptors(InterceptorRegistry registry) {
	registry.addInterceptor(authCheckInterceptor())   // AuthCheckInterceptor 객체를 인터셉터로 설정
    	.addPathPatterns("/edit/**");   // 인터셉터를 적용할 경로 지정, /edit/으로 시작하는 모든 경로에 인터셉트 적용
}

@Bean
public AuthCheckInterceptor authCheckInterceptor() {
	return new AuthCheckInterceptor();
}

두 개 이상 경로 패턴을 지정하려면 각 경로 패턴을 콤마로 구분,

addPathPatterns() 메서드에 지정한 경로 패턴 중 일부를 제외하고 싶다면 excludePathPatterns() 메서드 사용

 

 

 

 

 

컨트롤러에서 쿠키 사용하기

활용 : 쿠키를 사용하여 아이디를 기억해 두었다가, 사용자가 다음에 로그인할 때 아이디를 자동으로 넣어줄 수 있다.

스프링 MVC에서 쿠키를 사용하는 방법 중 하나는 @CookieValue 애노테이션을 사용하는 것이다.
@CookieValue 애노테이션은 요청 매핑 애노테이션 적용 메서드의 Cookie 타입 파라미터에 적용한다.

 

  • LoginController의 form() 메서드 :  쿠키가 존재할 경우 폼에 전달할 커맨드 객체의 email 프로퍼티를 쿠키의 값으로 설정한다.
  • LoginController의 submit() 메서드 : 이메일 기억하기 옵션을 선택한 경우 로그인 성공 후에 이메일을 담고 있는 쿠키를 생성한다.

 

 

 


문제 1.

로그인 상태를 유지하는 방법은 (HttpSession)을 이용하는 방법과 (쿠키)를 이용하는 방법이 있다.

문제 2.

컨트롤러에서 HttpSession을 사용할 때, HttpSession 파라미터를 추가하는 방법은 (항상 HttpSession을 생성)하지만,

HttpServletRequest 파라미터를 추가하고 HttpServletRequest를 이용해서 HttpSession을 구하는 방법은 (필요한 시점)에만 (HttpSession을 생성)할 수 있다.

문제 3.

다음 작업이 가능한 메서드는 무엇인가?

컨트롤러 객체를 실행하기 전에 필요한 기능을 구현한다.
로그인하지 않은 경우 컨트롤러를 실행하지 않는다.
컨트롤러를 실행하기 전에 컨트롤러에서 필요로 하는 정보를 생성한다.

답 : preHandle()

문제 4.

다음 1, 3, 5에 들어갈 메서드로 알맞은 것은?

답 : preHandle(), postHandle(), afterCompletion()

문제 5.

스프링 MVC에서 쿠키를 사용하는 방법 중 하나는 (@CookieValue) 애노테이션을 사용하는 것이다.

그 애노테이션은 (요청 매핑 애노테이션) 적용 메서드의 (Cookie) 타입 파라미터에 적용한다.

 

 

코드 문제 1.

로그인에 성공 시 세션에 로그인 정보를 저장하고자 한다.
적절한 파라미터를 추가하여, 세션에 인증 정보 객체를 저장하는 코드를 구현하라.

 public String submit(LoginCommand loginCommand, Errors errors, //(1)) {
    	new LoginCommandValidator().validate(loginCommand, errors);
        if (errors.hasErrors()) {     // 에러가 있을 경우
        	return "login/loginForm"; // 로그인 폼 뷰 리턴
        }
        try {                         // 에러가 없는 경우 
        	AuthInfo authInfo = authService.authenticate(
            	loginCommand.getEmail(),
               loginCommand.getPassword();
            )
            return "login/loginSuccess"; // 로그인 성공 뷰 리턴    
        } catch (WrongIdPasswordException e) {
       	 	errors.reject("idPasswordNotMatching");
            return "login/loginForm";
        }
    }

답 : HttpSession session, 
try 문 내 return 전, session.setAttribute("authInfo", authInfo);

코드문제 2.

인터셉터의 경로 패턴을 지정하고자한다.  /good/과 /excellent/로 시작하는 모든 경로에 인터셉트를 작용하는 메서드를 구현하라.

단, /good/error/ 가 포함된 경로는 제외하라.

@Override
public void addInterceptors(InterceptorRegistry registry) {
	registry.addInterceptor(authCheckInterceptor())
    // 작성~!
}

 

답: .addPathPatterns("/good/**", "/excellent/**")

.excludePathPatterns("/good/error/**");

 

728x90

관련글 더보기