티스토리 뷰

반응형

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객체에 포함시키기만 하면 된다

반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크