개요
목적: 이 문서에서는 GoogleCredential 유틸리티 클래스를 사용하여 Google 서비스에서 OAuth 2.0 승인을 수행하는 방법을 설명합니다. 제공되는 일반 OAuth 2.0 함수에 대한 자세한 내용은 OAuth 2.0 및 자바용 Google OAuth 클라이언트 라이브러리를 참조하세요.
요약: Google 서비스에 저장된 보호되는 데이터에 액세스하려면 승인에 OAuth 2.0을 사용합니다. Google API는 다양한 유형의 클라이언트 애플리케이션에 OAuth 2.0 흐름을 지원합니다. 이러한 모든 흐름에서 클라이언트 애플리케이션은 클라이언트 애플리케이션 및 액세스되는 보호되는 데이터의 소유자와만 연결된 액세스 토큰을 요청합니다. 또한 액세스 토큰은 클라이언트 애플리케이션이 액세스할 수 있는 데이터의 종류를 정의하는 제한된 범위 (예: '작업 관리')와 연결됩니다. OAuth 2.0의 중요한 목표는 보호된 토큰을 안전하고 편리하게 액세스하는 동시에 액세스 토큰을 도난당했을 때 미칠 수 있는 영향을 최소화하는 것입니다.
자바용 Google API 클라이언트 라이브러리의 OAuth 2.0 패키지는 범용 자바용 Google OAuth 2.0 클라이언트 라이브러리를 기반으로 빌드됩니다.
자세한 내용은 다음 패키지에 관한 Javadoc 문서를 참고하세요.
- com.google.api.client.googleapis.auth.oauth2(google-api-client에서)
- com.google.api.client.googleapis.extensions.appengine.auth.oauth2(google-api-client-appengine에서)
Google API 콘솔
Google API에 액세스하려면 클라이언트가 설치된 애플리케이션, 모바일 애플리케이션, 웹 서버 또는 브라우저에서 실행되는 클라이언트인지 여부에 관계없이 인증 및 결제 목적으로 Google API 콘솔에서 프로젝트를 설정해야 합니다.
사용자 인증 정보를 올바르게 설정하는 방법은 API 콘솔 도움말을 참조하세요.
사용자 인증 정보
GoogleCredential
GoogleCredential은 액세스 토큰을 사용하여 보호된 리소스에 액세스하기 위한 OAuth 2.0의 스레드 안전 도우미 클래스입니다. 예를 들어 이미 액세스 토큰이 있는 경우 다음과 같은 방법으로 요청할 수 있습니다.
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken); Plus plus = new Plus.builder(new NetHttpTransport(), GsonFactory.getDefaultInstance(), credential) .setApplicationName("Google-PlusSample/1.0") .build();
Google App Engine ID
이 대체 사용자 인증 정보는 Google App Engine App Identity Java API를 기반으로 합니다. 클라이언트 애플리케이션이 최종 사용자의 데이터에 대한 액세스를 요청하는 사용자 인증 정보와 달리 App Identity API는 클라이언트 애플리케이션의 데이터에 액세스할 수 있는 권한을 제공합니다.
google-api-client-appengine의 AppIdentityCredential을 사용합니다. Google App Engine이 모든 세부정보를 처리하므로 이 사용자 인증 정보는 훨씬 더 간단합니다. 필요한 OAuth 2.0 범위만 지정하세요.
urlshortener-robots-appengine-sample에서 가져온 코드의 예:
static Urlshortener newUrlshortener() { AppIdentityCredential credential = new AppIdentityCredential( Collections.singletonList(UrlshortenerScopes.URLSHORTENER)); return new Urlshortener.Builder(new UrlFetchTransport(), GsonFactory.getDefaultInstance(), credential) .build(); }
데이터 저장소
액세스 토큰의 만료일은 일반적으로 1시간이며, 이 값을 사용하려고 하면 오류가 발생합니다.
GoogleCredential은 토큰을 자동으로 '갱신'합니다. 즉, 새 액세스 토큰을 얻을 수 있습니다. 이 작업은 수명이 긴 갱신 토큰을 통해 이루어지며, 일반적으로 승인 코드 흐름 중에 access_type=offline
매개변수를 사용하면 액세스 토큰과 함께 수신됩니다 (GoogleauthorizationCodeFlow.Builder.setAccessType(String) 참고).
대부분의 애플리케이션은 사용자 인증 정보 액세스 토큰 또는 갱신 토큰을 유지해야 합니다. 사용자 인증 정보의 액세스 및 갱신 토큰을 유지하려면 DataStoreFactory를 SavedCredential으로 직접 구현하거나 라이브러리에서 제공하는 다음 구현 중 하나를 사용하면 됩니다.
- AppEngineDataStoreFactory: Google App Engine Data Store API를 사용하여 사용자 인증 정보를 유지합니다.
- MemoryDataStoreFactory: 메모리의 사용자 인증 정보로, 프로세스의 전체 기간 동안 단기 스토리지로만 유용합니다.
- FileDataStoreFactory: 파일의 사용자 인증 정보를 유지합니다.
AppEngine 사용자: AppEngineCredentialStore는 지원 중단되었으며 곧 삭제됩니다. AppCredential과 함께 AppEngineDataStoreFactory를 사용하는 것이 좋습니다. 이전 방식으로 사용자 인증 정보를 저장한 경우 추가된 도우미 메서드인 migrateTo(AppEngineDataStoreFactory) 또는 migrateTo(DataStore)를 사용하여 마이그레이션할 수 있습니다.
DataStoreCredentialRefreshListener를 사용하고 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener)를 사용하여 사용자 인증 정보에 설정할 수 있습니다.
승인 코드 흐름
승인 코드 흐름을 사용하여 최종 사용자가 Google API에서 보호되는 데이터에 액세스할 수 있는 권한을 애플리케이션에 부여할 수 있습니다. 이 흐름의 프로토콜은 승인 코드 부여에 지정됩니다.
이 흐름은 GoogleApprovalCodeFlow를 사용하여 구현됩니다. 단계는 다음과 같습니다.
- 최종 사용자가 애플리케이션에 로그인합니다. 이 사용자를 애플리케이션의 고유한 사용자 ID와 연결해야 합니다.
- 사용자 ID를 기반으로 authorizationCodeFlow.loadCredential(String)을 호출하여 최종 사용자의 사용자 인증 정보가 이미 있는지 확인합니다. 완료되었다면 완료된 것입니다.
- 그렇지 않은 경우 ApprovalCodeFlow.newauthorizationUrl()을 호출하고 최종 사용자의 브라우저를 승인 페이지로 전달하여 보호된 데이터에 대한 액세스 권한을 애플리케이션에 부여합니다.
- 그러면 Google 승인 서버가
code
쿼리 매개변수와 함께 애플리케이션에서 지정한 리디렉션 URL로 브라우저를 다시 리디렉션합니다.code
매개변수를 사용하여 authorizationCodeFlow.newTokenRequest(String)으로 액세스 토큰을 요청합니다. - ApprovalCodeFlow.createAndStoreCredential(TokenResponse, String)을 사용하여 보호된 리소스에 액세스하기 위한 사용자 인증 정보를 저장하고 가져옵니다.
또는 GoogleauthorizationCodeFlow를 사용하지 않는 경우 하위 수준의 클래스를 사용할 수 있습니다.
- DataStore.get(String)을 사용하여 사용자 ID를 기반으로 스토어에서 사용자 인증 정보를 로드합니다.
- GoogleAuthorizeCodeRequestUrl을 사용하여 브라우저를 승인 페이지로 이동합니다.
- ApprovalCodeResponseUrl을 사용하여 승인 응답을 처리하고 승인 코드를 파싱합니다.
- GoogleauthorizationCodeTokenRequest를 사용하여 액세스 토큰 및 갱신 토큰을 요청합니다.
- 새 GoogleCredential을 만들고 DataStore.set(String, V)를 사용하여 저장합니다.
GoogleCredential
를 사용하여 보호되는 리소스에 액세스합니다. 만료된 액세스 토큰은 갱신 토큰 (해당하는 경우)을 사용하여 자동으로 새로고침됩니다. DataStoreCredentialRefreshListener를 사용하고 GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener)를 사용하여 사용자 인증 정보에 맞게 설정해야 합니다.
Google API 콘솔에서 프로젝트를 설정할 때 사용 중인 흐름에 따라 다양한 사용자 인증 정보 중에서 선택합니다. 자세한 내용은 OAuth 2.0 설정 및 OAuth 2.0 시나리오를 참고하세요. 각 흐름의 코드 스니펫은 아래와 같습니다.
웹 서버 애플리케이션
이 흐름의 프로토콜은 웹 서버 애플리케이션에 OAuth 2.0 사용에 설명되어 있습니다.
이 라이브러리는 서블릿 도우미 클래스를 제공하여 기본 사용 사례의 승인 코드 흐름을 크게 단순화합니다. google-oauth-client- 서블릿에서 제공하는 AbstractApprovalCodeServlet 및 AbstractApprovalCodeCallbackServlet의 구체적인 서브클래스를 web.xml 파일에 추가하기만 하면 됩니다. 웹 애플리케이션의 사용자 로그인을 관리하고 사용자 ID를 추출해야 합니다.
public class CalendarServletSample 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 GoogleAuthorizationCodeFlow.Builder( new NetHttpTransport(), GsonFactory.getDefaultInstance(), "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]", Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory( DATA_STORE_FACTORY).setAccessType("offline").build(); } @Override protected String getUserId(HttpServletRequest req) throws ServletException, IOException { // return user ID } } public class CalendarServletCallbackSample 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 GoogleAuthorizationCodeFlow.Builder( new NetHttpTransport(), GsonFactory.getDefaultInstance() "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]", Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory( DATA_STORE_FACTORY).setAccessType("offline").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 자바 API를 사용 설정하려면 사용자가 로그인해야 합니다. 아직 로그인하지 않은 사용자를 로그인 페이지로 리디렉션하는 방법에 대한 자세한 내용은 security and Authentication(web.xml)을 참조하세요.
서블릿 사례와의 주요 차이점은 AbstractAppEngine AuthorizationCodeServlet과 AbstractAppEngine AuthorizationCodeCallbackServlet(google-oauth-client-appengine)의 구체적인 서브클래스를 제공한다는 것입니다.
이 API는 추상 서블릿 클래스를 확장하고 Users Java API를 사용하여 getUserId
메서드를 구현합니다. AppEngineDataStoreFactory(google-http-client-appengine)는 Google App Engine Data Store API를 사용하여 사용자 인증 정보를 유지하는 데 적합한 옵션입니다.
calendar-appengine-sample에서 가져온 예시 (약간 수정됨):
public class CalendarAppEngineSample extends AbstractAppEngineAuthorizationCodeServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { // do stuff } @Override protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException { return Utils.getRedirectUri(req); } @Override protected AuthorizationCodeFlow initializeFlow() throws IOException { return Utils.newFlow(); } } class Utils { static String getRedirectUri(HttpServletRequest req) { GenericUrl url = new GenericUrl(req.getRequestURL().toString()); url.setRawPath("/oauth2callback"); return url.build(); } static GoogleAuthorizationCodeFlow newFlow() throws IOException { return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, getClientCredential(), Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory( DATA_STORE_FACTORY).setAccessType("offline").build(); } } public class OAuth2Callback extends AbstractAppEngineAuthorizationCodeCallbackServlet { private static final long serialVersionUID = 1L; @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 { String nickname = UserServiceFactory.getUserService().getCurrentUser().getNickname(); resp.getWriter().print("<h3>" + nickname + ", why don't you want to play with me?</h1>"); resp.setStatus(200); resp.addHeader("Content-Type", "text/html"); } @Override protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException { return Utils.getRedirectUri(req); } @Override protected AuthorizationCodeFlow initializeFlow() throws IOException { return Utils.newFlow(); } }
추가 샘플은 storage-serviceaccount-appengine-sample을 참고하세요.
서비스 계정
GoogleCredential은 서비스 계정도 지원합니다. 클라이언트 애플리케이션이 최종 사용자의 데이터에 대한 액세스를 요청하는 사용자 인증 정보와 달리, 서비스 계정은 클라이언트 애플리케이션의 데이터에 대한 액세스 권한을 제공합니다. 클라이언트 애플리케이션이 Google API 콘솔에서 다운로드한 비공개 키를 사용하여 액세스 토큰 요청에 서명합니다.
plus-serviceaccount-cmdline-sample에서 발췌한 예시 코드:
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); JsonFactory jsonFactory = GsonFactory.getDefaultInstance(); ... // Build service account credential. GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json")) .createScoped(Collections.singleton(PlusScopes.PLUS_ME)); // Set up global Plus instance. plus = new Plus.Builder(httpTransport, jsonFactory, credential) .setApplicationName(APPLICATION_NAME).build(); ...
추가 샘플은 storage-serviceaccount-cmdline-sample을 참고하세요.
명의 도용
서비스 계정 흐름을 사용하여 소유한 도메인의 사용자로 가장할 수도 있습니다. 위의 서비스 계정 흐름과 매우 비슷하지만 GoogleCredential.Builder.setServiceAccountUser(String)를 추가로 호출합니다.
설치된 애플리케이션
설치된 애플리케이션에 OAuth 2.0 사용하기에 설명된 명령줄 승인 코드 흐름입니다.
plus-cmdline-sample의 스니펫 예:
public static void main(String[] args) { try { httpTransport = GoogleNetHttpTransport.newTrustedTransport(); dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR); // authorization Credential credential = authorize(); // set up global Plus instance plus = new Plus.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName( APPLICATION_NAME).build(); // ... } private static Credential authorize() throws Exception { // load client secrets GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(PlusSample.class.getResourceAsStream("/client_secrets.json"))); // set up authorization code flow GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( httpTransport, JSON_FACTORY, clientSecrets, Collections.singleton(PlusScopes.PLUS_ME)).setDataStoreFactory( dataStoreFactory).build(); // authorize return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user"); }
클라이언트 측 애플리케이션
클라이언트 측 애플리케이션에 OAuth 2.0 사용에 설명된 브라우저 기반 클라이언트 흐름을 사용하려면 일반적으로 다음 단계를 따르세요.
- GoogleBrowserClientRequestUrl을 사용하여 브라우저의 최종 사용자를 승인 페이지로 리디렉션하세요. 그러면 브라우저 애플리케이션이 최종 사용자의 보호되는 데이터에 액세스할 수 있습니다.
- 자바스크립트용 Google API 클라이언트 라이브러리를 사용하여 Google API 콘솔에 등록된 리디렉션 URI의 URL 프래그먼트에 있는 액세스 토큰을 처리합니다.
웹 애플리케이션의 사용 예시는 다음과 같습니다.
public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException { String url = new GoogleBrowserClientRequestUrl("812741506391.apps.googleusercontent.com", "https://oauth2.example.com/oauthcallback", Arrays.asList( "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile")).setState("/profile").build(); response.sendRedirect(url); }
Android
Android에서 사용할 라이브러리:
Android용으로 개발 중이고 사용할 Google API가 Google Play 서비스 라이브러리에 포함되어 있다면 이 라이브러리를 사용하여 최상의 성능과 환경을 경험하세요. Android와 함께 사용하려는 Google API가 Google Play 서비스 라이브러리의 일부가 아닌 경우 Android 4.0(Ice Cream Sandwich) 이상을 지원하고 여기에 설명된 자바용 Google API 클라이언트 라이브러리를 사용할 수 있습니다. 자바용 Google API 클라이언트 라이브러리에서는 Android가 @Beta로 지원됩니다.
배경 정보
Eclair (SDK 2.1)부터 Android 기기에서 계정 관리자를 사용하여 사용자 계정을 관리합니다. 모든 Android 애플리케이션 승인은 AccountManager를 사용하여 SDK에서 중앙 집중식으로 관리합니다. 애플리케이션에 필요한 OAuth 2.0 범위를 지정하면 사용할 액세스 토큰을 반환합니다.
OAuth 2.0 범위는 authTokenType
매개변수를 통해 oauth2:
에 범위를 지정합니다. 예를 들면 다음과 같습니다.
oauth2:https://www.googleapis.com/auth/tasks
Google Tasks API에 대한 읽기/쓰기 액세스 권한을 지정합니다. OAuth 2.0 범위가 여러 개 필요한 경우 공백으로 구분된 목록을 사용합니다.
일부 API에는 작동하는 authTokenType
특수 매개변수도 있습니다. 예를 들어 '할 일 관리'는 위에 표시된 authtokenType
예시의 별칭입니다.
또한 Google API 콘솔에서 API 키를 지정해야 합니다. 그렇지 않으면 AccountManager가 제공하는 토큰에서 익명 할당량만 제공하며, 이는 대개 매우 낮습니다. 반면에 API 키를 지정하면 무료 할당량을 더 많이 받을 수 있으며 이보다 많은 사용량에 대한 청구를 설정할 수도 있습니다.
tasks-android-sample에서 가져온 코드 스니펫의 예:
com.google.api.services.tasks.Tasks service; @Override public void onCreate(Bundle savedInstanceState) { credential = GoogleAccountCredential.usingOAuth2(this, Collections.singleton(TasksScopes.TASKS)); SharedPreferences settings = getPreferences(Context.MODE_PRIVATE); credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null)); service = new com.google.api.services.tasks.Tasks.Builder(httpTransport, jsonFactory, credential) .setApplicationName("Google-TasksAndroidSample/1.0").build(); } private void chooseAccount() { startActivityForResult(credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_GOOGLE_PLAY_SERVICES: if (resultCode == Activity.RESULT_OK) { haveGooglePlayServices(); } else { checkGooglePlayServicesAvailable(); } break; case REQUEST_AUTHORIZATION: if (resultCode == Activity.RESULT_OK) { AsyncLoadTasks.run(this); } else { chooseAccount(); } break; case REQUEST_ACCOUNT_PICKER: if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) { String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME); if (accountName != null) { credential.setSelectedAccountName(accountName); SharedPreferences settings = getPreferences(Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); editor.putString(PREF_ACCOUNT_NAME, accountName); editor.commit(); AsyncLoadTasks.run(this); } } break; } }