Opis
Cel: W tym dokumencie opisano ogólne funkcje protokołu OAuth 2.0 oferowane przez bibliotekę klienta Google OAuth dla języka Java. Funkcji tych możesz używać 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: protokół OAuth 2.0 to standardowa specyfikacja umożliwiająca użytkownikom bezpieczne autoryzowanie aplikacji klienckiej na dostęp do chronionych zasobów po stronie serwera. Dodatkowo specyfikacja tokena okaziciela OAuth 2.0 wyjaśnia, jak uzyskać dostęp do tych zabezpieczonych zasobów przy użyciu tokena dostępu przyznawanego podczas procesu autoryzacji użytkownika.
Szczegółowe informacje znajdziesz w dokumentacji Javadoc następujących pakietów:
- com.google.api.client.auth.oauth2 (z google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (from 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ć swoją aplikację na serwerze autoryzacji, aby otrzymać identyfikator klienta i tajny klucz klienta. Ogólne informacje na temat tego procesu znajdziesz w specyfikacji rejestracji klienta.
Magazyn danych logowania
Dane logowania to bezpieczna wątkowo klasa pomocnicza OAuth 2.0, która umożliwia dostęp do zabezpieczonych zasobów za pomocą tokena dostępu. Jeśli używasz tokena odświeżania, Credential
odświeża też przy użyciu tokena dostępu, gdy token dostępu wygaśnie. Jeśli na przykład masz już token dostępu, żądanie możesz wysłać 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 utrzymywać token dostępu i token odświeżania danych logowania, aby uniknąć w przyszłości przekierowania na stronę autoryzacji w przeglądarce. Implementacja CredentialStore w tej bibliotece została wycofana i zostanie usunięta w kolejnych wersjach. Alternatywą jest użycie interfejsów DataStoreFactory i DataStore z interfejsami StoredCredential udostępnianymi przez bibliotekę klienta HTTP Google dla języka Java.
Możesz użyć jednej z tych implementacji zapewnianych przez bibliotekę:
- JdoDataStoreFactory utrzymuje dane logowania za pomocą JDO.
- AppEngineDataStoreFactory przechowuje dane logowania przy użyciu interfejsu Google App Engine Data Store API.
- MemoryDataStoreFactory „zachowuje” dane logowania w pamięci, co jest przydatne tylko w przypadku krótkoterminowego przechowywania danych przez cały okres trwania procesu.
- FileDataStoreFactory przechowuje dane logowania w pliku.
Użytkownicy Google App Engine:
Interfejs AppEngineCredentialStore został wycofany i jest usuwany.
Zalecamy używanie AppEngineDataStoreFactory ze StoredCredential. Jeśli masz dane logowania zapisane w tradycyjny sposób, możesz przeprowadzić migrację za pomocą dodanych metod pomocniczych migrateTo(AppEngineDataStoreFactory) lub migrateTo(DataStore).
Użyj funkcji DataStoreCredentialRefreshListener i ustaw je na potrzeby danych logowania za pomocą GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).
Przepływ kodu autoryzacji
Użyj przepływu kodu autoryzacji, aby umożliwić użytkownikowi przyznanie aplikacji dostępu do swoich chronionych danych. Protokół tego procesu jest podany w specyfikacji przyznawania kodu autoryzacji.
Ten przepływ jest zaimplementowany za pomocą metody AuthorizationCodeFlow. Kroki:
- Użytkownik loguje się w Twojej aplikacji. Musisz powiązać go z unikalnym dla Twojej aplikacji identyfikatorem użytkownika.
- 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.
- Jeśli nie, wywołaj AuthorizationCodeFlow.newAuthorizationUrl() i przekieruj przeglądarkę użytkownika na stronę autoryzacji, na której może przyznać aplikacji dostęp do swoich chronionych danych.
- Następnie przeglądarka przekierowuje do przekierowania za pomocą parametru zapytania „code”, którego można użyć do żądania tokena dostępu za pomocą AuthorizationCodeFlow.newTokenRequest(String).
- Użyj AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) do przechowywania i uzyskania danych logowania umożliwiających dostęp do zabezpieczonych zasobów.
Jeśli nie używasz AuthorizationCodeFlow, możesz też użyć klas niższego poziomu:
- Użyj metody DataStore.get(String), aby wczytać dane logowania ze sklepu na podstawie identyfikatora użytkownika.
- Użyj AuthorizationCodeRequestUrl, aby przekierować przeglądarkę na stronę autoryzacji.
- Użyj AuthorizationCodeResponseUrl, aby przetworzyć odpowiedź autoryzacji i przeanalizować kod autoryzacji.
- Użyj AuthorizationCodeTokenRequest, aby zażądać tokena dostępu i ewentualnie tokena odświeżania.
- Utwórz nowe dane logowania i przechowuj je za pomocą DataStore.set(String, V).
- Dostęp do zabezpieczonych zasobów możesz uzyskać za pomocą danych logowania. Wygasłe tokeny dostępu są automatycznie odświeżane za pomocą tokena odświeżania (w stosownych przypadkach). Pamiętaj, aby użyć funkcji DataStoreCredentialRefreshListener i ustawić ją na potrzeby danych logowania za pomocą metody Credential.Builder.addRefreshListener(CredentialRefreshListener).
Przepływ kodu autoryzacji Servlet
Ta biblioteka udostępnia klasy pomocnicze serwletu, które znacznie upraszczają przepływ kodu autoryzacji w podstawowych przypadkach użycia. Wystarczy, że podasz konkretne podklasy elementów AbstractAuthorizationCodeServlet i AbstractAuthorizationCodeCallbackServlet (z google-oauth-client-servlet) oraz dodasz je do pliku web.xml. Pamiętaj, że nadal musisz się zalogować do 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 przepływ kodu autoryzacji serwletu z wyjątkiem interfejsu API Java użytkownika Google App Engine. Aby można było włączyć interfejs User Java API, użytkownik musi być zalogowany. Informacje o przekierowywaniu użytkowników na stronę logowania, jeśli nie są oni jeszcze zalogowani, znajdziesz w sekcji Zabezpieczenia i uwierzytelnianie (w pliku web.xml).
Główna różnica względem przypadku serwletu polega na tym, że udostępniasz konkretne podklasy AbstractAppEngineAuthorizationCodeServlet i AbstractAppEngineAuthorizationCodeCallbackServlet (z google-oauth-client-appengine). Rozszerzają one abstrakcyjne klasy serwletu i implementują metodę getUserId
za pomocą interfejsu User Java API. AppEngineDataStoreFactory (z biblioteki klienta HTTP Google dla języka Java to dobry sposób na przechowywanie 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 wiersza 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); ... }
Proces kliencki w przeglądarce
Oto typowe kroki procedury klienta działającej w przeglądarce wymienione w specyfikacji przyznania niejawnych:
- Za pomocą metody BrowserClientRequestUrl przekierowuje przeglądarkę użytkownika na stronę autoryzacji, na której może on przyznać aplikacji dostęp do swoich chronionych danych.
- Za pomocą aplikacji JavaScript przetwórz token dostępu znajdujący się we fragmencie adresu URL w identyfikatorze URI przekierowania zarejestrowanym na serwerze autoryzacji.
Przykładowe zastosowanie 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ą 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 zazwyczaj odpowiada kodem stanu HTTP 401 Unauthorized
, jak w tym przykładzie:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
Wydaje się jednak, że specyfikacja ma dużą elastyczność. Szczegółowe informacje znajdziesz w dokumentacji dostawcy OAuth 2.0.
Możesz też sprawdzić parametr expires_in
w odpowiedzi tokena dostępu.
Określa czas życia przyznanego tokena dostępu (w sekundach), co zwykle wynosi godzinę. Jednak token dostępu może nie wygasnąć po upływie tego czasu, a serwer może nadal zezwalać na dostęp. Dlatego zwykle zalecamy poczekać na kod stanu 401 Unauthorized
zamiast zakładać, że token wygasł po upływie określonego czasu. Możesz też spróbować odświeżyć token dostępu na krótko przed jego wygaśnięciem. Jeśli serwer tokenów jest niedostępny, używaj go, aż otrzymasz 401
. Jest to strategia używana domyślnie w sekcji Dane logowania.
Inna możliwość to pobieranie nowego tokena dostępu przed każdym żądaniem, ale wymaga to za każdym razem dodatkowego żądania HTTP do serwera tokenów, więc jest to prawdopodobnie słaby wybór, jeśli chodzi o szybkość i wykorzystanie sieci. Najlepiej przechowywać token dostępu w bezpiecznej, trwałej pamięci, aby zminimalizować liczbę żądań nowych tokenów dostępu przez aplikację. (jednak w przypadku zainstalowanych aplikacji bezpieczne przechowywanie stanowi problem).
Pamiętaj, że token dostępu może stać się nieważny z innych powodów niż jego wygaśnięcie, np. gdy użytkownik jawnie go unieważnił. Dlatego dopilnuj, aby kod obsługi błędów był solidny. Po wykryciu, że token nie jest już ważny (np. wygasł lub został unieważniony), musisz usunąć ten token dostępu z pamięci. Na przykład na urządzeniu z Androidem musisz wywołać metodę AccountManager.invalidateAuthToken.