Przegląd
Cel: w tym dokumencie opisano ogólne funkcje OAuth 2.0 oferowane przez Bibliotekę klienta Google OAuth dla języka Java. Za pomocą tych funkcji możesz uwierzytelniać i autoryzować wszystkie usługi internetowe.
Instrukcje korzystania z GoogleCredential
do autoryzacji OAuth 2.0 w usługach Google znajdziesz w artykule o używaniu OAuth 2.0 w Bibliotece klienta Google API w języku Java.
Podsumowanie: OAuth 2.0 to standardowa specyfikacja umożliwiająca użytkownikom bezpieczny dostęp aplikacji klienckiej do chronionych zasobów po stronie serwera. Dodatkowo specyfikacja tokena okaziciela OAuth 2.0 wyjaśnia, jak uzyskać dostęp do tych chronionych zasobów, korzystając z tokena dostępu przyznanego podczas procesu autoryzacji użytkownika.
Szczegółowe informacje na temat tych pakietów znajdziesz w dokumentacji Javadoc:
- com.google.api.client.auth.oauth2 (z google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (z google-oauth-client-servicelet)
- com.google.api.client.extensions.appengine.auth.oauth2 (z google-oauth-client-appengine)
Rejestracja klienta
Zanim zaczniesz korzystać z biblioteki klienta Google OAuth dla języka Java, prawdopodobnie musisz zarejestrować swoją aplikację na serwerze autoryzacji, aby otrzymać identyfikator klienta i tajny klucz klienta. Ogólne informacje o tym procesie znajdziesz w specyfikacji rejestracji klienta.
Magazyn danych logowania i danych logowania
Dane logowania to zabezpieczona wątkami klasa pomocnicza OAuth 2.0, która pozwala uzyskać dostęp do chronionych zasobów za pomocą tokena dostępu. W przypadku użycia tokena odświeżania Credential
odświeża też token dostępu, gdy token dostępu wygaśnie za pomocą tokena odświeżania. Jeśli na przykład masz już token dostępu, możesz przesłać żądanie w następujący sposób:
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(); }
Większość aplikacji musi zachować token dostępu danych logowania i odświeżyć token, aby uniknąć przyszłego przekierowania na stronę autoryzacji w przeglądarce. Implementacja CredentialStore w tej bibliotece została wycofana i zostanie usunięta w kolejnych wersjach. Zamiast tego możesz korzystać z interfejsów DataStoreFactory i DataStore oraz StoredCredential, które są dostarczane przez bibliotekę klienta Google HTTP dla języka Java.
Możesz skorzystać z jednej z tych implementacji dostępnych w bibliotece:
- JdoDataStoreFactory przechowuje dane logowania za pomocą JDO.
- AppEngineDataStoreFactory przechowuje dane logowania za pomocą interfejsu Google App Engine Data Store API.
- MemoryDataStoreFactory "persists" dane logowania w pamięci, co jest przydatne tylko jako krótkoterminowe przechowywanie przez cały proces.
- FileDataStoreFactory przechowuje dane logowania w pliku.
Użytkownicy Google App Engine:
Parametr AppEngineCredentialStore został wycofany i jest usuwany.
Zalecamy użycie metody AppEngineDataStoreFactory i StoredCredentials. Jeśli masz dane logowania zapisane w starym stylu, możesz użyć dodanych metod pomocniczych migrateTo(AppEngineDataStoreFactory) lub migrateTo(DataStore).
Użyj metody DataStoreCredentialRefreshListener i ustaw ją dla danych logowania za pomocą funkcji GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).
Przepływ kodu autoryzacji
Użyj procesu autoryzacji, aby zezwolić użytkownikowi na przyznanie aplikacji dostępu do chronionych danych. Protokół tego procesu jest podany w specyfikacji uwierzytelniania kodu autoryzacji.
Ten proces jest zaimplementowany za pomocą AuthorizationCodeFlow. Kroki:
- Użytkownik loguje się do aplikacji. Musisz powiązać go z identyfikatorem użytkownika, który jest unikalny dla Twojej aplikacji.
- Wywołaj AuthorizationCodeFlow.loadCredential(string) na podstawie identyfikatora użytkownika, by sprawdzić, czy dane logowania użytkownika są już znane. Jeśli tak, wszystko gotowe.
- Jeśli nie, wywołaj metodę AuthorizationCodeFlow.newAuthorizationUrl() i skieruj przeglądarkę użytkownika na stronę autoryzacji, na której może przyznać aplikacji dostęp do swoich danych chronionych.
- Następnie przeglądarka przekierowuje do adresu URL przekierowania za pomocą parametru zapytania "code", który może zostać potem użyty do żądania tokena dostępu przy użyciu AuthorizationCodeFlow.newTokenRequest(String).
- Użyj metody AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String), by przechowywać i pobierać dane logowania do uzyskiwania dostępu do chronionych zasobów.
Jeśli nie korzystasz z AuthorizationCodeFlow, możesz też użyć klas niższego poziomu:
- Użyj narzędzia DataStore.get(String), aby wczytać dane uwierzytelniające ze sklepu na podstawie identyfikatora użytkownika.
- Użyj pola AuthorizationCodeRequestUrl, by przekierować przeglądarkę na stronę autoryzacji.
- Aby przetworzyć odpowiedź autoryzacji i przeanalizować kod autoryzacji, użyj polecenia AuthorizationCodeResponseUrl.
- Za pomocą AuthorizationCodeTokenRequest poproś o token dostępu, a w razie potrzeby token odświeżania.
- Utwórz nowe dane logowania i zapisz je za pomocą DataStore.set(String, V).
- Uzyskaj dostęp do chronionych zasobów za pomocą danych logowania. Wygasłe tokeny dostępu są automatycznie odświeżane przy użyciu tokena odświeżania (w stosownych przypadkach). Pamiętaj, aby użyć DataStoreCredentialRefreshListener i ustawić dla danych logowania za pomocą funkcji Credential.Builder.addRefreshListener(CredentialRefreshListener).
Przepływ kodu autoryzacji Servlet
Ta biblioteka udostępnia klasy pomocnicze serwletu, które znacznie upraszczają kod autoryzacji dla podstawowych przypadków użycia. Wystarczy podać konkretne klasy podrzędne AbstractAuthorizationCodeServlet i AbstractAuthorizationCodeCallbackServlet (z google-oauth-client-servlet) i dodać je do pliku web.xml. Nadal musisz zadbać o logowanie użytkownika aplikacji internetowej i wyodrębnić identyfikator użytkownika.
Przykładowy kod:
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 } }
Przepływ kodu autoryzacji Google App Engine
Przepływ kodu autoryzacji w App Engine jest prawie taki sam jak kod autoryzacji serwletu, z wyjątkiem tego, że możemy użyć interfejsu Java Java App Engine. Aby włączyć interfejs Java Java, użytkownik musi być zalogowany. Informacje o przekierowaniu użytkowników na stronę logowania, jeśli nie są jeszcze zalogowani, znajdziesz w artykule Bezpieczeństwo i uwierzytelnianie (w pliku web.xml).
Główną różnicą od przypadku serwletu jest to, że należy podać konkretne podkategorie AbstractAppEngineAuthCodeServlet i AbstractAppEngineAuthCodeCallbackServlet (z google-oauth-client-appengine). Rozszerzają zajęcia abstrakcyjne i zaimplementują metodę getUserId
za pomocą interfejsu User Java API. AppEngineDataStoreFactory (z Biblioteki klienta Google HTTP dla języka Java to dobre rozwiązanie do przechowywania danych logowania przy użyciu interfejsu Google App Engine Data Store API).
Przykładowy kod:
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(); } }
Przepływ kodu autoryzacji w wierszu poleceń
Uproszczony przykładowy kod pobrany z 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); ... }
Wzorzec klienta oparty na przeglądarce
Poniżej znajdziesz typowe czynności, które trzeba wykonać w przeglądarce klienta, zgodnie ze specyfikacją elementu zamówienia pośredniego:
- Za pomocą przeglądarki BrowserClientRequestUrl przekieruj przeglądarkę użytkownika na stronę autoryzacji, na której może on przyznać aplikacji dostęp do swoich danych chronionych.
- Użyj tokena JavaScript, aby przetworzyć token dostępu znaleziony w sekcji adresu URL w identyfikatorze URI przekierowania zarejestrowanym na serwerze autoryzacji.
Przykład użycia aplikacji internetowej:
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); }
Wykrywanie nieważnego tokena dostępu
Zgodnie ze specyfikacją uwierzytelniania okaziciela OAuth 2.0, gdy serwer jest proszony o dostęp do chronionego zasobu z wygasłym tokenem dostępu, zwykle odpowiada za pomocą kodu stanu HTTP 401 Unauthorized
, takiego jak:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
Wydaje się jednak, że w specyfikacji masz znaczną elastyczność. Szczegółowe informacje znajdziesz w dokumentacji dostawcy OAuth 2.0.
Możesz też sprawdzić parametr expires_in
w odpowiedzi tokenu dostępu.
Określa czas działania tokena dostępu (w sekundach), który zazwyczaj wynosi godzinę. Token dostępu może jednak nie wygasnąć pod koniec tego okresu, a serwer nadal będzie zezwalać na dostęp. Dlatego zalecamy, aby poczekać, aż kod stanu pojawi się w polu 401 Unauthorized
, zamiast zakładać, że token wygasł w zależności od czasu, który upłynął. Możesz też spróbować odświeżyć token dostępu tuż przed wygaśnięciem, a jeśli serwer tokenów jest niedostępny, nadal używaj tokena dostępu do momentu otrzymania 401
. Ta strategia jest używana domyślnie w danych logowania.
Innym sposobem jest pobranie nowego tokena dostępu przed każdym żądaniem, ale wymaga to dodatkowego żądania HTTP do serwera tokenów za każdym razem, więc szybkość i sposób korzystania z sieci są złe. Najlepiej, aby token dostępu był przechowywany w bezpiecznym, trwałym miejscu, aby zminimalizować żądania aplikacji dotyczące nowych tokenów dostępu. Jednak w przypadku zainstalowanych aplikacji bezpieczny magazyn to trudny problem.
Pamiętaj, że token dostępu może być nieważny z innych powodów, np. gdy użytkownik wyraźnie unieważnił token dostępu, więc upewnij się, że kod obsługi błędów jest niezawodny. Gdy wykryjemy, że token jest już nieważny, na przykład wygasł lub został unieważniony, musisz usunąć dostęp z pamięci. Na przykład w systemie Android musisz wywołać AccountManager.invalidateAuthToken.