Thoughts, stories and ideas.

OAuth2 - state 매개변수

OAuth 인증 과정에서 인증 코드를 받은 후 콜백 URL로 리디렉션되는 과정에서 추가적인 매개변수를 유지하려면, 주로 두 가지 방법을 사용할 수 있습니다:

State 매개변수 사용: OAuth 프로토콜에서는 CSRF 공격을 방지하고, 클라이언트 상태를 유지하기 위해 state 매개변수를 제공합니다. 이 state 매개변수를 사용하여 필요한 데이터를 전달할 수 있습니다.

세션 또는 로컬 스토리지 사용: 클라이언트 측에서 세션 또는 로컬 스토리지를 사용하여 필요한 데이터를 저장하고, 콜백 후에 이를 다시 검색할 수 있습니다.

State 매개변수 사용
인증 요청을 보낼 때 state 매개변수에 필요한 데이터를 추가합니다. 서버는 인증 과정을 완료한 후 이 state 값을 콜백 URL과 함께 반환합니다.

const state = encodeURIComponent(JSON.stringify({myParam: 'value'}));
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientID}&redirect_uri=${redirectURI}&response_type=code&scope=${scope}&state=${state}`;
window.location.href = authUrl;

콜백 URL에서 state 매개변수를 파싱하여 필요한 데이터를 얻습니다.

// 콜백 URL에서 파라미터 추출
const params = new URLSearchParams(window.location.search);
const stateParam = params.get('state');
const originalState = JSON.parse(decodeURIComponent(stateParam));

console.log(originalState.myParam); // 'value'

세션 또는 로컬 스토리지 사용
인증 요청을 보내기 전에 클라이언트 측에서 필요한 데이터를 세션 또는 로컬 스토리지에 저장합니다.

sessionStorage.setItem('myParam', 'value');

인증 과정이 끝난 후 콜백에서 이 데이터를 다시 검색합니다.

const myParam = sessionStorage.getItem('myParam');
console.log(myParam); // 'value'

주의 사항
state 매개변수는 클라이언트의 상태 정보를 저장하는 데 사용되며, CSRF 공격을 방지하는 데에도 중요합니다. 따라서 state 값을 안전하게 생성하고 검증하는 것이 중요합니다.
세션 또는 로컬 스토리지를 사용할 때는 브라우저의 동일 출처 정책(Same-Origin Policy)을 고려해야 합니다.
어떤 방법을 사용하든, 보안과 사용자 데이터의 프라이버시를 항상 고려해야 합니다.


이 매개변수의 주요 목적은 두 가지입니다:

CSRF(Cross-Site Request Forgery) 공격 방지: state 매개변수는 인증 요청이 시작된 원래 사이트로부터 발생했음을 확인하는 데 사용됩니다. 이는 서드 파티 사이트에서 악의적인 인증 요청을 발생시키는 CSRF 공격을 방지하는 데 도움이 됩니다.

클라이언트 상태 정보 유지: OAuth 인증 프로세스 중에 클라이언트의 상태(예: 원래 페이지의 위치, 사용자가 선택한 특정 옵션 등)를 유지하는 데 사용됩니다.

인증 요청 시 state 매개변수에 상태 정보 또는 일종의 세션 식별자를 설정합니다. 인증 서버는 인증 과정을 마친 후 사용자를 리디렉션할 때 이 state 값을 그대로 반환함으로써, 클라이언트는 이 값을 검증하여 요청의 유효성을 확인하고, 필요한 상태 정보를 복원할 수 있습니다.

Google, Facebook, Twitter 등 대부분의 주요 OAuth 제공자들은 이 state 매개변수를 사용하는 것을 지원하며, 보안적인 관점에서도 이를 권장합니다. 따라서 OAuth를 사용할 때 state 매개변수를 적절히 활용하는 것이 좋습니다.


스프링 부트(Java) 애플리케이션에서 OAuth 인증 과정 중에 state 매개변수를 사용하려면, 인증 요청을 보내는 서버 측에서 state 매개변수를 생성하고, OAuth 제공자로부터 리디렉션된 콜백 요청에서 이 state 매개변수를 검증하는 과정을 구현해야 합니다.

다음은 이를 구현하기 위한 기본적인 단계입니다:

  1. state 매개변수 생성 및 인증 요청에 포함
    인증 요청을 보내는 부분에서 state 매개변수를 생성하고, OAuth 인증 URL에 이를 포함시킵니다. state는 CSRF 공격을 방지하기 위한 무작위 문자열이나, 특정 사용자 세션 정보를 포함할 수 있습니다.
// state 매개변수 생성 (예: 랜덤 문자열 또는 암호화된 세션 정보)
String state = UUID.randomUUID().toString(); // 또는 다른 방법으로 생성

// state를 세션에 저장
request.getSession().setAttribute("oauthState", state);

// OAuth 인증 URL 구성
String authUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=" + clientId
    + "&redirect_uri=" + redirectUri
    + "&response_type=code"
    + "&scope=" + scope
    + "&state=" + state; // state 매개변수 추가

// 인증 페이지로 리디렉션
response.sendRedirect(authUrl);
  1. 콜백에서 state 매개변수 검증
    OAuth 제공자로부터 리디렉션된 콜백 요청을 처리하는 부분에서, 콜백 요청에 포함된 state 매개변수를 검증합니다.
@GetMapping("/oauth2/callback")
public String oauthCallback(@RequestParam String code, @RequestParam String state, HttpServletRequest request) {
    // 세션에서 저장된 state 값 가져오기
    String sessionState = (String) request.getSession().getAttribute("oauthState");

    // state 값 검증
    if (sessionState == null || !sessionState.equals(state)) {
        // state 불일치 - 에러 처리
        return "error";
    }

    // state 일치 - 인증 코드로부터 액세스 토큰 요청 등의 처리
    // ...

    return "success";
}

위 코드는 스프링 부트(Java)에서 OAuth 인증 과정 중에 state 매개변수를 사용하는 방법을 간략하게 보여줍니다. 실제 구현시에는 애플리케이션의 보안 요구사항, OAuth 제공자의 요구사항, 그리고 사용자의 경험을 고려하여 적절하게 조정해야 합니다.


OAuth 인증 과정 중에 state 매개변수만을 사용하여 서버에 추가적인 데이터를 전달하고자 한다면, 이 state 매개변수에 필요한 정보를 인코딩하여 전달하고, 콜백에서 해당 정보를 디코딩하여 사용할 수 있습니다. 세션을 사용하지 않고 이 방식을 구현하는 것이 가능합니다.

다만, 이 방법은 state 매개변수의 길이 제한과 URL 인코딩이 필요한 문자들을 고려해야 합니다. 또한, 보안상의 이유로 state 매개변수에 민감한 정보를 직접 넣는 것은 피해야 합니다.

state 매개변수에 정보 인코딩

// 추가적인 정보 (예: 사용자 식별자)
String additionalData = "userId=12345";

// state 매개변수에 정보를 인코딩하여 포함
String state = Base64.getEncoder().encodeToString(additionalData.getBytes(StandardCharsets.UTF_8));

// OAuth 인증 URL 구성
String authUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=" + clientId
    + "&redirect_uri=" + redirectUri
    + "&response_type=code"
    + "&scope=" + scope
    + "&state=" + URLEncoder.encode(state, StandardCharsets.UTF_8.toString());

// 인증 페이지로 리디렉션
response.sendRedirect(authUrl);

콜백에서 state 매개변수 디코딩

@GetMapping("/oauth2/callback")
public String oauthCallback(@RequestParam String code, @RequestParam String state, HttpServletRequest request) {
    // state 매개변수 디코딩
    String decodedState = new String(Base64.getDecoder().decode(state), StandardCharsets.UTF_8);

    // 디코딩된 정보 사용 (예: 사용자 식별자 추출)
    // ...

    return "success";
}

이 방법은 state 매개변수를 통해 서버 측에서 추가적인 정보를 전달하고, 콜백에서 이를 다시 추출하는 과정을 보여줍니다. state 매개변수는 URL에 포함되므로 URL 인코딩과 디코딩이 필요합니다.

보안 고려사항
state 매개변수에 민감한 정보를 직접 넣는 것은 피해야 합니다. 필요한 경우 암호화 또는 서버에서만 이해할 수 있는 형태로 정보를 변환하는 것을 고려하세요.
CSRF 공격 방지를 위해 state 매개변수에 무작위로 생성된 값을 포함시키는 것이 좋습니다.
state 매개변수의 길이 제한과 URL 인코딩을 고려해야 합니다. 너무 많은 데이터를 전달하려 하지 않도록 주의하세요.


단순한 숫자나 짧은 문자열 데이터인 경우, 인코딩 및 디코딩 과정 없이 state 매개변수를 직접 사용할 수 있습니다. 이 경우, 단순히 해당 값을 state 매개변수에 할당하고, 콜백에서 이 값을 직접 읽어 사용하면 됩니다.

state 매개변수에 단순 숫자 사용 예시
인증 요청 시:

// 단순 숫자 데이터
String additionalData = "12345";

// state 매개변수에 정보를 직접 포함
String state = additionalData;

// OAuth 인증 URL 구성
String authUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=" + clientId
    + "&redirect_uri=" + redirectUri
    + "&response_type=code"
    + "&scope=" + scope
    + "&state=" + state;

// 인증 페이지로 리디렉션
response.sendRedirect(authUrl);

콜백에서 state 매개변수 사용:

@GetMapping("/oauth2/callback")
public String oauthCallback(@RequestParam String code, @RequestParam String state, HttpServletRequest request) {
    // state 매개변수에서 숫자 데이터 읽기
    String additionalData = state;

    // 추가 데이터 사용
    // ...

    return "success";
}

이 방식은 state 매개변수를 통해 간단한 데이터를 전달하고 콜백에서 이를 바로 사용하는 가장 단순한 형태의 예시입니다. 인코딩 및 디코딩 과정이 필요 없으므로, 구현이 간단하고 효율적입니다.

보안 고려사항
CSRF 공격 방지를 위해 state 매개변수에는 여전히 무작위로 생성된 값을 사용하는 것이 좋습니다.
전달하는 데이터가 민감한 정보를 포함하지 않는지 확인하세요. state 매개변수는 URL에 노출되므로, 보안에 주의해야 합니다.
URL에 직접 포함되므로 URL에서 사용할 수 없는 문자가 포함되지 않도록 주의하세요.