Spring/Springboot-Intellij

[SpringBoot] 인증과 인가 / 쿠키와 세션

congs 2025. 2. 3. 15:59

인증과 인가의 차이가 무엇일까?

  • 간단하게 설명하자면
    • 인증 = 아이디, 비밀번호를 입력하고 제출하여 유저인지 확인하는 절차! (지문인식 같이)
    • 인가 = 회원/비회원의 여부에 맞게 특정 리소스에 대한 접근 권한을 주는 것!(관리자 권한처럼)

 

그렇다면, 웹 애플리케이션 인증은 다를까?

  • 서버 - 클라이언트 구조로 되어있는 웹은 Http라는 프로토콜을 이용하여 통신합니다
  • 그리고 이 통신은 "비연결성(Connectionless), 무상태(Stateless)"로 이루어지는데
    • 비연결성(Connectionless) 
      • 서버- 클라이언트가 연결되어 있지 않다는 의미!
      • 왜 연결을 안하나요?
      • 계속 연결을 해놓으면 서버의 비용이 너무 많이 들기때문!
      • 즉, 서버는 하나의 요청 - 하나의 응답 - 연결 종료를 하고 있습니다
    • 무상태( Stateless)
      • 서버가 클라이언트의 상태를 저장하지 않는다는 의미!
      • 왜 저장을 안하나요?
      • 수 많은 클라이언트들의 정보를 저장하기엔 비용이 너무 많이 들기 때문!
      • 그러므로 서버는 이전에 클라이언트가 어떤 요청을 보냈는지 알 수가 없다

하지만, 클라이언트가 인터넷을 사용할 때 연속성이 있는 것처럼 사용했는데..?

  • 그 이유는 클라이언트들이 그렇게 느끼도록 하기위해 url를 계층적으로 설계하기 때문!
  • 바로 /News/Issue/8 처럼 다음 요청에 대한 api url를 이전 계층에 두면 연속적이라 느낍니다

 

그럼 해당 클라이언트가 인증되었다는 정보를 어떻게 유지할까?

- 웹 어플리케이션은 인증을 처리하는 방법이 2가지가 있다.

1. 쿠키 - 세션 방식의 인증

  • 쿠키: 저장 공간 / 세션: 인증 정보(ID/식별자 값같은) = HTTP에 상태정보 유지를 위해 사용
  • 특정 유저가 로그인이 되었다라는 상태를 저장하는 방식
  • 인증과 관련된 최소한의 정보를 저장하여 유지시키는 느낌!
  • JWT와 다른 점은 세션 저장소가 있다는 것
  • 흐름을 보면
    1. 로그인을 하면 DB에서 회원인지 확인을 하고(인증)
    2. 세션 저장소에 회원정보를 저장하고
    3. 세션) 유저 정보와 관련이 없는 세션ID(난수)를 만들어 서버에게 전달 = 보안을 위해 생성
    4. 서버) 발급한 세션ID를 전달
    5. 클라이언트는 세션ID를 쿠키에 저장해 놓았다가 - 데이터 요청시 쿠키(세션ID)를 함께 전달(HTTP Header에)
    6. 서버) 쿠키가 있다면 세션저장소에 있는 정보랑 비교하여 
    7. 클라이언트에게 요구에 맞는 응답 (예, 마이페이지의 포인트 정보)

 

2. JWT (JSON Web Token) 기반 인증

  • JWT = 인증에 필요한 정보를 암호화한 토큰 (JSON형식으로 생긴 웹에서 사용하는 토큰)
  • 쿠키/세션 방식과 유사한 느낌
  • JWT 토큰을 HTTP Header에 담아 서버에서 클라이언트를 식별
  • 흐름을 보면
    1. 로그인을 하면 회원 DB에서 비교하여 회원인지 확인하고(인증)
    2. 인증 통과하면 유저에 대한 정보를 JWT로 암호화
    3. JWT를 응답에 담아 클라이언트에게 전달
    4. 클라이언트는 쿠키저장소/로컬 스토리지에 JWT를 저장했다가
    5. 서버에 데이터 요청시에 JWT를 담아 함께 보내고
    6. 서버) 쿠키(JWT)가 있다면 검증 후 클라이언트에게 응답
  • 세션 저장소를 쓰는 쿠키/세션 방식보다 서버에 부담이 적어 훨씬 더 효율적!

 

그렇다면 쿠키와 세션이 무엇인지 알아보자

- 쿠키와 세션은 모두 HTTP에 상태 정보를 유지하는데 사용됩니다 (서버에서 클라이언트 별 인증, 인가에 사용)

! 쿠키

  • 클라이언트에 저장될 목적으로 생성한 정보를 담은 파일
  • 저장 위치 : 클라이언트(웹 브라우저) = 웹 브라우저에서 개발자 도구에서 쿠키 확인 가능!
  • 용량 : 브라우저 별로 상이 (크롬기준 도메인하나당 180개, 쿠키하나 4KB) 
    • name : 쿠키를 구별하는 키(pk) / value : 값
    • domain : 저장된 도메인
    • path : 쿠키 사용 경로
    • Expires : 쿠키 만료 기한(기한이 지나면 쿠키는 삭제됨!) = 브라우저 종료시에도 유지 가능
  • 보안에 취약하다는 단점! (클라이언트에서 정보 변경, 삭제 등이 쉽게 가능)

 

! 세션 

  • 서버에서 일정시간 동안 클라이언트의 상태를 유지하기 위해 사용! 
  • 저장 위치 : 웹 서버
  • 만료 시점 (하나만 만족해도 만료) 
    1. 브라우저 종료시
    2. 클라이언트 로그아웃시
    3. 서버에 설정한 유지기간까지 해단 클라이언트가 재요청하지 않는 경우
  • 용량 : 세션 저장소 크기까지 (개수 제한x)
  • 보안에 안전! = 서버에 저장되기 때문에 쿠키보다 안전
  • 서버에서 만든 세션ID는 클라이언트의 쿠키(세션 쿠키)로 저장되어 클라이언트 식별에 사용
  • 쿠키 - 세션 방식의 인증 흐름처럼 같은 클라이언트(브라우저)이구나를 인지하고 연속성이 있는 것처럼 느끼도록 만들어줍니다.

 

쿠키와 세션을 Spring에서 만들고 사용해볼까? 

- 브라우저와 Spring이 쿠키를 주고 받는 걸 확인해보자

1. 먼저, 쿠키를 생성하는 메서드를 이해해보자

public static final String AUTHORIZATION_HEADER = "Authorization";
//name을 상수로 만들었습니다. 

@GetMapping("/create-cookie")
    public String createCookie(HttpServletResponse res) {
        addCookie("Robbie Auth", res);
		//쿠키의 value값에 공백이 있어요
        return "createCookie";
    }
    

public static void addCookie(String cookieValue, HttpServletResponse res) {
//HttpServletResponse res : selvet에서 만들어준 response객체에 데이터를 담으면 클라이언트에 자동으로 반환
    try {
        cookieValue = URLEncoder.encode(cookieValue, "utf-8").replaceAll("\\+", "%20"); 
        // Cookie Value 에는 공백이 불가능해서 encoding 진행!
        //.replaceAll("\\+", "%20"); 전체 공백을 변경

        Cookie cookie = new Cookie(AUTHORIZATION_HEADER, cookieValue);
        //cookie 생성자를 이용하여 담기
        // Name-Value 모양으로 담고
        //(메인부분에 Authorizaion이라는 이름으로 보내겠습니다) 
       
       	cookie.setPath("/"); //set메서드를 이용하여 path넣고
        cookie.setMaxAge(30 * 60); //만료기한 넣기

        // Response 객체에 Cookie 추가 (이미 쿠키를 담을 공간이 존재해서 넣기만 하면 됩니다)
        res.addCookie(cookie);
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e.getMessage());
    }
}

 

2. 쿠키를 읽는 메서드는?

 public static final String AUTHORIZATION_HEADER = "Authorization";
 
 //쿠키 읽기(가져오기) = @CookieValue
 @GetMapping("/get-cookie")
    public String getCookie(@CookieValue(AUTHORIZATION_HEADER) String value) {
    //@CookieValue(가져오는 값)을 넣어주면 가져오기 가능
    //HttpServletRequest안에 들어있는 쿠키 중에서 AUTHORIZATION_HEADER라는 쿠키를 가져와 value에 담겠어요
    
        
        System.out.println("value = " + value);
        //value = Authorization

        return "getCookie : " + value;
    }

requestHeader



3. 세션을 만들어보자 (HttpSession 생성)

- Servlet에서는 유일무이한 세션ID를 간편하게 만들 수 있는 HttpSession을 제공한다

public static final String AUTHORIZATION_HEADER = "Authorization";

//세션 생성
    @GetMapping("/create-session")
    public String createSession(HttpServletRequest req) {
        //servlet에서 요청이 들어왔을때 request객체를 만든걸 req로 받아오기

        HttpSession session = req.getSession(true);
        // .getSession(true); 세션이 존재할 경우 세션 반환, 없을 경우 새로운 세션을 생성한 후 반환

        session.setAttribute(AUTHORIZATION_HEADER, "Robbie Auth");
        // 세션에 저장될 정보 Name - Value 를 추가합니다.

        return "createSession";
    }

세션ID를 유일무이하게 HttpSession이 생성
JSESSIONID라고 있지만 안에는Authorization라는 정보가 들어있음

 

 

4. 세션을 가져와보자

 public static final String AUTHORIZATION_HEADER = "Authorization";
 
 //세션 가져오기
    @GetMapping("/get-session")
    public String getSession(HttpServletRequest req) {
        
        // 세션이 존재할 경우 세션 반환, 없을 경우 null 반환 = 가지고 오는 거니까 또 생성할 필요는 x
        HttpSession session = req.getSession(false);

        String value = (String) session.getAttribute(AUTHORIZATION_HEADER); 
        // 가져온 세션에 저장된 Value 를 Name 을 사용하여 가져옵니다.
        System.out.println("value = " + value);

        return "getSession : " + value;
    }