🌱 Spring MVC (9) μ„Έμ…˜μ„ μ΄μš©ν•œ 둜그인 처리

Kim Dae HyunΒ·2021λ…„ 7μ›” 14일
0

Spring-MVC

λͺ©λ‘ 보기
9/13
post-thumbnail

Github μ†ŒμŠ€μ½”λ“œ

πŸ”Ž Cookie κ°€ μ•„λ‹Œ Session을 μ΄μš©ν•˜λŠ” 이유

정말 κ°„λ‹¨ν•˜κ²Œ Cookieλ§Œμ„ μ΄μš©ν•΄μ„œ 둜그인 μ²˜λ¦¬λ„ κ°€λŠ₯ν•©λ‹ˆλ‹€.
ν•˜μ§€λ§Œ λ³΄μ•ˆμƒμ— 정말 λ§Žμ€ 취약점이 λ°œμƒν•©λ‹ˆλ‹€. Cookieλ₯Ό λ‘œκ·ΈμΈμ— μ΄μš©ν•œλ‹€λŠ” 것은 μ‚¬μš©μžμ˜ κΆŒν•œμ„ κ·ΈλŒ€λ‘œ κ³΅κ°œν•˜λŠ” 것과 κ°™μŠ΅λ‹ˆλ‹€.
CookieλŠ” νƒˆμ·¨ κ°€λŠ₯ν•˜κ³  λ³€μ‘°κ°€ κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ΄μ§€μš”.
이런 이유둜 λ³΄μ•ˆμƒ 이슈λ₯Ό ν•΄κ²°ν•΄μ•Ό ν•©λ‹ˆλ‹€.

πŸ”Ž Session ...

μ„Έμ…˜μ€ Cookie에 κΈ°λ°˜ν•©λ‹ˆλ‹€. κ²°κ΅­ μΏ ν‚€λ₯Ό μ‚¬μš©ν•˜λŠ” κ²ƒμ΄μ§€μš”.
ν•˜μ§€λ§Œ κ²°μ •μ μœΌλ‘œ λ‹€λ₯Έ 것은 μ˜λ―ΈμžˆλŠ” 정보λ₯Ό Cookie μžμ²΄μ— μ €μž₯ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” 것 μž…λ‹ˆλ‹€.

Session은 μ˜λ―ΈμžˆλŠ” 정보λ₯Ό μ„œλ²„μ— μ €μž₯ν•©λ‹ˆλ‹€.

Cookieμ—λŠ” μ„œλ²„μ˜ μ˜λ―ΈμžˆλŠ” 정보과 맀핑을 μœ„ν•œ μΆ”μ • λΆˆκ°€λŠ₯ν•œ λ‚œμˆ˜κ°€ μ €μž₯λ˜μ–΄ ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ μ „λ‹¬λ©λ‹ˆλ‹€.
μ„œλ²„λŠ” ν΄λΌμ΄μ–ΈνŠΈλ‘œλΆ€ν„° 받은 Cookie의 λ‚œμˆ˜κ°’μ„ μ„œλ²„μΈ‘ μ„Έμ…˜ μ €μž₯μ†Œμ™€ λ§€ν•‘ν•˜μ—¬ μ‹€μ œ μ˜λ―ΈμžˆλŠ” 값을 μ‚¬μš©ν•˜κ²Œ λ˜λŠ” 것 μž…λ‹ˆλ‹€.


πŸ”Ž Session 직접 κ΅¬ν˜„

μ„Έμ…˜ μ €μž₯μ†Œ 생성

μ„Έμ…˜ μ €μž₯μ†Œλ₯Ό μœ„ν•œ Map을 μƒμ„±ν•©λ‹ˆλ‹€. μ„Έμ…˜μ„ μœ„ν•œ ν΄λž˜μŠ€λŠ” μŠ€ν”„λ§ 빈으둜 등둝할 것이기 λ•Œλ¬Έμ— μ‹±κΈ€ν†€μž…λ‹ˆλ‹€. λ©€ν‹°μ“°λ ˆλ“œ ν™˜κ²½μ—μ„œ race condition문제λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ ConcurrentHashMap을 μ‚¬μš©ν•©λ‹ˆλ‹€.
Map의 keyκ°’μœΌλ‘œλŠ” λ‚œμˆ˜(UUID)κ°€ μ €μž₯될 것이고 valueμ—λŠ” μ‹€μ œ μ˜λ―ΈμžˆλŠ” 정보가 μ €μž₯될 κ²ƒμž…λ‹ˆλ‹€.

private Map<String, Object> sessionMap = new ConcurrentHashMap<>();

μΏ ν‚€μ˜ key값이 될 λ¬Έμžμ—΄ μƒμˆ˜ μ •μ˜

private final String MY_SESSION_NAME = "mySessionName";

μ„Έμ…˜ 생성 λ©”μ„œλ“œ

UUIDλ₯Ό μ„Έμ…˜ μ €μž₯μ†Œμ˜ keyκ°’μœΌλ‘œ ν•˜κ³  λ§€κ°œλ³€μˆ˜λ‘œ 받은 μ˜λ―ΈμžˆλŠ” 데이터λ₯Ό μ„Έμ…˜ μ €μž₯μ†Œμ˜ value둜 μ €μž₯ν•©λ‹ˆλ‹€.
μ‚¬μš©μžμ—κ²Œ 응닡해쀄 Cookieμ—λŠ” μž„μ˜ λ¬Έμžμ—΄ μƒμˆ˜λ₯Ό keyκ°’μœΌλ‘œ ν•˜κ³  μ„Έμ…˜ μ €μž₯μ†Œμ™€ 맀핑될 UUIDλ₯Ό keyκ°’μœΌλ‘œ μ§€μ •ν•˜μ—¬ 응닡객체 (HttpServletResponse)에 λ‹΄μ•„μ€λ‹ˆλ‹€.

public void createSession(HttpServletResponse response, Object value) {

    String sessionKey = UUID.randomUUID().toString();
    sessionMap.put(sessionKey, value);

    Cookie cookie = new Cookie(MY_SESSION_NAME, sessionKey);
    response.addCookie(cookie);
}

μ„Έμ…˜ get λ©”μ„œλ“œ

ν΄λΌμ΄μ–ΈνŠΈλ‘œλΆ€ν„° 받은 μš”μ²­μ—μ„œ Cookieλ₯Ό νŒŒμ‹±ν•©λ‹ˆλ‹€.
νŒŒμ‹±λœ Cookieμ—μ„œ value(μ„Έμ…˜ μ €μž₯μ†Œkey)둜 μ„Έμ…˜ μ €μž₯μ†Œλ₯Ό μ‘°νšŒν•˜μ—¬ μ‹€μ œ 값을 데이터λ₯Ό λ°›μ•„μ˜΅λ‹ˆλ‹€.

public Object getSession(HttpServletRequest request) {

    if (request.getSession() == null) return null;

    Cookie requestCookie = Arrays.stream(request.getCookies())
            .filter(cookie -> cookie.getName().equals(MY_SESSION_NAME))
            .findAny()
            .orElse(null);
    if (requestCookie == null) return null;
    return sessionMap.get(requestCookie.getValue());
}

μ„Έμ…˜ 만료 λ©”μ„œλ“œ

μ„Έμ…˜μ„ λ§Œλ£Œμ‹œν‚¨λ‹€λŠ” 것은 μ„Έμ…˜ μ €μž₯μ†Œμ—μ„œ μ‹€μ œ 값을 μ‚­μ œν•΄μ£ΌλŠ” 것 μž…λ‹ˆλ‹€.
ν΄λΌμ΄μ–ΈνŠΈ 츑에 μ—¬μ „νžˆ CookieλŠ” λ‚¨μ•„μžˆκ² μ§€λ§Œ μ„Έμ…˜ μ €μž₯μ†Œμ— μ—†λŠ” key κ°’μ΄λ―€λ‘œ 의미 μ—†λŠ” Cookie이기 λ•Œλ¬Έμ— 문제 μ—†μŠ΅λ‹ˆλ‹€.

public void expireCookie(HttpServletRequest request) {
    Cookie requestCookie = Arrays.stream(request.getCookies())
                .filter(cookie -> cookie.getName().equals(MY_SESSION_NAME))
                .findAny()
                .orElse(null);
        
    if (requestCookie != null) sessionMap.remove(requestCookie.getValue());
}

πŸ”Ž Spring MVCκ°€ μ œκ³΅ν•˜λŠ” μ„Έμ…˜ μ‚¬μš© (ServletHttpSession)

둜그인 처리

컨트둀러둜 둜그인 μš”μ²­μ΄ 왔을 λ•Œ μ„Έμ…˜μ— 둜그인 정보λ₯Ό μ €μž₯ν•΄μ£ΌλŠ” λΆ€λΆ„μž…λ‹ˆλ‹€. request 객체λ₯Ό 톡해 session을 μƒμ„±ν•˜κ±°λ‚˜ 기쑴의 μ„Έμ…˜μ„ κ°€μ Έμ˜΅λ‹ˆλ‹€.
후에 session의 setAttributeλ©”μ„œλ“œλ₯Ό μ΄μš©ν•΄ μ‹€μ œ 값을 μ €μž₯ν•©λ‹ˆλ‹€.

μ΄λ•Œ Cookieλ₯Ό μƒμ„±ν•˜λŠ”λ° 기본으둜 Cookie의 이름은 JSESSIONIDκ°€ 되고 값은 μΆ”μ • λΆˆκ°€λŠ₯ν•œ λ‚œμˆ˜λ‘œ μ„€μ •λ©λ‹ˆλ‹€.

HttpSession session = request.getSession(true);
session.setAttribute("SESSION-KEY", member);

λ‘œκ·Έμ•„μ›ƒ 처리 (μ„Έμ…˜λ§Œλ£Œ)

getSession() λ©”μ„œλ“œλŠ” false의 경우 μ„Έμ…˜μ΄ μžˆλŠ” 경우 μ„Έμ…˜μ„ λ°›μ•„μ˜€κ³  μ—†λŠ” 경우 null을 λ°˜ν™˜ν•©λ‹ˆλ‹€. true의 경우 μ„Έμ…˜μ΄ μ—†λŠ” 경우 μ‹ κ·œλ‘œ μ„Έμ…˜μ„ μƒμ„±ν•©λ‹ˆλ‹€.
session이 null이 μ•„λ‹ˆλΌλ©΄ invalidate() λ©”μ„œλ“œλ₯Ό μ΄μš©ν•΄μ„œ μ„Έμ…˜μ„ μ œκ±°ν•©λ‹ˆλ‹€.

HttpSession session = request.getSession(false);
if (session != null) {
    session.invalidate(); // μ„Έμ…˜ 제거
}

μ„Έμ…˜ κ°’ λ°›μ•„μ˜€κΈ°

@SessionAttributeλ₯Ό μ΄μš©ν•΄ μ„Έμ…˜μ˜ 값을 객체둜 바인딩 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
μ•Œλ§žμ€ key값을 μ§€μ •ν•˜μ—¬ 객체에 값을 λ°”μΈλ”©ν•©λ‹ˆλ‹€. required 속성을 μ΄μš©ν•΄ ν•„μˆ˜ μ—¬λΆ€λ₯Ό 지정할 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

둜그인 μ²˜λ¦¬μ‹œ μ„Έμ…˜μ„ 톡해 λ°›μ•„μ˜¨ Member객체가 null이 μ•„λ‹ˆλΌλ©΄ 둜그인 μ„±κ³΅μ‹œ νŽ˜μ΄μ§€λ‘œ null이라면 λ‹€μ‹œ 둜그인 νŽ˜μ΄μ§€λ‘œ λΆ„κΈ°μ‹œν‚€λŠ” 것이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

public String home(
            @SessionAttribute(name="SESSION-KEY", required = false) Member member) {
        ...
}

μΈν”„λŸ° κΉ€μ˜ν•œλ‹˜μ˜ μŠ€ν”„λ§ MVC 2편 을 μˆ˜κ°•ν•˜κ³  μ •λ¦¬ν•œ λ‚΄μš©μž…λ‹ˆλ‹€.

profile
μ’€ 더 천천히 까먹기 μœ„ν•΄ κΈ°λ‘ν•©λ‹ˆλ‹€. 🧐

0개의 λŒ“κΈ€