여니의 프로그래밍 study/Spring & Spring Boot

[Spring] MVC 3 : 세션, 인터셉터, 쿠키

여니's 2023. 12. 26. 22:30

 

참고 자료 : 초보 개발자를 위한 스프링5 프로그래밍 입문

 


 

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

로그인 성공 후 인증상태 정보를 세션에 보관할 때 사용할 

AutoInfo.java 클래스

package spring;

public class AuthInfo {

	private Long id;
	private String email;
	private String name;

	public AuthInfo(Long id, String email, String name) {
		this.id = id;
		this.email = email;
		this.name = name;
	}

	public Long getId() {
		return id;
	}

	public String getEmail() {
		return email;
	}

	public String getName() {
		return name;
	}

}

 

 

암호 일치 여부를 확인하기 위한 메서드 : matchPassword() 추가함

 

package spring;

import java.time.LocalDateTime;

public class Member {

	private Long id;
	private String email;
	private String password;
	private String name;
	private LocalDateTime registerDateTime;

	public Member(String email, String password, 
			String name, LocalDateTime regDateTime) {
		this.email = email;
		this.password = password;
		this.name = name;
		this.registerDateTime = regDateTime;
	}

	void setId(Long id) {
		this.id = id;
	}

	public Long getId() {
		return id;
	}

	public String getEmail() {
		return email;
	}

	public String getPassword() {
		return password;
	}

	public String getName() {
		return name;
	}

	public LocalDateTime getRegisterDateTime() {
		return registerDateTime;
	}

	public void changePassword(String oldPassword, String newPassword) {
		if (!password.equals(oldPassword))
			throw new WrongIdPasswordException();
		this.password = newPassword;
	}

	public boolean matchPassword(String password) {
		return this.password.equals(password);
	}

}

 

 

이메일과 비밀번호가 일치하는지 확인한 후에

AutoInfo 객체를 생성하는

AutoService 클래스

 

package spring;

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());
	}

}

 

AutoService 클래스를 이용하여

로그인 요청을 처리하는 LoginController 클래스

 

LoginCommand 클래스 : 폼에 입력한 값을 전달받기 위한 클래스

 

package controller;

public class LoginCommand {

	private String email;
	private String password;
	private boolean rememberEmail;

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public boolean isRememberEmail() {
		return rememberEmail;
	}

	public void setRememberEmail(boolean rememberEmail) {
		this.rememberEmail = rememberEmail;
	}

}

 

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

 

package controller;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

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");
		ValidationUtils.rejectIfEmpty(errors, "password", "required");
	}

}

 

 

로그인 요청을 처리하는 LoginController 클래스

 

package controller;

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

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

import spring.AuthInfo;
import spring.AuthService;
import spring.WrongIdPasswordException;

@Controller
@RequestMapping("/login")
public class LoginController {
    private AuthService authService;

    public void setAuthService(AuthService authService) {
        this.authService = authService;
    }

    @GetMapping
    public String form(LoginCommand loginCommand,
    		@CookieValue(value = "REMEMBER", required = false) Cookie rCookie) {
		if (rCookie != null) {
			loginCommand.setEmail(rCookie.getValue());
			loginCommand.setRememberEmail(true);
		}
    	return "login/loginForm";
    }

    @PostMapping
    public String submit(
    		LoginCommand loginCommand, Errors errors, HttpSession session,
    		HttpServletResponse response) {
        new LoginCommandValidator().validate(loginCommand, errors);
        if (errors.hasErrors()) {
            return "login/loginForm";
        }
        try {
            AuthInfo authInfo = authService.authenticate(
                    loginCommand.getEmail(),
                    loginCommand.getPassword());
            
            session.setAttribute("authInfo", authInfo);

			Cookie rememberCookie = 
					new Cookie("REMEMBER", loginCommand.getEmail());
			rememberCookie.setPath("/");
			if (loginCommand.isRememberEmail()) {
				rememberCookie.setMaxAge(60 * 60 * 24 * 30);
			} else {
				rememberCookie.setMaxAge(0);
			}
			response.addCookie(rememberCookie);

            return "login/loginSuccess";
        } catch (WrongIdPasswordException e) {
            errors.reject("idPasswordNotMatching");
            return "login/loginForm";
        }
    }
}

 

 

이제 남은 작업은 컨트롤러와 서비스를

스프링 빈으로 등록하기.

 

AuthService 클래스, LoginÇontroller 클래스를 빈으로 등록

MemberConfig.java > AuthService

package config;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import spring.AuthService;
import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberRegisterService;

@Configuration
@EnableTransactionManagement
public class MemberConfig {

	@Bean(destroyMethod = "close")
	public DataSource dataSource() {
		DataSource ds = new DataSource();
		ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");
		ds.setUsername("spring5");
		ds.setPassword("spring5");
		ds.setInitialSize(2);
		ds.setMaxActive(10);
		ds.setTestWhileIdle(true);
		ds.setMinEvictableIdleTimeMillis(60000 * 3);
		ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
		return ds;
	}

	@Bean
	public PlatformTransactionManager transactionManager() {
		DataSourceTransactionManager tm = new DataSourceTransactionManager();
		tm.setDataSource(dataSource());
		return tm;
	}

	@Bean
	public MemberDao memberDao() {
		return new MemberDao(dataSource());
	}

	@Bean
	public MemberRegisterService memberRegSvc() {
		return new MemberRegisterService(memberDao());
	}

	@Bean
	public ChangePasswordService changePwdSvc() {
		ChangePasswordService pwdSvc = new ChangePasswordService();
		pwdSvc.setMemberDao(memberDao());
		return pwdSvc;
	}
	
	@Bean
	public AuthService authService() {
		AuthService authService = new AuthService();
		authService.setMemberDao(memberDao());
		return authService;
	}
}

 

 

ControllerConfig.java > LoginController

 

package config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import controller.ChangePwdController;
import controller.LoginController;
import controller.LogoutController;
import controller.RegisterController;
import spring.AuthService;
import spring.ChangePasswordService;
import spring.MemberRegisterService;

@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;
	}
}

 


[의존성 주입이 일어났을 때의 과정 정리]

1. AuthService 빈 등록 (MemberConfig.java)

2. ControllerConfig.java에서 의존성 주입 설정

: AuthService에 대한 의존성을 선언 (@Autowired)

3. 스프링 컨테이너에서 loginController 빈 생성시,

생성자를 통해 필요한 authService 빈을 주입함 

4. loginController 빈 사용

: 이제 loginController 객체는 authService에 대한 의존성이 해결됨.

 

> 의존성 주입을 통해 객체 간의 결합도를 낮추고

유연하게 서비스를 교체하거나 테스트가 쉽도록 할 수 있다. 

 

> 객체가 직접 의존 객체를 생성하거나 찾아서 사용하는 것이 아니라

외부에서 주입받기에 더 모듈화되고 테스트 가능한 코드를 작성할 수 있다.

 


 

[의존성 주입의 이점]

1. 유연성과 재사용성 향상

> 코드 내에서 직접 객체를 생성하지 않고

외부에서 주입받아서 결합도가 감소함

 

(1) 직접 객체 생성 예시

public class OrderService {
	private PaymentProcessor paymentProcessor;
    
    public OrderService() {
    	// 직접 객체 생성
        this.paymentProcessor=new PaymentProcessor();
    }
    
    public void processOrder(Order order){
    	// 주문 처리 로직
        paymentProcessor.processPayment(order);
    }
 }

 

: OrderService는 직접 PaymentProcessor 객체를 생성함

OrderService는 PaymentProcessor 클래스에 강하게 의존하게 되며

결합도가 높아짐.

 

만약 다른 결제 프로세서 사용을 하게 될 경우

OrderService 클래스 코드를 수정해야함.

 

 

(2) 의존성 주입

public class OrderService {
	private PaymentProcessor paymentProcessor;
    
    // 의존성 주입을 통해 PaymentProcessor를 외부에서 주입받음
    public OrderService(PaymentProcessor paymentProcessor) {
    	this.paymentProcessor = paymentProcessor;
    }
    
    public void processOrder(Order order){
    	// 주문처리 로직
        paymentProcessor.processPayment(order);
    }
 }

 

OrderServic는 생성자를 통해 PaymentProcessor를 외부에서 주입받아 사용함

외부에서 주입을 받으면

OrderService에서는 PaymentProcessor의 구현에 대해 알 필요가 없음

다양한 PaymentProcesso 구현체를 주입받아 사용할 수 있음

 

 

만약 새로운 결제 프로세서가 도입된다면?

(1) 직접 객체 생성하는 코드 변경사항

> 변경사항 발생함

public OrderService() {
    // 변경된 결제 프로세서를 생성함 (변경사항 발생)
    this.paymentProcessor = new NewPaymentProcessor();
}

 

 

(2) 의존성 주입을 받는 코드 변경사항

> OrderService 에서는 변경사항이 없음. 

외부에서 OrderService 생성 시, 새로운 결제 프로세서를 주입함.

public class NewPaymentProcessor implements PaymentProcessor {
	// 새로운 프로세서 구현

}

// OrderService를 새로운 결제 프로세서로 업데이트
PaymentProcessor newPaymentProcessor = new NewPaymentProcessor();
OrderService orderService = new OrderService(newPaymentProcessor);

 

 

OrderService 클래스를 수정할 필요 없이

외부에서 새로운 구현체를 주입받아 변경이 최소화된다.

 

PaymentProcessor newPaymentProcessor = new NewPaymentProcessor();

 

NewPaymentProcessor 클래스는 PaymentProcessor 인터페이스를 구현한 클래스.

새로운 결제 프로세서의 구현을 담당함. 

 

public class NewPaymentProcessor implements PaymentProcessor {

    @Override
    public void processPayment(Order order) {
        // 실제 결제 프로세싱 로직을 구현
        System.out.println("Processing payment for order: " + order.getId());
        // 기타 결제 처리 관련 로직
    }

    // 추가적인 메서드나 속성이 필요한 경우 구현
}

 

인터페이스는 메서드의 시그니처만 정의되어 있고

구현은 없는 추상적인 형태임

 

클래스가 특정 인터페이스를 implements 하면, 

해당 클래스는 그 인터페이스의 모든 메서드를 반드시 구현해야 함

public interface PaymentProcessor {
    void processPayment(Order order);
}

public class NewPaymentProcessor implements PaymentProcessor {
    @Override
    public void processPayment(Order order) {
        // 구체적인 결제 처리 로직을 구현
    }
}

 

 

직접 객체를 생성하는 경우에는 인터페이스를 구현한 것이 아니라

해당 인터페이스를 구현한 구체적인 클래스를 만들어 사용함

 

[전체적인 코드]

(1) 직접 객체 생성

public interface PaymentProcessor {
    void processPayment(Order order);
}

public class NewPaymentProcessor implements PaymentProcessor {
    @Override
    public void processPayment(Order order) {
        // 새로운 결제 프로세서의 특정 처리 로직을 구현
    }
}

// 직접 객체를 생성하여 사용
public class Main {
    public static void main(String[] args) {
        // 새로운 결제 프로세서를 생성
        NewPaymentProcessor newPaymentProcessor = new NewPaymentProcessor();

        // 결제 프로세서를 사용하여 주문 처리
        Order order = new Order();
        newPaymentProcessor.processPayment(order);
    }
}

 

 

(2) 의존성 주입 활용

public interface PaymentProcessor {
    void processPayment(Order order);
}

public class NewPaymentProcessor implements PaymentProcessor {
    @Override
    public void processPayment(Order order) {
        // 새로운 결제 프로세서의 특정 처리 로직을 구현
    }
}

public class OrderService {
    private PaymentProcessor paymentProcessor;

    // 의존성 주입을 통해 PaymentProcessor를 외부에서 주입받음
    public OrderService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    // 주문 처리 메서드
    public void processOrder(Order order) {
        // 주문 처리 로직
        paymentProcessor.processPayment(order);
        // 기타 주문 관련 로직
    }
}

public class Main {
    public static void main(String[] args) {
        // 새로운 결제 프로세서를 생성
        NewPaymentProcessor newPaymentProcessor = new NewPaymentProcessor();

        // OrderService를 의존성 주입을 통해 생성하고 새로운 결제 프로세서를 주입
        OrderService orderService = new OrderService(newPaymentProcessor);

        // 주문 처리
        Order order = new Order();
        orderService.processOrder(order);
    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

public interface PaymentProcessor {
    void processPayment(Order order);
}

@Service
public class NewPaymentProcessor implements PaymentProcessor {
    @Override
    public void processPayment(Order order) {
        // 새로운 결제 프로세서의 특정 처리 로직을 구현
    }
}

@Service
public class OrderService {
    private final PaymentProcessor paymentProcessor;

    // @Autowired를 통해 Spring이 알아서 주입하도록 지정
    @Autowired
    public OrderService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    // 주문 처리 메서드
    public void processOrder(Order order) {
        // 주문 처리 로직
        paymentProcessor.processPayment(order);
        // 기타 주문 관련 로직
    }
}

public class Main {
    public static void main(String[] args) {
        // Spring 컨테이너에서 OrderService를 가져와서 사용
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        OrderService orderService = context.getBean(OrderService.class);

        // 주문 처리
        Order order = new Order();

 

 

필드 주입과 생성자 주입

결론적으로 가급적이면 생성자 주입을 권장함

불변성을 유지하고 테스트가 용이하며

명시적으로 의존성 전달이 가능해서 좋은 객체지향 설계를 장려함

 

(1) 필드 주입

@Service
public class OrderService {
    @Autowired
    private PaymentProcessor paymentProcessor;

    // ...
}

 

(2) 생성자 주입

@Service
public class OrderService {
    private final PaymentProcessor paymentProcessor;

    @Autowired
    public OrderService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    // ...
}

2. 컨트롤러에서 HttpSession 사용하기

로그인 상태를 유지하기 위해서는

HttpSession과 쿠키를 사용하는 두 가지 방법이 존재한다.

 

HttpSession을 사용하기 위해

2가지 방법 중 한 가지를 사용하면 된다.

 

(1) 요청 매핑 어노테이션 적용 메서드에 HttpSession 파라미터를 추가함

 

@PostMapping
public String form(LoginCommand loginCommand, Error errors, HttpSession session){
	....
	// Session 을 사용하는 코드
}

 

요청매핑 어노테이션 적용 메서드에

HttpSession 파라미터가 존재할 경우

스프링 MVC는 컨트롤러의 메서드 호출 시, HttpSession 객체를 파라미터로 전달함

 

HttpSession 생성 전이면 새로운 HttpSession 을 생성하고

그렇지 않으면 기존에 존재하는 걸 전달함

 

(2) 요청 매핑 어노테이션 적용 메서드에 HttpServletRequest 파라미터를 추가하고

HttpServletRequest를 이용해서 HttpSession을 구함

@PostMapping
public String subimt(LoginCommand loginCommand, Error errors, HttpServletRequest req){
	HttpSession session = req.getSession();
	// Session 을 사용하는 코드
}

 

첫 번째 방법은 항상 HttpSession을 생성

두 번째 방법은 필요한 시점에만 생성

 

로그인 성공하면

HttpSession의 authInfo 속성에

인증 정보 객체를 저장하도록 코드를 추가함

 

AuthInfo authInfo = authService.authenticate(
        loginCommand.getEmail(),
        loginCommand.getPassword());

session.setAttribute("authInfo", authInfo);

 

 

로그아웃을 위한 컨트롤러 클래스는

HttpSession을 제거하면 된다. 

 

package controller;

import javax.servlet.http.HttpSession;

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

@Controller
public class LogoutController {

	@RequestMapping("/logout")
	public String logout(HttpSession session) {
		session.invalidate();
		return "redirect:/main";
	}

}

 

 

새로운 컨트롤러를 구현했으니

스프링 설정에 빈을 추가해야 함.

ControllerConfig.java

 

Bean
public LogoutController logoutController() {
    return new LogoutController();
}

 


 

3. 비밀번호 변경기능 구현

서버를 재시작하면 로그인부터 다시 진행해야함.

세션 정보가 유지되지 않기 때문에

세션에 보관된 autoInfo 객체 정보가 사라지게 된다.

 


 

4. 인터셉터 사용하기

로그인을 하지 않은 상태에서

비밀번호 변경 폼을 요청하면 로그인 화면으로 이동시켜야한다. 

 

HttpSession에 autoInfo 객체가 존재하는지 검사하고

존재하지 않는다면 로그인 경로로 리다이렉트 되도록

changePwdController 클래스를 수정할 수도 있다.

 

하지만, 실제 웹 어플리케이션에서는

비밀번호 변경 기능 외 더 많은 기능에서

로그인 여부를 확인해야 함.

 

다수의 컨트롤러에 대해 동일한 기능 적용 시

사용할 수 있는 것이 HandlerInterceptor이다.

 

 

(1) HandlerInterceptor 인터페이스 구현

 

: 위 인터페이스를 사용하면

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

컨트롤러 실행전 / 컨트롤러 실행 후, 아직 뷰를 실행하기 전 / 뷰를 실행한 이후

 

 

세 시점을 처리하기 위해

HandlerInterceptor 인터페이스는

다음 메서드를 정의하고 있다

 

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

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

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

 

 

preHandle()

 

: 컨트롤러 객체 실행 전, 필요한 기능 구현 시 사용

handler 파라미터는 웹 요청을 처리할 컨트롤러 객체

 

> 로그인 하지 않은 경우 컨트롤러 실행하지 않음

> 컨트롤러 실행 전, 컨트롤러에서 필요로 하는 정보 생성

 

false를 리턴하면 컨트롤러를 실행하지 않음

 

 

postHandle()

 

: 컨트롤러가 정상적으로 실행된 이후 추가 기능 구현시 사용

 

> 컨트롤러가 익셉션 발생하면 postHandle() 메서드는 실행되지 않음

 

 

afterCompletion() 

 

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

컨트롤러 실행 과정에서 익셉션 발생 시, 이 메서드의 네 번째 파라미터로 전달된다.

 

익셉션 발생하지 않으면 네 번째 파라미터는 Nul이 된다.

 

따라서 컨트롤러 실행 이후 예기치 않게 발생한 익셉션을

로그로 남긴다거나 혹은 실행시간을 기록하는 등의 후처리를 하기에 적합함

 

 

비밀번호 변경 기능에 접근 시

HandlerInterception 를 사용하면

로그인 여부에 따라 로그인 폼으로 보내거나

컨트롤러를 실행하도록 구현할 수 있다. 

 

package interceptor;

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

import org.springframework.web.servlet.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) {
				return true;
			}
		}
		response.sendRedirect(request.getContextPath() + "/login");
		return false;
	}

}

 

preHandle() 메서드는

session에 authInfo 속성이 존재하면 true를 리턴한다. 

 

true 리턴 시, 컨트롤러를 실행하니까

로그인 상태면 컨트롤러를 실행함

 

false를 리턴하면 로그인 상태가 아니니까

지정한 경로로 리다이렉트함.

 


(2) HandlerInterception 설정하기

 

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

 

package config;

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

import interceptor.AuthCheckInterceptor;

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	...

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(authCheckInterceptor())
			.addPathPatterns("/edit/**")
			.excludePathPatterns("/edit/help/**");
	}

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

	}

}

 

addInterceptors 메서드

: 인터셉터를 설정함

 

addInterceptor 메서드

: HandlerInterceptor 객체를 설정함

 

addPathPatterns() 메서드에 /edit/**를 주었으니까

/edit/changePassword 경로에

AuthCheckInterceptor가 적용됨

 


 

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

사용자 편의를 위하여

아이디를 기억해두었다가 다음 로그인 시

아이디를 자동으로 넣어주는 사이트가 많은데,

이 기능 구현 시 쿠키를 사용한다

 

LoginController 의 form 메서드

> 쿠키 존재할 경우 폼에 전달할 커맨드 객체의 Email 프로퍼티를

쿠키의 값으로 설정함

 

LoginController의 submit() 메서드

> 이메일 기억하기 옵션을 선택한 경우, 

로그인 성공 후 이메일을 담고 있는 쿠키를 생성함

 

 

스프링에서 쿠키를 사용하는 방법 중 하나는

@CookieValue 어노테이션을 사용하는 것

 

 

요청 매핑 어노테이션 적용 메서드의 Cookie 타입 파라미터에 적용함

@CookieValue의 value 속성은 쿠키의 이름을 지정함

23행 : 이름이 REMEBER인 쿠키를 Cookie 타입으로 전달받는다.

지정한 이름을 가진 쿠키가 존재하지 않을 수 있다면 required 속성값을 False로 지정함

(이메일 기억하기를 선택하지 않을 수 있어서 위 값으로 지정함)

기본값은 True임

 

package controller;

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

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

import spring.AuthInfo;
import spring.AuthService;
import spring.WrongIdPasswordException;

@Controller
@RequestMapping("/login")
public class LoginController {
    private AuthService authService;

    public void setAuthService(AuthService authService) {
        this.authService = authService;
    }

    @GetMapping
    public String form(LoginCommand loginCommand,
    		@CookieValue(value = "REMEMBER", required = false) Cookie rCookie) {
		if (rCookie != null) {
			loginCommand.setEmail(rCookie.getValue());
			loginCommand.setRememberEmail(true);
		}
    	return "login/loginForm";
    }

    @PostMapping
    public String submit(
    		LoginCommand loginCommand, Errors errors, HttpSession session,
    		HttpServletResponse response) {
        new LoginCommandValidator().validate(loginCommand, errors);
        if (errors.hasErrors()) {
            return "login/loginForm";
        }
        try {
            AuthInfo authInfo = authService.authenticate(
                    loginCommand.getEmail(),
                    loginCommand.getPassword());
            
            session.setAttribute("authInfo", authInfo);

			Cookie rememberCookie = 
					new Cookie("REMEMBER", loginCommand.getEmail());
			rememberCookie.setPath("/");
			if (loginCommand.isRememberEmail()) {
				rememberCookie.setMaxAge(60 * 60 * 24 * 30);
			} else {
				rememberCookie.setMaxAge(0);
			}
			response.addCookie(rememberCookie);

            return "login/loginSuccess";
        } catch (WrongIdPasswordException e) {
            errors.reject("idPasswordNotMatching");
            return "login/loginForm";
        }
    }
}

 

실제로 REMEMBER 쿠키를 생성하는 부분은

로그인을 처리하는 Submit 메서드다.

 

쿠키를 생성하려면 HttpServletResponse 객체가 필요하므로

submit 메서드의 파라미터로 추가함

 

로그인 성공시, 30일동안 유지되는 쿠키 생성함