로그인 - 쿠키, 세션
domain은 web을 참조하면 안된다.
// LoginService MemberRepository주입 public Member login(String loginId, String password) { return memberRepository.findByLoginId(loginId) .filter(m -> m.getPassword().equals(password)) .orElse(null); } //LoginController - login() Member loginMember = loginService.login(form.getLoginId(),form.getPassword()); if (loginMember == null) { bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다."); return "login/loginForm"; }
로그인 상태 유지 - 쿠키 사용
영속 쿠키: 만료 날짜 까지
세션 쿠키: 만료 날짜 생략, 브라우저 종료시 까지
세션 쿠키 생성
//LoginController - login() HttpServletResponse response 추가 ~로그인 성공로직~ Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId())); response.addCookie(idCookie); //HomeController @CookieValue(name = "memberId", required = false) Long memberId // memberId,loginMember null이면 그냥화면 //로그인 됐으면 model.addAttribute("member", loginMember); -> loginHome th:text="|로그인: ${member.name}|"
로그아웃
private void expireCookie(HttpServletResponse response, String cookieName) { Cookie cookie = new Cookie(cookieName, null); cookie.setMaxAge(0); response.addCookie(cookie); }
보안 문제
- 쿠키 값 변경 가능
- 쿠키에 보관된 정보 훔쳐갈 수 있음
- 쿠키 평생 사용 가능
임의의 토큰 노출, 서버에서 토큰과 사용자 id 매핑, 토큰 만료시간
로그인 - 세션
세션: 서버에 중요한 정보를 보관하고 연결을 유지하는 방법
@Component SessionManager Map<String, Object> sessionStore //세션 생성 //세션 id생성 String sessionId = UUID.randomUUID().toString(); sessionStore.put(sessionId, value); // 쿠키 생성 Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId); response.addCookie(mySessionCookie); //세션 조회 Cookie sessionCookie = Arrays.stream(request.getCookies()) .filter(cookie -> cookie.getName().equals(cookieName)) .findAny() .orElse(null); sessionStore.get(sessionCookie.getValue()); //세션 만료 쿠키 찾고->sessionStore.remove(sessionCookie.getValue());
세션 적용
LoginController
private final SessionManager sessionManager;
// login
sessionManager.createSession(loginMember, response);
//logout
sessionManager.expire(request);
HomeController
private final SessionManager sessionManager;
Member member = (Member)sessionManager.getSession(request);
model.addAttribute("member", member);
서블릿 HTTP 세션1
LoginController
// login
//세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성(true)<->null(false)
HttpSession session = request.getSession(); //true
//세션에 로그인 회원 정보 보관
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
//logout
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();//세션 제거
}
HomeController
HttpSession session = request.getSession(false); //session null체크
Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
//loginMembers null체크
model.addAttribute("member", loginMember);
서블릿 HTTP 세션2
- @SessionAttribute
HomeController @SessionAttribute(name = "loginMember", required = false) Member loginMember
세션 정보와 타임아웃 설정
- sessionId,maxInactiveInterval,creationTime,lastAccessedTime,isNew
- 타임아웃: 서버에 최근에 요청한 시간을 기준으로 30분
- server.servlet.session.timeout=1800(30분)
로그인 - 필터, 인터셉터
서블릿 필터
로그인 여부를 체크하는 로직-> 웹과 관련된 공통 관심사-> HTTP의 헤더나 URL의 정보들이 필요 -> HttpServletRequest 제공
서블릿 필터(수문장): HTTP 요청 -> WAS -> 필터 -> 디스패처 서블릿 -> 컨트롤러
// Filter의 doFilter구현 try { if (isLoginCheckPath(requestURI)) { HttpSession session = httpRequest.getSession(false); if (session == null ||session.getAttribute(SessionConst.LOGIN_MEMBER) == null) { httpResponse.sendRedirect("/login?redirectURL=" +requestURI);//로그인 화면 갔다가 다시와 return; } } chain.doFilter(request, response); } // WebConfig - loginCheckFilter() FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new LoginCheckFilter()); filterRegistrationBean.setOrder(2); filterRegistrationBean.addUrlPatterns("/*");//모든 요청에 로그인 필터 return filterRegistrationBean; // LoginController @RequestParam(defaultValue = "/") String redirectURL return "redirect:" + redirectURL;
스프링 인터셉터
- HTTP 요청 -> WAS -> 필터 -> 디스패처 서블릿 -> 스프링 인터셉터 -> 컨트롤러
- preHandle -> handler -> 핸들러 어댑터 -> 핸들러 (컨트롤러) -> ModelAndView -> postHandle -> render(Model) -> afterCompletion
//HandlerInterceptor preHandle구현
String requestURI = request.getRequestURI();
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER)== null) {
response.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
//WebMvcConfigurer addInterceptors구현
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns(
"/", "/members/add", "/login", "/logout",
"/css/**", "/*.ico", "/error"
);
ArgumentResolver 활용
//HomeController
@Login Member loginMember
//@Login
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}
//HandlerMethodArgumentResolver구현
supportsParameter:@Login,Member 타입 체크
resolveArgument:
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
return session.getAttribute(SessionConst.LOGIN_MEMBER);
//WebMvcConfigurer에 등록
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginMemberArgumentResolver());
}
링크
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2