다잘하고싶어

[Spring] 로그인구현5_세션 직접 구현하고 적용하기 본문

이론학습/SRPING

[Spring] 로그인구현5_세션 직접 구현하고 적용하기

챙영잉 2022. 10. 7. 16:19

세션 직접 구현

1. 세션 생성

2. 세션 조회

3. 세션 만료

 


1. 세션 생성

@Component
public class SessionManager {

    public static final String SESSION_COOKIE_NAME = "mySessionId";
    private Map <String, Object> sessionStore = new ConcurrentHashMap<>(); 
    //동시성 문제가 발생할 수 있을 때에는 무조건 ConcurrentHashMap 사용하기

    /**
     * 세션생성
     */
    public void createsSession(Object value, HttpServletResponse response) {
        //session id 생성, 값을 세션에 저장
        String sessionId = UUID.randomUUID().toString();
        sessionStore.put(sessionId, value);
        
        //쿠키 생성
        Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
        response.addCookie(mySessionCookie);
    } 
}

mySessionId 입력 후, 상수로 만들기 -> 단축키 : ctrl + alt + C ( option + command + C) 

 

2. 세션 조회

/**
 * 세션 조회
 */

public Object getSession(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();//쿠키는 배열로 반환된다.
    if (cookies == null) {
        return null;
    }
    for (Cookie cookie: cookies ) {
        if (cookie.getName().equals(SESSION_COOKIE_NAME)) {
            return sessionStore.get(cookie.getValue());
        }
    }
    return null;
}

위의 코딩을 간단하게 리팩토링하면?

public Cookie findCookie(HttpServletRequest request, String cookName) {
    Cookie[] cookies = request.getCookies();//쿠키는 배열로 반환된다.
    if (cookies == null) {
        return null;
    }
    //배열을 스트림으로 바꿔줌
    return Arrays.stream(cookies)
            .filter(cookie -> cookie.getName().equals(cookName))
            .findAny()
            .orElse(null);
}

 

(+) FindFirst()와 FindAny()의 차이?

FindFirst() 순서가 중요. 첫번째로 나온 애.

FindAny() 순서와 상관없이 빨리 나온 애.

 

 

최종코드

/**
 * 세션 조회
 */
public Object getSession(HttpServletRequest request) {
    Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
    if (sessionCookie == null) {
        return null; //실패
    }
    return sessionStore.get(sessionCookie.getValue());
}

public Cookie findCookie(HttpServletRequest request, String cookName) {
    Cookie[] cookies = request.getCookies();//쿠키는 배열로 반환된다.
    if (cookies == null) {
        return null;
    }
    //배열을 스트림으로 바꿔줌
    return Arrays.stream(cookies)
            .filter(cookie -> cookie.getName().equals(cookName))
            .findAny()
            .orElse(null);
}

 

3. 세션 만료

/**
 * 세션 만료
 */ 
public void expire(HttpServletRequest request) {
    Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
    if (sessionCookie != null) {
        sessionStore.remove(sessionCookie.getValue());
    }
}

 

 

Test 생성해서 세션 적용해보기

그런데 httpServletResponse 는 인터페이스 이기때문에 구현체가 있지만 애매하다. (톰캣이 별도로 제공함)

이러한 경우에 스프링이 제공해주는 'MockHttpServletResponse'를 사용할 수 있다.  

MockHttpServletResponse response = new MockHttpServletResponse();

 

최종코드

class SessionManagerTest {

    SessionManager sessionManager = new SessionManager();

    @Test
    void sessionTest() {

        MockHttpServletResponse response = new MockHttpServletResponse();

        //세션 생성 (서버->클라이언트)
        Member member = new Member();
        sessionManager.createsSession(member,response );
        MockHttpServletRequest request = new MockHttpServletRequest();
         
        request.setCookies(response.getCookies()); //mySessionId = adkfjaewok393r...

        //session 조회
        Object result = sessionManager.getSession(request);
        Assertions.assertThat(result).isEqualTo(member);

        //세션만료
        sessionManager.expire(request);
        Object expired = sessionManager.getSession(request);
        Assertions.assertThat(expired).isNull();

    }

}

 

 

sessionManager.createsSession(member,response );

: 서버에서 세션을 만들고, 쿠키를 만들어서 응답에 담아둔 것.

즉, 서버에서  웹 브라우저에 응답이 나간 상황으로 가정할 수 있음 

 

request.setCookies(response.getCookies());

 : 웹브라우저에서 서버로 쿠키를 전송하는 것(응답에서 받은 쿠키를 가지고 요청에 쿠키를 만들어서 넣음)
   안에 담긴 값은  mySessionId = adkfjaewok393r...    과 같은 예시 들 수 있음.

MockHttpServletRequest request = new MockHttpServletRequest();

:요청에 응답 쿠키가 저장되었는지 확인
여기부터 웹브라우저의 요청 (클라이언트 -> 서버) 으로 이해 가능.

 

실제 세션 적용해보기

LoginController

@PostMapping("login") //로그인 폼 보여줌
public String loginV2(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletResponse response) {
    if (bindingResult.hasErrors()) {
        return "login/loginForm";
    }
    Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
    if (loginMember == null) { //회원을 못찾거나, 아이디 패스워드가 틀린 경우
        bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
        return "login/loginForm";
    }
    //로그인 성공처리

    //세션 관리자를 통해 세션을 생성하고, 회원데이터 보관
    sessionManager.createsSession(loginMember, response);
    
    return "redirect:/";
}

@PostMapping("/logout")
public String logoutV2(HttpServletRequest request) {
    sessionManager.expire(request);
    return "redirect:/";
}

HomeController

@GetMapping("/")
public String homeLoginV2(HttpServletRequest request, Model model) {
     
    //세션 관리자에 저장된 회원 정보 조회
    Member member = (Member) sessionManager.getSession(request);
    

    //로그인 
    if (member == null) {
        return "home";
    }

    model.addAttribute("member", member);
    return "loginHome"; //로그인 사용자 전용 홈


}

 

 

로그아웃해도 세션아이디를 확인할 수 있지만, 

실제 세션 저장소에는 삭제되었기 때문에 

저 아이디는 노출되어도 상관없다.

 

 

 

그러나 프로젝트 마다 세션 개념을 직접 개발하는 것은 불편하므로,

서블릿은 세션 개념을 지원한다.

서블릿이 공식적으로 지원하는 세션에 대해 알아보기.

 

2022.10.08 - [SRPING] - [Spring] 로그인구현6_서블릿HTTP 세션, 스프링이 제공하는 SessionAttribute


Reference : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

강의 듣고 내용 정리중