티스토리 뷰

study/Spring

chap 13 - HandlerInterceptor

xoxowo 2023. 2. 23. 15:24

초보 웹 개발자를 위한 스프링 5 프로그램 입문을 보고 복습 겸 개인 학습 정리입니다. (windows 기준)

 

 

비밀번호 변경 기능 구현 

chap13 예제에 추가로 비밀번호 변경 기능을 추가하기 위해 1번부터 7번까지는 기능 구현을 위한 코드를 작성했고, 8번 부터 컨트롤러에서 로그인 상태를 유지하는 HttpSession를 사용한 코드를 변경하면서 작업했다. 

옳지않은 경로로 바로 접속 시 어떻게 리다이렉트하는지 살펴봤다.

 

1. 비밀번호 변경에 사용할 커맨드 객체를 작성한다.

비밀번호를 변경할 때 현재 비밀번호와 새 비밀번호를 입력받도록 구현하므로 currentPasswordnewPassword 두개의 파라미터가 필요하다.

 

sp5-chp13/src/main/java/controller/ChangePwdCommand.java

package controller;

public class ChangePwdCommand {
	
	private String currentPassword;
	private String newPassword;
	
	public String getCurrentPassword() {
		return currentPassword;
	}
	public void setCurrentPassword(String currentPassword) {
		this.currentPassword=currentPassword;
	}
	
	public String getNewPassword() {
		return newPassword;
	}
	
	public void setNewPassword(String newPassword) {
		this.newPassword=newPassword;
	}
}

 

2. ChangePwdCommand 객체를 검증할 ChangePwdCommandValidator클래스를 작성한다.

(Validator 인터페이스를 구현)

 

sp5-chp13/src/main/java/controller/ChangePwdCommandValidator.java

package controller;

import org.springframework.validation.*;

public class ChangePwdCommandValidator implements Validator{
	
	@Override
	public boolean supports(Class<?> clazz) {
		return ChangePwdCommand.class.isAssignableFrom(clazz);
	}
	
	@Override
	public void validate(Object target, Errors errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "currentPassword","required");
		ValidationUtils.rejectIfEmpty(errors, "newpassword", "required");
	}
}

 

3. 비밀번호 변경 요청을 처리하는 컨트롤러 클래스를 작성한다.

submit() 메서드를 보면 현재 로그인한 사용자 정보를 구하기 위해 HttpSession의  "authInfo" 속성을 사용했다. 

 

sp5-chp13/src/main/java/controller/ChangePwdController.java

package controller;

..import 생략..

@Controller
@RequestMapping("/edit/changePassword")
public class ChangePwdController {

	private ChangePasswordService changePasswordService;

	public void setChangePasswordService(
			ChangePasswordService changePasswordService) {
		this.changePasswordService = changePasswordService;
	}

	@GetMapping
	public String form(
			@ModelAttribute("command") ChangePwdCommand pwdCmd) {
		return "edit/changePwdForm";
	}

	@PostMapping
	public String submit(
			@ModelAttribute("command") ChangePwdCommand pwdCmd,
			Errors errors,
			HttpSession session) {
		new ChangePwdCommandValidator().validate(pwdCmd, errors);
		if (errors.hasErrors()) {
			return "edit/changePwdForm";
		} // 현재 로그인한 사용자 정보를 구하기 위해 HttpSession의  "authInfo" 속성을 사용
		AuthInfo authInfo = (AuthInfo) session.getAttribute("authInfo");
		try {
			changePasswordService.changePassword(
					authInfo.getEmail(),
					pwdCmd.getCurrentPassword(),
					pwdCmd.getNewPassword());
			return "edit/changedPwd";
		} catch (WrongIdPasswordException e) {
			errors.rejectValue("currentPassword", "notMatching");
			return "edit/changePwdForm";
		}
	}
}

 

 

4. changePwdForm, changePwd 뷰를 위한 JSP를 작성하자.  (접은 글로 대체)

 

→ sp5-chap13/src/main/WEB-INF/view/edit/changePwdForm.java

더보기
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html>
<html>
<head>
    <title><spring:message code="change.pwd.title" /></title>
</head>
<body>
    <form:form>
    <p>
        <label><spring:message code="currentPassword" />:<br>
        <form:input path="currentPassword" />
        <form:errors path="currentPassword"/>
        </label>
    </p>
    <p>
        <label><spring:message code="newPassword" />:<br>
        <form:password path="newPassword" />
        <form:errors path="newPassword"/>
        </label>
    </p>
    <input type="submit" value="<spring:message code="change.btn" />">
    </form:form>
</body>
</html>

→ sp5-chap13/src/main/WEB-INF/view/edit/changePwd.java

더보기
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html>
<html>
<head>
    <title><spring:message code="change.pwd.title" /></title>
</head>
<body>
    <p>
        <spring:message code="change.pwd.done" />
    </p>
    <p>
        <a href="<c:url value='/main'/>">
            [<spring:message code="go.main" />]
        </a>
    </p>
</body>
</html>

 

5. 뷰에서 사용할 메시지 label.properties 파일에 추가  (접은 글로 대체)

 

→ sp5-chap13/src/main/resources/message/label.properties

더보기

change.pwd.title=비밀번호 변경
currentPassword=현재 비밀번호
newPassword=새 비밀번호
change.btn=변경하기
notMatching.currentPassword=비밀번호를 잘못 입력했습니다.
change.pwd.done=비밀번호를 변경했습니다.

 

6. 컨트롤러와 서비스를 스프링 빈에 등록하기 

비밀번호 변경 요청을 처리하는 컨트롤러 클래스와 비밀번호 변경 서비스 클래스를 ControllerConfig 클래스에 스프링 빈으로 등록한다.

→ sp5-chap13/src/main/java/config/MemberConfig.java

package config;

...import 생략...
@Configuration
public class ControllerConfig {

    @Autowired
    private MemberRegisterService memberRegSvc;
    @Autowired
    private AuthService authService;
    @Autowired     // 추가
    private ChangePasswordService changePasswordService;

    @Bean
    public RegisterController registerController(){
        RegisterController controller = new RegisterController();
        controller.setMemberRegisterService(memberRegSvc);
        return controller;
    }
    @Bean
    public LoginController loginController() {
    	LoginController controller = new LoginController();
    	controller.setAuthService(authService);
    	return controller;
    }
    
    @Bean
    public LogoutController logoutController() {
    	return new LogoutController();
    }   
    // 추가한 빈
    @Bean
    public ChangePwdController changePwdController() {
    	ChangePwdController controller=new ChangePwdController();
    	controller.setChangePasswordService(changePasswordService);
    	return controller;
    }   
}

 

7. 서버 실행 후 "localhost:8090/sp5-chap13/main" 접속 후 비밀번호 변경 기능 테스트 하기

[Run As] → [Run on Server] → [톰캣😺 서버 실행]

→ [브라우저에 "localhost:8090/sp5-chap13/main" 접속]             → [로그인 후 메인으로 가기] 

 

→ [비밀번호 변경]                                                                                 → [비밀번호가 변경되었는지 확인]

 

📌 인터셉터 사용하기

서버를 재작동하고 로그인을 하지않았는데 ("edit/changePassword")에 접속하니 비밀번호 변경 폼이 나온다.

이처럼 "authInfo" 객체가 존재하는지 검사하고 존재하지 않으면 로그인 화면으로 리다이렉트하도록, ChangePwdController 클래스를 수정하면된다. 

 

그러나 이런 인증 외에도 다른 인증들을 필요로하는 경우 각 기능별로 코드를 수정하기보다 다수의 컨트롤러에 대해 동일한 기능을 적용해야할 때 HandlerInterceptor를 사용하면 된다.

 

HandlerInterceptor 인터페이스

HandlerInterceptor 인터페이스를 사용하면 아래 세 시점에 공통 기능을 넣을 수 있다.

 

- 컨트롤러 실행 전

- 컨트롤러 실행 후, 뷰를 실행하기 전

- 뷰를 실행한 이후

 

HandlerInterceptor 인터페이스를 살펴보면 다음 메서드들을 정의하고 있다.

 

- preHandle() 메서드는 컨트롤러(핸들러) 객체를 실행하기 전에 필요한 기능을 구현할때 사용한다.

handler 파라미터는 웹 요청을 처리할 컨트롤러(핸들러) 객체이다.

boolean preHandle(
	HttpServletRequest request,
	HttpServletResponse response,
	Object handler) throws Exception;

 

- postHandle() 메서드는 컨트롤러(핸들러) 객체를 실행된 이후 추가 기능을 구현할때 사용한다. 

컨트롤러가 익셉션을 발생하면 메서드는 실행하지 않는다.

void postHandle(
	HttpServletRequest request,
	HttpServletResponse response,
	Object handler,
	ModelAndView modelAndView) throws Exception;

 

- afterCompletion() 메서드는 가 클라이언트에 응답을 전송한 뒤에 실행된다.

컨트롤러 실행 과정에서 익셉션이 발생하면 네 번째 파라미터로 전달된다. 익셉션이 발생하지 않으면 네 번째 파라미터는 null이된다.

void afterCompletion(
	HttpServletRequest request,
	HttpServletResponse response,
	Object handler,
	Exception ex) throws Exception;

 

 

8. 비밀번호 변경 기능에 접근할 때 로그인 여부에 따른 리다이렉트 

interceptor 패키지를 만들고 AuthCheckinterceptor 클래스를 들고 preHandle()메서드를 구현하여 HttpSession에 "authInfo"속성이 없으면 지정한 경로로 리다이렉트하도록 구현한다.

 

→ sp5-chap13/src/main/java/interceptor/AuthCheakInterceptor.java

package interceptor;

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

import org.springframework.web.servlet.HandlerInterceptor;

public class AuthCheakInterceptor 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) {
				return true;
			}
		}
		response.sendRedirect(request.getContextPath()+"/login");
		return false;
	}
}

 

@Override
public boolean preHandle(
        HttpServletRequest request, 
        HttpServletResponse response,
        Object handler) throws Exception{
      HttpSession session = request.getSession(false);
      if (session != null) {  session이 null이 아니면
               Object authInfo = session.getAttribute("authInfo");
               if (authInfo != null) { ←"authInfo"가 null 이아니면 
                    return true; ←true를 리턴하면 컨트롤러를 실행한다
               }
      }
      response.sendRedirect(request.getContextPath()+"/login"); ←request.getContextPath()는현재 컨텍스트 경로를 리턴
      return false; false를 리턴하면 로그인 상태가 아니므로 위에서 지정한 경로로 리다이렉트한다.
}

 

 

9. HandlerInterceptor 설정하기

HandlerInterceptor를 구현하면 HandlerInterceptor 를 어디에 적용할지 설정해야한다. 

config 패키지의 MvcConfig 설정 클래스에 추가한다.

 

→ sp5-chap13/src/main/java/config/MvcConfig.java

 

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;

import interceptor.AuthCheakInterceptor;

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
        configurer.enable();
    }
    
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry){
        registry.jsp("/WEB-INF/view/", ".jsp");
    }
    
    @Override
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/main").setViewName("main");
	}
    // addInterceptors 추가 
    @Override
    public void addInterceptors(InterceptorRegistry register) {
    	register.addInterceptor(authCheckInterceptor()).addPathPatterns("/edit/**");
    }
    
    @Bean
    public MessageSource messageSource() {
    	ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
    	ms.setBasenames("message.label");
    	ms.setDefaultEncoding("UTF-8");
    	return ms;
    }
    // 
    @Bean AuthCheakInterceptor 빈 추가
    public AuthCheakInterceptor authCheckInterceptor() {
    	return new AuthCheakInterceptor();
    }
    
}

 

 

10. HandlerInterceptor가 적용되었는지 확인하기

[Run As] → [Run on Server] → [톰캣😺 서버 실행] → [브라우저에 "localhost:8090/sp6-chap13/main" 접속] [브라우저에 "localhost:8090/sp6-chap13/edit/changePassword" 경로로 접속] 

 

로그인을 하지 않고 바로 changePassword 경로로 들어왔기 때문에 authInfo 가 없어 login 페이지로 리다이렉트된다.

 

댓글