Omówienie
Cel: ten dokument zawiera opis ogólnych funkcji OAuth 2.0 oferowanych przez bibliotekę klienta OAuth Google dla języka Java. Możesz używać tych funkcji do uwierzytelniania i autoryzacji w dowolnych usługach internetowych.
Instrukcje dotyczące używania GoogleCredential
do autoryzacji OAuth 2.0 w usługach Google znajdziesz w artykule Używanie OAuth 2.0 z biblioteką klienta interfejsu API Google dla języka Java.
Podsumowanie: OAuth 2.0 to standardowa specyfikacja umożliwiająca użytkownikom autoryzowanie aplikacji klienckiej w celu bezpiecznego uzyskiwania dostępu do zasobów chronionych po stronie serwera. Dodatkowo specyfikacja tokena uprawnień OAuth 2.0 wyjaśnia, jak uzyskać dostęp do tych zasobów za pomocą tokena dostępu przyznanego podczas procesu autoryzacji użytkownika.
Szczegółowe informacje znajdziesz w dokumentacji Javadoc dotyczącej tych pakietów:
- com.google.api.client.auth.oauth2 (z google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (z google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (from google-oauth-client-appengine)
Rejestracja klienta
Zanim zaczniesz korzystać z biblioteki klienta Google OAuth dla Javy, prawdopodobnie musisz zarejestrować aplikację na serwerze autoryzacji, by otrzymywać identyfikator klienta i tajny klucz klienta. (Ogólne informacje o tym procesie znajdziesz w specyfikacji rejestracji klienta).
Dane logowania i magazyn danych logowania
Credential to bezpieczna w wątku klasa pomocnicza OAuth 2.0, która umożliwia dostęp do zasobów chronionych za pomocą tokena dostępu. W przypadku użycia tokena odświeżania Credential
odświeża też token dostępu po jego wygaśnięciu. Jeśli na przykład masz już token dostępu, możesz wysłać żądanie w ten 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 zachowywać token dostępu i token odświeżania danych logowania, aby w przyszłości uniknąć przekierowania na stronę autoryzacji w przeglądarce. Implementacja CredentialStore w tej bibliotece została wycofana i zostanie usunięta w przyszłych wersjach. Możesz też użyć interfejsów DataStoreFactory i DataStore z StoredCredential, które są udostępniane przez bibliotekę klienta HTTP Google dla Javy.
Możesz użyć jednej z tych implementacji udostępnionych przez bibliotekę:
- JdoDataStoreFactory zapisuje dane logowania za pomocą JDO.
- AppEngineDataStoreFactory zachowuje dane logowania za pomocą interfejsu Google App Engine Data Store API.
- MemoryDataStoreFactory „zachowuje” dane logowania w pamięci, co jest przydatne tylko jako krótkoterminowa pamięć masowa przez cały okres trwania procesu.
- FileDataStoreFactory zapisuje dane logowania w pliku.
Użytkownicy Google App Engine:
Interfejs AppEngineCredentialStore został wycofany i zostanie usunięty.
Zalecamy użycie AppEngineDataStoreFactory z StoredCredential. Jeśli masz dane logowania zapisane w stary sposób, możesz użyć do migracji dodanych metod pomocniczych migrateTo(AppEngineDataStoreFactory) lub migrateTo(DataStore).
Użyj interfejsu DataStoreCredentialRefreshListener i ustaw go dla danych uwierzytelniających za pomocą metody GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).
Przepływ kodu autoryzacji
Użyj procesu autoryzacji za pomocą kodu, aby umożliwić użytkownikowi przyznanie aplikacji dostępu do jego chronionych danych. Protokół dla tego przepływu jest określony w specyfikacji kodu autoryzacji.
Ten proces jest realizowany za pomocą AuthorizationCodeFlow. Kroki:
- Użytkownik loguje się w aplikacji. Musisz powiązać tego użytkownika z identyfikatorem użytkownika, który jest unikalny w Twojej aplikacji.
- Wywołaj metodę AuthorizationCodeFlow.loadCredential(String) na podstawie identyfikatora użytkownika, aby sprawdzić, czy dane logowania użytkownika są już znane. Jeśli tak, to już wszystko.
- W przeciwnym razie wywołaj metodę AuthorizationCodeFlow.newAuthorizationUrl() i przekieruj przeglądarkę użytkownika na stronę autoryzacji, na której może on przyznać aplikacji dostęp do swoich chronionych danych.
- Następnie przeglądarka wykonuje przekierowanie na adres URL przekierowania za pomocą parametru zapytania „kod”, którego można następnie użyć do żądania tokena dostępu za pomocą metody AuthorizationCodeFlow.newTokenRequest(String).
- Aby zapisać dane logowania i uzyskać dostęp do zasobów chronionych, użyj metody AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String).
Jeśli nie używasz klasy AuthorizationCodeFlow, możesz użyć klas niskiego poziomu:
- Użyj metody DataStore.get(String), aby załadować dane logowania ze sklepu na podstawie identyfikatora użytkownika.
- Użyj parametru AuthorizationCodeRequestUrl, aby przekierować przeglądarkę na stronę autoryzacji.
- Użyj parametru AuthorizationCodeResponseUrl, aby przetworzyć odpowiedź autoryzacji i przeanalizować kod autoryzacji.
- Aby poprosić o token dostępu i ewentualnie token odświeżania, użyj żądania AuthorizationCodeTokenRequest.
- Utwórz nowe dane logowania i zapisz je za pomocą metody DataStore.set(String, V).
- Uzyskaj dostęp do zasobów chronionych przy użyciu danych logowania. Tokeny dostępu, które wygasły, są automatycznie odświeżane za pomocą tokena odświeżania (jeśli to możliwe). Pamiętaj, aby użyć funkcji DataStoreCredentialRefreshListener i ustawić ją dla danych logowania za pomocą Credential.Builder.addRefreshListener(CredentialRefreshListener).
Przepływ kodu autoryzacji serwera Servlet
Ta biblioteka udostępnia pomocnicze klasy serwletów, które znacznie upraszczają przepływ kodu autoryzacji w przypadku podstawowych zastosowań. Wystarczy, że podasz konkretne podklasy AbstractAuthorizationCodeServlet i AbstractAuthorizationCodeCallbackServlet (z google-oauth-client-servlet) i dodasz je do pliku web.xml. Pamiętaj, że nadal musisz zadbać o logowanie użytkownika w aplikacji internetowej i wyodrębnienie identyfikatora 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 identyczny jak w przypadku servletów, z tym wyjątkiem, że możemy korzystać z interfejsu Users Java API w Google App Engine. Aby umożliwić korzystanie z interfejsu Users Java API, użytkownik musi się zalogować. Informacje o przekierowywaniu użytkowników na stronę logowania, jeśli nie są jeszcze zalogowani, znajdziesz w sekcji Bezpieczeństwo i uwierzytelnianie (w pliku web.xml).
Podstawowa różnica w stosunku do przypadku serwletu polega na tym, że podajesz konkretne podklasy klasy AbstractAppEngineAuthorizationCodeServlet i AbstractAppEngineAuthorizationCodeCallbackServlet (z google-oauth-client-appengine). Rozszerzają one abstrakcyjne klasy serwera servleta i zaimplementują metodę getUserId
za pomocą interfejsu Users Java API. AppEngineDataStoreFactory (z biblioteki klienta HTTP Google dla języka Java) to dobre rozwiązanie do trwałego przechowywania danych logowania za pomocą 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 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); ... }
Przepływ klienta w przeglądarce
Oto typowe czynności związane z przetwarzaniem danych klienta w przeglądarce zgodnie ze specyfikacją Implicit Grant:
- Za pomocą parametru BrowserClientRequestUrl przekieruj przeglądarkę użytkownika na stronę autoryzacji, na której użytkownik może zezwolić aplikacji na dostęp do jego chronionych danych.
- Użyj aplikacji JavaScript, aby przetworzyć token dostępu znaleziony w fragmentach adresu URL w identyfikatorze URI przekierowania zarejestrowanym na serwerze autoryzacji.
Przykładowe zastosowanie w przypadku 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 wygasłego tokena dostępu
Zgodnie ze specyfikacją nośnika protokołu OAuth 2.0 gdy serwer jest wywoływany w celu uzyskania dostępu do chronionego zasobu z wygasłym tokenem dostępu, serwer zwykle w odpowiedzi przesyła kod stanu HTTP 401 Unauthorized
, taki jak:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
Wygląda jednak, że specyfikacja jest bardzo elastyczna. Więcej informacji znajdziesz w dokumentacji dostawcy usługi OAuth 2.0.
Innym sposobem jest sprawdzenie parametru expires_in
w odpowiedzi na żądanie tokena dostępu.
Określa okres ważności przyznanego tokena dostępu w sekundach, który wynosi zazwyczaj godzinę. Token dostępu może jednak nie wygasnąć z końcem tego okresu i serwer może nadal zezwalać na dostęp. Dlatego zazwyczaj zalecamy czekanie na kod stanu 401 Unauthorized
, a nie zakładanie, że token wygasł na podstawie upływu czasu. Możesz też spróbować odświeżyć token dostępu tuż przed jego wygaśnięciem. Jeśli serwer tokenów będzie niedostępny, używaj go do momentu otrzymania 401
. Jest to strategia używana domyślnie w danych uwierzytelniających.
Inną opcją jest pobieranie nowego tokena dostępu przed każdym żądaniem, ale wymaga to dodatkowego żądania HTTP do serwera tokenów za każdym razem, więc jest to prawdopodobnie zły wybór pod względem szybkości i korzystania z sieci. Token dostępu należy przechowywać w bezpiecznej, trwałej pamięci masowej, aby zminimalizować liczbę żądań wysyłanych przez aplikację o nowe tokeny dostępu. (W przypadku zainstalowanych aplikacji bezpieczne przechowywanie jest trudnym problemem).
Pamiętaj, że token dostępu może stać się nieważny z powodów innych niż upłynięcie jego ważności, na przykład jeśli użytkownik wyraźnie go cofnie. Dlatego zadbaj o to, aby kod obsługi błędów był niezawodny. Gdy wykryjesz, że token nie jest już ważny, na przykład gdy wygasł lub został cofnięty, musisz usunąć go ze swojej pamięci. Na przykład w przypadku Androida musisz wywołać metodę AccountManager.invalidateAuthToken.