김영한 강사님의 스프링 MVC 강의 2편을 듣고 수정하고 정리한 내용입니다.
위 내용을 텍스트로 풀면
1)사용자가 로그인을 시도
2)로그인이 성공하면 서버에서 세션 ID를 생성한다.
3)서버 내부에 세션 저장소에 세션 ID를 key,로그인한 사용자를 value값으로 둔다.
4)서버는 클라이언트에 mySessionId라는 이름으로 세션 ID만 쿠키에 담아서 전달한다.
5)클라이언트는 쿠키 저장소에 mySessionId 쿠키를 보관한다.
쿠키와 다른 점
먼저 Filter를 수정한다.
@Slf4j
public class LoginCheckFilter implements Filter {
private static final String[] whitelist={"/members/add","/members/homeBySession","/members/homeByCookie","/loginByCookie","/logoutByCookie","/loginBySession","/logoutBySession"};
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void destroy() {
Filter.super.destroy();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest=(HttpServletRequest) request;
String requestURI=httpRequest.getRequestURI();
HttpServletResponse httpResponse=(HttpServletResponse) response;
try{
log.info("인증 체크 필터 시작 {}",requestURI);
if(isLoginCheckPath(requestURI)){
log.info("인증 체크 로직 실행 {}",requestURI);
HttpSession session=httpRequest.getSession(false);
Cookie []cookies=httpRequest.getCookies();
if(cookies!=null){
for (Cookie cookie : cookies) {
log.info("cookie의 이름 {}",cookie.getName());
log.info("cookie의 value {}",cookie.getValue());
}
}
else{
log.info("쿠키는 null입니다");
} if(session==null||session.getAttribute("loginMember")==null){
log.info("미인증 사용자 요청 {}",requestURI);
return;
}
}
chain.doFilter(request,response);
}catch (Exception e){
throw e;
}finally {
log.info("인증 체크 필터 종료 {}",requestURI);
}
}
public boolean isLoginCheckPath(String requestURI){
return !PatternMatchUtils.simpleMatch(whitelist,requestURI);
}
}
위 코드에서 HttpSession을 이용해서 session이 null이거나 session의 loginMember라는 쿠키이름을 찾아 그 값이 null 이라면 로그인하지 않은 사용자라 판단한 후 접근할 수 없도록 한다. 또한 Cookie로 cookieName과 cookie값을 파악해보도록 하겠다.
참고로 HttpSession session=httpRequest.getSession(false)는 session이 있으면 가지고 오고 없으면 아무것도 하지 않는다. default값은 true인데 true인 경우 session을 아예 생성해버리니 이에 유의해야 함.
@RestController
@RequiredArgsConstructor
@Slf4j
public class LoginController {
private final LoginService loginService;
@PostMapping("/loginByCookie")
public String loginByCookie(@Valid @RequestBody MemberSignInDto memberSignInDto,HttpServletResponse response){
System.out.println(memberSignInDto.getLoginId()+ " "+memberSignInDto.getPassword());
Member loginMember=loginService.login(memberSignInDto.getLoginId(), memberSignInDto.getPassword());
log.info("login? {}",loginMember);
if(loginMember==null){
return "로그인 실패";
}
Cookie idCookie=new Cookie("memberId",String.valueOf(loginMember.getId()));
//헤더가 Cookie이고 value는 memberId=1인 상황이다.
response.addCookie(idCookie);
return "로그인 성공";
}
@PostMapping("/logoutByCookie")
public String logoutByCookie(HttpServletResponse response){
expireCookie(response,"memberId");
return "로그아웃 완료";
}
public void expireCookie(HttpServletResponse response,String cookieName){
Cookie cookie=new Cookie(cookieName,null);
cookie.setMaxAge(0);
response.addCookie(cookie);
}
//------------------------------------------------------------------------------------------------
@PostMapping("/loginBySession")
public String loginBySession(@RequestBody MemberSignInDto memberSignInDto,HttpServletRequest request){
Member loginMember=loginService.login(memberSignInDto.getLoginId(), memberSignInDto.getPassword());
log.info("login? {}",loginMember);
if(loginMember==null){
return "오류 발생";
}
HttpSession session=request.getSession();//세션이 있으면 있는 세션 반환, 없으면 신규 생성 default가 true이고 true일 떄
//request.getSession(false):세션이 없으면 새로운 세션 생성 X, 세션이 있으면 반환
session.setAttribute("loginMember",loginMember);//하나의 세션에 여러 값을 저장할 수 있다.,session에 loginMember라는 속성 추가
return "로그인 성공";
}
@PostMapping("/logoutBySession")
public String logoutBySession(HttpServletRequest request){
HttpSession session=request.getSession();
if(session!=null){
session.invalidate();//세션을 제거한다.
}
return "로그아웃 완료";
}
}
밑에 -----------가 있는데 그 아래가 session을 이용해서 login과 logout을 구현한 것이다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/members")
public class MemberController {
private final MemberRepository memberRepository;
@PostMapping ("/add")
public String addForm(@RequestBody @Valid MemberSignUpDto memberSignUpDto){
Member member=new Member(memberSignUpDto.getName(),memberSignUpDto.getLoginId(),memberSignUpDto.getPassword());
memberRepository.save(member);
return "저장되었습니다";
}
@GetMapping("/homeByCookie")
public String homeLoginByCookie(@CookieValue(name = "memberId",required = false)Long memberId){
if(memberId==null){
return "기본 홈 화면";
}
Member loginMember=memberRepository.findById(memberId).get();
if(loginMember==null){
return "기본 홈 화면";
}
String memberName=loginMember.getName();
return memberName+"을 위한 기본 홈 화면";
}
@GetMapping("/homeBySession")
public String homeLoginBySession(HttpServletRequest request){
HttpSession session= request.getSession(false);
if(session==null){
return "기본 홈 화면";
}
Member loginMember=(Member)session.getAttribute("loginMember");
if(loginMember==null){
return "기본 홈 화면";
}
return loginMember.getName()+"을 위한 기본 홈 화면";
}
}
위의 코드의 의미는 request에서 session을 가져올 때 session이 null이거나 loginMember의 속성이 없을 떄는 기본 홈 화면이라는 글자를 반환하고 그렇지 않으면 loginMember의 이름+"을 위한 기본 홈 화면"을 반환한다.
@Slf4j
@RestController
public class SessionInfoController {
@GetMapping("/session-info")
public String sessionInfo(HttpServletRequest request){
HttpSession session=request.getSession(false);
if(session==null){
return "세션이 없습니다";
}
System.out.println(session.getAttributeNames());//HttpSession 객체에서 현재 세션에 저장된 모든 속성의 이름(키)을 가져오는데 사용되는 메서드
session.getAttributeNames().asIterator().forEachRemaining(name->log.info("session name={},value={}",name,session.getAttribute(name)));
//name에는 loginMember가 들어간다.,session.getAttribute(name)에는
log.info("sessionId={}",session.getId());//031AF55
log.info("maxInactiveInterval={}",session.getMaxInactiveInterval());
log.info("creationTime={}",session.getCreationTime());
log.info("lastAccessedTime={}",new Date(session.getLastAccessedTime()));
log.info("isNew={}",session.isNew());
return "세션 출력";
}
}
위 코드는 session에 대해서 알아보기 위해 작성한 코드이다.
위에서 보듯 로그인을 하지 않은 상태라면 items에 접근할 수 없고 기본 홈 화면만 출력되는 것을 볼 수 있다.
이제 회원가입과 로그인을 시도해보자
위에서 보듯이 request 에 cookie가 들어가고 JSESSIONID=... 이 들어가는 것을 볼 수 있다.
이제 items를 들어가보면
정상적으로 문자열이 출력되고
이름+"을 위한 기본 홈 화면" 이 출력되는 것을 볼 수 있다.
아까 Filter에서
Cookie []cookies=httpRequest.getCookies();
if(cookies!=null){
for (Cookie cookie : cookies) {
log.info("cookie의 이름 {}",cookie.getName());
log.info("cookie의 value {}",cookie.getValue());
}
}
else{
log.info("쿠키는 null입니다");
}
이 코드가 실행되는 것을 보자.
2023-07-26 21:40:16.788 INFO 21316 --- [nio-8080-exec-7] c.l.C.configuration.filter.LogFilter : REQUEST [938282c5-288f-4eca-8bc8-36bd7d819ac5][/items]
2023-07-26 21:40:16.788 INFO 21316 --- [nio-8080-exec-7] c.l.C.c.filter.LoginCheckFilter : 인증 체크 필터 시작 /items
2023-07-26 21:40:16.788 INFO 21316 --- [nio-8080-exec-7] c.l.C.c.filter.LoginCheckFilter : 인증 체크 로직 실행 /items
2023-07-26 21:40:16.788 INFO 21316 --- [nio-8080-exec-7] c.l.C.c.filter.LoginCheckFilter : cookie의 이름 JSESSIONID
2023-07-26 21:40:16.788 INFO 21316 --- [nio-8080-exec-7] c.l.C.c.filter.LoginCheckFilter : cookie의 value BCFEDFAF9744BE3B6C1F2EA9780AF669
2023-07-26 21:40:16.788 INFO 21316 --- [nio-8080-exec-7] c.l.C.c.filter.LoginCheckFilter : 인증 체크 필터 종료 /items
2023-07-26 21:40:16.788 INFO 21316 --- [nio-8080-exec-7] c.l.C.configuration.filter.LogFilter : RESPONSE [938282c5-288f-4eca-8bc8-36bd7d819ac5][/items]
로그가 이렇게 찍힌다.
즉 session 도 cookie 기반이라는 것을 보다 직관적으로 확인할 수 있다.
cookie의 이름이 JSESSIONID라는 것과 cookie의 getValue를 하면 그 복잡한 String 이 나온다는 것을 알 수 있다.
또한 session-info를 출력하여 보면
이렇게 나온다는 것을 알 수 있다.
위에서 session에는 여러 AttributeNames를 가질 수 있고
session의 name은 우리가 설정한 loginMember이고, 그 값은 Member 객체를 의미한다는 것 또한 알 수 있다.
또한 session의 ID가 위에서 cookie의 value값과 같은 "그 긴거" 라는 것을 확인할 수 있다.
일단 마저 로그아웃을 테스트해보자.
이제 items 에 접근할 수 없다.
일단 위의 내용을 토대로 정리를 해보자.
1)cookie.getName()은 JSESSIONID이다.
2)session.getId()==cookie.getValue() 이고 그 값은 BCFEDFAF9744BE3B6C1F2EA9780AF669 이다.
3)session.getAttributeNames로 session의 속성을 각각 탐색할 수 있는데 session.getAttribute(name)을 한다면 실제 값인 Member가 튀어 나온다.
이제 텍스트로 정리를 해보겠다.
1)사용자가 로그인 요청을 한다
2)로그인에 성공시 서버는 session에 loginMember라는 속성을 추가하고 로그인한 회원을 저장한다.
3)이 과정에서 서버는 세션 저장소에 세션 ID(ex:zz0101..)를 생성(key값)하고 value로는 로그인한 회원을 저장해 둔다.
4)이제 response에 쿠키를 넣어서 보내는데
Cookie idCookie=new Cookie("JSESSIONID","zz0101..");을 보내는 느낌이다.
5)앞으로 클라이언트는 무언가 보낼 떄 이 쿠키값을 가지고 간다
6)서버는 이 쿠키의 값("zz0101")(세션에서 key) 과 session.getAttribute("loginMember")로 어떤 값을 꺼내와야(로그인멤버) 할지 알고 그 것을 꺼내온다.
즉 보안적으로 개발자가 할게 많다.
그래서 스프링 시큐리티를 이용해서 더욱 쉽고 안전하게 회원가입, 로그인 기능을 사용할 수 있다.