OAuth 2.0 및 Java용 Google OAuth 클라이언트 라이브러리

개요

목적: 이 문서에서는 Google Workspace API가 제공하는 일반적인 OAuth 2.0 기능을 설명합니다. 사용할 수 있습니다 이러한 함수는 인증 및 승인에 사용할 수 있습니다.

GoogleCredential를 사용하여 OAuth 2.0 승인을 수행하는 방법에 대한 안내 Google 서비스에 대한 자세한 내용은 Java용 Google API 클라이언트 라이브러리에서 OAuth 2.0 사용

요약: OAuth 2.0은 최종 사용자가 안전하게 클라이언트를 승인할 수 있는 표준 사양 안전하게 서버 측 리소스에 액세스할 수 있습니다. 또한 OAuth 2.0 Bearer 토큰 사양은 액세스를 사용하여 보호되는 리소스에 액세스하는 방법을 설명합니다 최종 사용자 인증 프로세스 중에 부여되는 토큰입니다.

자세한 내용은 다음 패키지에 대한 Javadoc 문서를 참고하세요.

클라이언트 등록

Java용 Google OAuth 클라이언트 라이브러리를 사용하려면 먼저 인증 서버에 애플리케이션을 등록하여 클라이언트 ID를 받고 클라이언트 보안 비밀번호 이 프로세스에 대한 일반적인 정보는 고객 등록 사양을 참조하세요.

사용자 인증 정보 및 사용자 인증 정보 저장소

사용자 인증 정보 는 액세스할 수 있습니다. 갱신 토큰을 사용하면 Credential도 액세스 권한을 새로고침합니다. 토큰을 만료해야 합니다. 예를 들어 액세스 토큰이 있는 경우 다음과 같은 방법으로 요청할 수 있습니다.

  public static HttpResponse executeGet(
      HttpTransport transport, JsonFactory jsonFactory, String accessToken, GenericUrl url)
      throws IOException {
    Credential credential =
        new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accessToken);
    HttpRequestFactory requestFactory = transport.createRequestFactory(credential);
    return requestFactory.buildGetRequest(url).execute();
  }

대부분의 애플리케이션은 사용자 인증 정보의 액세스 토큰을 유지하고 향후 승인으로 리디렉션되지 않도록 토큰 새로고침 페이지로 이동합니다. 이 CredentialStore 이 라이브러리의 구현은 지원 중단되었으며 향후 삭제될 예정입니다. 지원합니다 대안은 DataStoreFactoryDataStore 인코더-디코더 StoredCredential 이러한 API는 Java용 Google HTTP 클라이언트 라이브러리.

라이브러리에서 제공되는 다음 구현 중 하나를 사용할 수 있습니다.

  • JdoDataStoreFactory JDO를 사용하여 사용자 인증 정보를 유지합니다
  • AppEngineDataStoreFactory Google App Engine Data Store API를 사용하여 사용자 인증 정보를 유지합니다.
  • MemoryDataStoreFactory '유지' 메모리의 크리덴셜 정보를 저장할 수 있으며, 이는 단기적인 수명 주기 동안 사용할 수 있는 스토리지 용량을 제공합니다
  • FileDataStoreFactory 사용자 인증 정보를 파일에 유지합니다

Google App Engine 사용자:

AppEngineCredentialStore가 지원 중단되었으며 삭제될 예정입니다.

이때 AppEngineDataStoreFactory StoredCredential을 사용합니다. 사용자 인증 정보를 이전 방식으로 저장한 경우 추가된 도우미 메서드를 사용할 수 있습니다. migrateTo(AppEngineDataStoreFactory) 또는 migrateTo(DataStore) 마이그레이션합니다

DataStoreCredentialRefreshListener 사용 공개 IP 주소를 사용하여 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).

승인 코드 흐름

승인 코드 흐름을 사용하여 최종 사용자가 애플리케이션에 권한을 부여할 수 있도록 허용 액세스할 수 있도록 합니다. 이 흐름의 프로토콜은 승인 코드 부여 사양

이 흐름은 AuthorizationCodeFlow에 응답합니다. 단계는 다음과 같습니다.

또는 kube-APIserver와 AuthorizationCodeFlow, 하위 수준의 클래스를 사용할 수 있습니다.

Servlet 인증 코드 흐름

이 라이브러리는 서블릿 도우미 클래스를 제공하여 인증 코드 플로우를 자세히 설명합니다. 구체적인 서브클래스를 제공하기만 하면 / AbstractAuthorizationCodeServletAbstractAuthorizationCodeCallbackServlet (google-oauth-client-servlet에서 가져옴) web.xml 파일에 추가합니다. 여전히 사용자를 직접 처리해야 합니다. 로그인하고 사용자 ID를 추출합니다.

샘플 코드

public class ServletSample extends AbstractAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

public class ServletCallbackSample extends AbstractAuthorizationCodeCallbackServlet {

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    // handle error
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

Google App Engine 승인 코드 흐름

App Engine의 승인 코드 흐름은 서블릿과 거의 동일합니다. Google App Engine의 Users Java API. Users Java API를 사용하려면 사용자가 로그인해야 합니다. 대상: 사용자를 로그인 페이지로 리디렉션하는 방법에 대한 정보 로그인한 후 다음을 확인하세요. 보안 및 인증 (web.xml에 있음).

서블릿 사례와의 가장 큰 차이점은 서브클래스로 AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServlet (google-oauth-client-appengine에서) 추상 서블릿 클래스를 확장하고 Users Java API를 사용하여 getUserId 메서드를 자동으로 구현합니다. Java용 Google HTTP 클라이언트 라이브러리AppEngineDataStoreFactory는 Google App Engine Data Store API를 사용하여 사용자 인증 정보를 유지하기에 좋은 옵션입니다.

샘플 코드

public class AppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

public class AppEngineCallbackSample extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    // handle error
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

명령줄 승인 코드 흐름

간단한 예시 코드: dailymotion-cmdline-sample:

/** Authorizes the installed application to access user's protected data. */
private static Credential authorize() throws Exception {
  OAuth2ClientCredentials.errorIfNotSpecified();
  // set up authorization code flow
  AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken
      .authorizationHeaderAccessMethod(),
      HTTP_TRANSPORT,
      JSON_FACTORY,
      new GenericUrl(TOKEN_SERVER_URL),
      new ClientParametersAuthentication(
          OAuth2ClientCredentials.API_KEY, OAuth2ClientCredentials.API_SECRET),
      OAuth2ClientCredentials.API_KEY,
      AUTHORIZATION_SERVER_URL).setScopes(Arrays.asList(SCOPE))
      .setDataStoreFactory(DATA_STORE_FACTORY).build();
  // authorize
  LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost(
      OAuth2ClientCredentials.DOMAIN).setPort(OAuth2ClientCredentials.PORT).build();
  return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}

private static void run(HttpRequestFactory requestFactory) throws IOException {
  DailyMotionUrl url = new DailyMotionUrl("https://api.dailymotion.com/videos/favorites");
  url.setFields("id,tags,title,url");

  HttpRequest request = requestFactory.buildGetRequest(url);
  VideoFeed videoFeed = request.execute().parseAs(VideoFeed.class);
  ...
}

public static void main(String[] args) {
  ...
  DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
  final Credential credential = authorize();
  HttpRequestFactory requestFactory =
      HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
        @Override
        public void initialize(HttpRequest request) throws IOException {
          credential.initialize(request);
          request.setParser(new JsonObjectParser(JSON_FACTORY));
        }
      });
  run(requestFactory);
  ...
}

브라우저 기반 클라이언트 흐름

다음은 암시적 부여 사양:

  • BrowserClientRequestUrl 사용 최종 사용자의 브라우저를 승인 페이지로 리디렉션합니다. 여기서 최종 사용자가 애플리케이션에 보호 대상 데이터에 대한 액세스 권한을 부여할 수 있습니다.
  • JavaScript 애플리케이션을 사용하여 URL에 있는 액세스 토큰을 처리합니다. 부분 URL은 인증 서버에 등록된 리디렉션 URI에 있습니다.

웹 애플리케이션의 샘플 사용법:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  String url = new BrowserClientRequestUrl(
      "https://server.example.com/authorize", "s6BhdRkqt3").setState("xyz")
      .setRedirectUri("https://client.example.com/cb").build();
  response.sendRedirect(url);
}

만료된 액세스 토큰 감지

OAuth 2.0 Bearer 사양에 따라, 액세스 권한이 만료된 보호된 리소스에 액세스하기 위해 서버가 호출될 때 토큰의 경우 서버는 일반적으로 HTTP 401 Unauthorized 상태 코드로 응답합니다. 다음과 같습니다.

   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Bearer realm="example",
                     error="invalid_token",
                     error_description="The access token expired"

그러나 사양에 많은 유연성이 있는 것으로 보입니다. 대상 OAuth 2.0 제공업체의 문서를 확인하세요.

또 다른 방법은 expires_in 매개변수를 확인하는 것입니다. 액세스 토큰 응답 부여된 액세스 토큰의 전체 기간(초)을 지정합니다. 보통 1시간 정도 소요됩니다 하지만 마지막에 액세스 토큰이 실제로 만료되지는 않을 수도 있습니다. 서버에서 계속 액세스를 허용할 수 있습니다. 그래서 저희는 401 Unauthorized 상태 코드를 기다리는 것이 좋습니다. 경과 시간을 기준으로 토큰이 만료되었다고 가정합니다. 또는 다음 작업을 수행할 수 있습니다. 만료 직전에 액세스 토큰을 새로고침해 보세요. 그리고 토큰 서버가 사용할 수 없는 경우 401를 수신할 때까지 액세스 토큰을 계속 사용하세요. 이 인코더-디코더 아키텍처를 사용자 인증 정보.

또 다른 옵션은 모든 요청 전에 새 액세스 토큰을 가져오는 것이지만 매번 토큰 서버에 추가 HTTP 요청을 해야 하므로 선택의 폭이 넓지 않을 수도 있습니다. 이상적으로는 액세스 토큰을 안전한 영구 스토리지에 보관하여 새로운 액세스에 대한 애플리케이션의 요청을 최소화 토큰입니다. (하지만 설치된 애플리케이션의 경우 보안 저장소가 어려운 문제입니다.)

액세스 토큰은 만료 이외의 이유로 유효하지 않을 수 있습니다. 예를 들어 사용자가 명시적으로 토큰을 취소한 경우 오류 처리 코드가 강력합니다. 토큰이 더 이상 만료되거나 취소된 경우 액세스 권한을 삭제해야 합니다. 삭제할 수 있습니다 예를 들어 Android에서는 AccountManager.invalidateAuthToken을 확인할 수 있습니다.