Ringkasan
Tujuan: Dokumen ini menjelaskan fungsi OAuth 2.0 generik yang ditawarkan oleh Library Klien OAuth Google untuk Java. Anda dapat menggunakan fungsi ini untuk autentikasi dan otorisasi untuk layanan Internet apa pun.
Untuk petunjuk tentang cara menggunakan GoogleCredential
untuk melakukan otorisasi OAuth 2.0 dengan
layanan Google, lihat
Menggunakan OAuth 2.0 dengan Library Klien Google API untuk Java.
Ringkasan: OAuth 2.0 adalah spesifikasi standar untuk mengizinkan pengguna akhir memberikan otorisasi dengan aman kepada aplikasi klien untuk mengakses resource sisi server yang dilindungi. Selain itu, spesifikasi token pembawa OAuth 2.0 menjelaskan cara mengakses resource yang dilindungi tersebut menggunakan token akses yang diberikan selama proses otorisasi pengguna akhir.
Untuk detailnya, lihat dokumentasi Javadoc untuk paket-paket berikut:
- com.google.api.client.auth.oauth2 (dari google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (dari google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (from google-oauth-client-appengine)
Pendaftaran klien
Sebelum menggunakan Library Klien OAuth Google untuk Java, Anda mungkin perlu mendaftarkan aplikasi ke server otorisasi untuk menerima client ID dan secret klien. (Untuk mengetahui informasi umum tentang proses ini, lihat Spesifikasi Pendaftaran Klien.)
Kredensial dan penyimpanan kredensial
Kredensial
adalah class helper OAuth 2.0 yang aman untuk thread untuk mengakses resource yang dilindungi menggunakan
token akses. Saat menggunakan token refresh, Credential
juga akan memperbarui token akses
saat masa berlaku token akses berakhir menggunakan token refresh. Misalnya, jika sudah memiliki token akses, Anda dapat membuat permintaan dengan cara berikut:
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(); }
Sebagian besar aplikasi perlu mempertahankan token akses dan token refresh kredensial untuk menghindari pengalihan di masa mendatang ke halaman otorisasi di browser. Implementasi CredentialStore di library ini tidak digunakan lagi dan akan dihapus dalam rilis mendatang. Alternatifnya adalah menggunakan antarmuka DataStoreFactory dan DataStore dengan StoredCredential, yang disediakan oleh Library Klien HTTP Google untuk Java.
Anda dapat menggunakan salah satu implementasi berikut yang disediakan oleh library:
- JdoDataStoreFactory mempertahankan kredensial menggunakan JDO.
- AppEngineDataStoreFactory mempertahankan kredensial menggunakan Google App Engine Data Store API.
- MemoryDataStoreFactory "mempertahankan" kredensial dalam memori, yang hanya berguna sebagai penyimpanan jangka pendek selama proses berlangsung.
- FileDataStoreFactory mempertahankan kredensial dalam file.
Pengguna Google App Engine:
AppEngineCredentialStore tidak digunakan lagi dan akan dihapus.
Sebaiknya gunakan AppEngineDataStoreFactory dengan StoredCredential. Jika memiliki kredensial yang disimpan dengan cara lama, Anda dapat menggunakan metode helper tambahan migrateTo(AppEngineDataStoreFactory) atau migrateTo(DataStore) untuk melakukan migrasi.
Gunakan DataStoreCredentialRefreshListener dan tetapkan untuk kredensial menggunakan GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).
Alur kode otorisasi
Gunakan alur kode otorisasi untuk memungkinkan pengguna akhir memberikan akses ke aplikasi Anda ke data mereka yang dilindungi. Protokol untuk alur ini ditetapkan dalam spesifikasi Pemberian Kode Otorisasi.
Alur ini diimplementasikan menggunakan AuthorizationCodeFlow. Langkah-langkahnya adalah:
- Pengguna akhir login ke aplikasi Anda. Anda perlu mengaitkan pengguna tersebut dengan ID pengguna yang unik untuk aplikasi Anda.
- Panggil AuthorizationCodeFlow.loadCredential(String), berdasarkan ID pengguna, untuk memeriksa apakah kredensial pengguna sudah diketahui. Jika ya, Anda sudah selesai.
- Jika tidak, panggil AuthorizationCodeFlow.newAuthorizationUrl() dan arahkan browser pengguna akhir ke halaman otorisasi tempat mereka dapat memberi aplikasi Anda akses ke data mereka yang dilindungi.
- Browser web kemudian mengalihkan ke URL alihan dengan parameter kueri "code" yang kemudian dapat digunakan untuk meminta token akses menggunakan AuthorizationCodeFlow.newTokenRequest(String).
- Gunakan AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) untuk menyimpan dan mendapatkan kredensial guna mengakses resource yang dilindungi.
Atau, jika tidak menggunakan AuthorizationCodeFlow, Anda dapat menggunakan class tingkat rendah:
- Gunakan DataStore.get(String) untuk memuat kredensial dari penyimpanan, berdasarkan ID pengguna.
- Gunakan AuthorizationCodeRequestUrl untuk mengarahkan browser ke halaman otorisasi.
- Gunakan AuthorizationCodeResponseUrl untuk memproses respons otorisasi dan mengurai kode otorisasi.
- Gunakan AuthorizationCodeTokenRequest untuk meminta token akses dan mungkin token refresh.
- Buat Kredensial baru dan simpan menggunakan DataStore.set(String, V).
- Akses resource yang dilindungi menggunakan Kredensial. Token akses yang sudah tidak berlaku akan otomatis diperbarui menggunakan token refresh, jika berlaku. Pastikan untuk menggunakan DataStoreCredentialRefreshListener dan menetapkannya untuk kredensial menggunakan Credential.Builder.addRefreshListener(CredentialRefreshListener).
Alur kode otorisasi Servlet
Library ini menyediakan class helper servlet untuk menyederhanakan alur kode otorisasi secara signifikan untuk kasus penggunaan dasar. Anda hanya perlu menyediakan subclass konkret dari AbstractAuthorizationCodeServlet dan AbstractAuthorizationCodeCallbackServlet (dari google-oauth-client-servlet) dan menambahkannya ke file web.xml. Perhatikan bahwa Anda masih perlu menangani login pengguna untuk aplikasi web dan mengekstrak ID pengguna.
Contoh kode:
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 } }
Alur kode otorisasi Google App Engine
Alur kode otorisasi di App Engine hampir identik dengan alur kode otorisasi servlet, kecuali bahwa kita dapat memanfaatkan Users Java API Google App Engine. Pengguna harus login agar Users Java API diaktifkan. Untuk mengetahui informasi tentang cara mengalihkan pengguna ke halaman login jika belum login, lihat Keamanan dan Autentikasi (di web.xml).
Perbedaan utama dari kasus servlet adalah Anda memberikan subclass konkret dari AbstractAppEngineAuthorizationCodeServlet dan AbstractAppEngineAuthorizationCodeCallbackServlet (dari google-oauth-client-appengine). Class ini memperluas class servlet abstrak dan mengimplementasikan metode getUserId
untuk Anda menggunakan Users Java API. AppEngineDataStoreFactory (dari Library Klien HTTP Google untuk Java adalah opsi yang baik untuk mempertahankan kredensial menggunakan Google App Engine Data Store API.
Contoh kode:
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(); } }
Alur kode otorisasi command line
Contoh kode sederhana yang diambil dari 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); ... }
Alur klien berbasis browser
Berikut adalah langkah-langkah umum alur klien berbasis browser yang ditentukan dalam spesifikasi Pemberian Implisit:
- Dengan menggunakan BrowserClientRequestUrl, alihkan browser pengguna akhir ke halaman otorisasi tempat pengguna akhir dapat memberikan akses ke data mereka yang dilindungi ke aplikasi Anda.
- Gunakan aplikasi JavaScript untuk memproses token akses yang ditemukan di fragmen URL di URI alihan yang terdaftar dengan server otorisasi.
Contoh penggunaan untuk aplikasi web:
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); }
Mendeteksi token akses yang sudah tidak berlaku
Menurut spesifikasi pembawa OAuth 2.0,
saat server dipanggil untuk mengakses resource yang dilindungi dengan token akses
yang sudah tidak berlaku, server biasanya merespons dengan kode status 401 Unauthorized
HTTP
seperti berikut:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
Namun, tampaknya ada banyak fleksibilitas dalam spesifikasi. Untuk mengetahui detailnya, lihat dokumentasi penyedia OAuth 2.0.
Pendekatan alternatifnya adalah memeriksa parameter expires_in
di
respons token akses.
Ini menentukan masa berlaku token akses yang diberikan dalam detik, yang
biasanya satu jam. Namun, token akses mungkin tidak benar-benar berakhir masa berlakunya pada akhir
periode tersebut, dan server mungkin terus mengizinkan akses. Itulah sebabnya kami
biasanya merekomendasikan untuk menunggu kode status 401 Unauthorized
, bukan
menganggap token telah habis masa berlakunya berdasarkan waktu yang berlalu. Atau, Anda dapat
mencoba memuat ulang token akses sesaat sebelum masa berlakunya berakhir, dan jika server token
tidak tersedia, terus gunakan token akses hingga Anda menerima 401
. Ini
adalah strategi yang digunakan secara default di
Kredensial.
Opsi lainnya adalah mengambil token akses baru sebelum setiap permintaan, tetapi hal itu memerlukan permintaan HTTP tambahan ke server token setiap saat, sehingga kemungkinan merupakan pilihan yang buruk dalam hal kecepatan dan penggunaan jaringan. Idealnya, simpan token akses dalam penyimpanan persisten yang aman untuk meminimalkan permintaan aplikasi untuk token akses baru. (Tetapi untuk aplikasi yang terpasang, penyimpanan yang aman adalah masalah yang sulit.)
Perhatikan bahwa token akses dapat menjadi tidak valid karena alasan selain masa berlaku, misalnya jika pengguna telah mencabut token secara eksplisit, jadi pastikan kode penanganan error Anda andal. Setelah mendeteksi bahwa token tidak lagi valid, misalnya jika masa berlakunya telah berakhir atau dicabut, Anda harus menghapus token akses dari penyimpanan. Di Android, misalnya, Anda harus memanggil AccountManager.invalidateAuthToken.