Backend/Spring Framework

[Spring Boot] 인증 구현2(로그인/로그아웃) - 세션

mopil 2022. 2. 23. 16:25
반응형

https://mopil.tistory.com/23

 

[Spring] 인증 구현1(로그인/로그아웃) - 쿠키

# 서론 HTTP는 무상태 프로토콜이다. 즉, 요청을 한번 처리하고 서버와 클라이언트는 연결이 끊긴다. 따라서 다음 요청을 보낼 때, 서버는 클라이언트가 누구인지 매번 확인해야 한다. 서버가 클

mopil.tistory.com

지난 포스트에서 사용자 인증을 쿠키로 구현했다 하지만, 여러가지 보안상 이슈가 있어서 해당 부분을 보완해야 했었다

 

보완해야 하는 부분은 다음과 같다

 

  • 쿠키 값이 예측가능하면 안된다
  • 예측 불가능한 쿠키 값을 서버쪽에서 유저 데이터와 매핑해서 관리하고, 쿠키 지속시간을 짧도록 유지한다

# 세션 도입

사실, 세션이라는 개념은 그리 거창한 개념은 아니다

완전한 랜덤값으로된 쿠키를 사용자 데이터와 1:1로 매핑해서 서버측에서 관리하는 개념이 세션이라는 개념이다

따라서 다음과 같이 인증을 개편할 것이다

 

1. 사용자가 최초 로그인시, 완전한 랜덤값을 만들어 쿠키로 넣어준다

2. 해당 랜덤값과 사용자 정보가 1:1로 매핑된 테이블 데이터는 서버측에서 관리한다 (Java Map을 활용)

3. 해당 쿠키의 지속시간을 적절하게 짧게 유지하도록 설정한다

 

# 쿠키 값 예측 불가능하게 변경 : UUID 사용

Java에서 기본으로 제공하는 UUID라는 클래스를 활용하면 완전하게 무작위인 특정 값을 생성할 수 있다

 

나중에 스프링을 통해 의존성을 주입받을수 있도록 컴포넌트 스캔의 대상이 되도록 SessionManager 클래스를 생성한다

@Component
public class SessionManager {

    public static final String SESSION_COOKIE_NAME = "mySessionId";
    private Map<String, Object> sessionStore = new ConcurrentHashMap<>();

    /**
     * 세션 생성
     * sessionId 생성 (임의의 추정 불가능한 랜덤 값)
     * 세션 저장소에 sessionId와 보관할 값 저장
     * sessionId로 응답 쿠키를 생성해서 클라이언트에 전달
     */
    public void createSession(Object value, HttpServletResponse response) {
        // 세션 ID를 생성하고 값을 저장

        String sessionId = UUID.randomUUID().toString();
        sessionStore.put(sessionId, value);

        //쿠키 생성
        Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
        response.addCookie(mySessionCookie);
    }

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

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


    public Cookie findCookie(HttpServletRequest request, String cookieName) {
        if (request.getCookies() == null) {
            return null;
        }
        return Arrays.stream(request.getCookies())
                .filter(cookie -> cookie.getName().equals(cookieName))
                .findAny()
                .orElse(null);
    }
}

 

# 컨트롤러 수정

@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.createSession(loginMember, response);
    return "redirect:/";
}

로그인 / 로그아웃

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

SessionManager를 통해서 세션들을 관리하도록 한다

 

# 이제 된 것 인가?

이제 보안 문제는 어느정도 해결되었다 (세션 유지 시간은 아직 보류)

그런데, 이러한 SessionManager를 매 프로젝트 마다 새로 구현해서 사용하면 굉장히 귀찮을 것이다

그래서 서블릿이 HttpSession이라는 편리한 기능을 제공한다 (역시 개발자들은 중복을 싫어한다)

 

Java Collection에 따로 세션을 저장안해도 되고 request객체에 포함시키기만 하면 된다

반응형