ภาพรวม
วัตถุประสงค์: เอกสารนี้อธิบายฟังก์ชันทั่วไปของ OAuth 2.0 ที่ไลบรารีของไคลเอ็นต์ Google OAuth สำหรับ Java มีให้ คุณสามารถใช้ฟังก์ชันเหล่านี้เพื่อตรวจสอบสิทธิ์และการให้สิทธิ์สำหรับบริการอินเทอร์เน็ตได้
ดูวิธีการใช้ GoogleCredential
เพื่อทำการให้สิทธิ์ OAuth 2.0 กับบริการของ Google ได้ที่หัวข้อการใช้ OAuth 2.0 กับไลบรารีของไคลเอ็นต์ Google API สำหรับ Java
สรุป: OAuth 2.0 เป็นข้อกำหนดมาตรฐานที่อนุญาตให้ผู้ใช้ปลายทางให้สิทธิ์แอปพลิเคชันไคลเอ็นต์เข้าถึงทรัพยากรฝั่งเซิร์ฟเวอร์ที่ได้รับการปกป้องได้อย่างปลอดภัย นอกจากนี้ ข้อกําหนดของโทเค็นของผู้ถือ OAuth 2.0 ยังอธิบายวิธีเข้าถึงทรัพยากรที่ได้รับการปกป้องเหล่านั้นโดยใช้โทเค็นการเข้าถึงที่ได้รับระหว่างกระบวนการให้สิทธิ์ผู้ใช้ปลายทาง
โปรดดูรายละเอียดในเอกสารประกอบ Javadoc สำหรับแพ็กเกจต่อไปนี้
- com.google.api.client.auth.oauth2 (จาก google-oauth-client)
- com.google.api.client.extensions.servlet.auth.oauth2 (จาก google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (from google-oauth-client-appengine)
การลงทะเบียนลูกค้า
ก่อนที่จะใช้ไลบรารีของไคลเอ็นต์ Google OAuth สำหรับ Java คุณอาจต้องลงทะเบียนแอปพลิเคชันกับเซิร์ฟเวอร์การให้สิทธิ์เพื่อรับรหัสไคลเอ็นต์และรหัสลับไคลเอ็นต์ (สำหรับข้อมูลทั่วไปเกี่ยวกับกระบวนการนี้ โปรดดูข้อกำหนดการลงทะเบียนไคลเอ็นต์)
ข้อมูลเข้าสู่ระบบและที่เก็บข้อมูลเข้าสู่ระบบ
ข้อมูลเข้าสู่ระบบ
เป็นคลาสตัวช่วย OAuth 2.0 ที่ปลอดภัยระดับเทรดสำหรับการเข้าถึงทรัพยากรที่มีการป้องกันโดยใช้โทเค็นเพื่อการเข้าถึง เมื่อใช้โทเค็นการรีเฟรช Credential
จะรีเฟรชโทเค็นเพื่อการเข้าถึงเมื่อโทเค็นเพื่อการเข้าถึงหมดอายุโดยใช้โทเค็นการรีเฟรช ตัวอย่างเช่น หากคุณมีโทเค็นการเข้าถึงอยู่แล้ว คุณจะส่งคำขอได้ดังนี้
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(); }
แอปพลิเคชันส่วนใหญ่ต้องเก็บ Access Token และ Refresh Token ของข้อมูลเข้าสู่ระบบไว้เพื่อหลีกเลี่ยงการเปลี่ยนเส้นทางไปยังหน้าการให้สิทธิ์ในเบราว์เซอร์ในอนาคต การใช้งาน CredentialStore ในไลบรารีนี้เลิกใช้งานแล้วและจะถูกนําออกในรุ่นต่อๆ ไป อีกทางเลือกหนึ่งคือการใช้อินเทอร์เฟซ DataStoreFactory และ DataStore กับ StoredCredential ซึ่งให้บริการโดยไลบรารีของไคลเอ็นต์ HTTP สำหรับ Java
คุณใช้การติดตั้งใช้งานอย่างใดอย่างหนึ่งต่อไปนี้จากไลบรารีได้
- JdoDataStoreFactory จะเก็บข้อมูลเข้าสู่ระบบโดยใช้ JDO
- AppEngineDataStoreFactory เก็บข้อมูลรับรองโดยใช้ API พื้นที่เก็บข้อมูลของ Google App Engine
- MemoryDataStoreFactory "เก็บ" ข้อมูลเข้าสู่ระบบในหน่วยความจำซึ่งมีประโยชน์เฉพาะในฐานะพื้นที่เก็บข้อมูลระยะสั้นตลอดอายุของกระบวนการ
- FileDataStoreFactory จะเก็บข้อมูลเข้าสู่ระบบในไฟล์
ผู้ใช้ Google App Engine
AppEngineCredentialStore เลิกใช้งานแล้วและจะถูกนำออก
เราขอแนะนำให้คุณใช้ AppEngineDataStoreFactory กับ StoredCredential หากมีข้อมูลเข้าสู่ระบบที่จัดเก็บด้วยวิธีเก่า คุณสามารถใช้เมธอดตัวช่วยที่เพิ่มเข้ามา migrateTo(AppEngineDataStoreFactory) หรือ migrateTo(DataStore) เพื่อย้ายข้อมูล
ใช้ DataStoreCredentialRefreshListener และตั้งค่าสําหรับข้อมูลเข้าสู่ระบบโดยใช้ GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener)
ขั้นตอนรหัสการให้สิทธิ์
ใช้ขั้นตอนรหัสการให้สิทธิ์เพื่ออนุญาตให้ผู้ใช้ปลายทางให้สิทธิ์แอปพลิเคชันเข้าถึงข้อมูลที่ตนปกป้อง โปรโตคอลสำหรับขั้นตอนนี้มีระบุไว้ในข้อกำหนดเฉพาะเกี่ยวกับการให้สิทธิ์รหัสการให้สิทธิ์
ขั้นตอนนี้ติดตั้งใช้งานโดยใช้ AuthorizationCodeFlow ขั้นตอนมีดังต่อไปนี้
- ผู้ใช้ปลายทางเข้าสู่ระบบแอปพลิเคชัน คุณต้องเชื่อมโยงผู้ใช้ดังกล่าวกับรหัสผู้ใช้ที่ไม่ซ้ำกันสำหรับแอปพลิเคชัน
- โทรไปที่ AuthorizationCodeFlow.loadCredential(String) ตามรหัสผู้ใช้เพื่อตรวจสอบว่าระบบทราบข้อมูลเข้าสู่ระบบของผู้ใช้แล้วหรือยัง หากใช้ได้ แสดงว่าเชื่อมต่อเสร็จแล้ว
- หากไม่ได้ใช้ ให้เรียกใช้ AuthorizationCodeFlow.newAuthorizationUrl() และนำเบราว์เซอร์ของผู้ใช้ปลายทางไปยังหน้าการให้สิทธิ์ ซึ่งผู้ใช้สามารถให้สิทธิ์แอปพลิเคชันเข้าถึงข้อมูลที่คุ้มครองได้
- จากนั้นเว็บเบราว์เซอร์จะเปลี่ยนเส้นทางไปยัง URL เปลี่ยนเส้นทางพร้อมพารามิเตอร์การค้นหา "code" ซึ่งสามารถใช้เพื่อขอโทเค็นการเข้าถึงได้โดยใช้ AuthorizationCodeFlow.newTokenRequest(String)
- ใช้ AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) เพื่อจัดเก็บและรับข้อมูลเข้าสู่ระบบสำหรับการเข้าถึงทรัพยากรที่ได้รับการปกป้อง
หรือหากไม่ได้ใช้ AuthorizationCodeFlow คุณจะใช้คลาสระดับล่างต่อไปนี้ได้
- ใช้ DataStore.get(String) เพื่อโหลดข้อมูลเข้าสู่ระบบจากพื้นที่เก็บข้อมูลตามรหัสผู้ใช้
- ใช้ AuthorizationCodeRequestUrl เพื่อนำทางเบราว์เซอร์ไปยังหน้าการให้สิทธิ์
- ใช้ AuthorizationCodeResponseUrl เพื่อประมวลผลคำตอบการให้สิทธิ์และแยกวิเคราะห์รหัสการให้สิทธิ์
- ใช้ AuthorizationCodeTokenRequest เพื่อขอโทเค็นการเข้าถึงและอาจขอโทเค็นการรีเฟรช
- สร้างข้อมูลเข้าสู่ระบบใหม่และจัดเก็บโดยใช้ DataStore.set(String, V)
- เข้าถึงทรัพยากรที่ได้รับการปกป้องโดยใช้ข้อมูลเข้าสู่ระบบ ระบบจะรีเฟรชโทเค็นการเข้าถึงที่หมดอายุโดยอัตโนมัติโดยใช้โทเค็นการรีเฟรช หากมี อย่าลืมใช้ DataStoreCredentialRefreshListener และตั้งค่าสําหรับข้อมูลเข้าสู่ระบบโดยใช้ Credential.Builder.addRefreshListener(CredentialRefreshListener)
ขั้นตอนรหัสการให้สิทธิ์ของ Servlet
ไลบรารีนี้มีคลาสตัวช่วยของเซิร์ฟเล็ตเพื่อลดความซับซ้อนของขั้นตอนการสร้างรหัสการให้สิทธิ์สำหรับกรณีการใช้งานพื้นฐาน คุณเพียงระบุคลาสย่อยที่เฉพาะเจาะจงของ AbstractAuthorizationCodeServlet และ AbstractAuthorizationCodeCallbackServlet (จาก google-oauth-client-servlet) แล้วเพิ่มลงในไฟล์ web.xml โปรดทราบว่าคุณยังคงต้องดูแลการเข้าสู่ระบบของผู้ใช้สำหรับเว็บแอปพลิเคชันและดึงรหัสผู้ใช้ออกมา
โค้ดตัวอย่าง
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 } }
ขั้นตอนรหัสการให้สิทธิ์ของ Google App Engine
ขั้นตอนการใช้รหัสการให้สิทธิ์ใน App Engine เกือบจะเหมือนกับขั้นตอนการใช้รหัสการให้สิทธิ์ของเซิร์วิเลต ยกเว้นว่าเราสามารถใช้ Users Java API ของ Google App Engine ได้ ผู้ใช้ต้องลงชื่อเข้าสู่ระบบเพื่อให้เปิดใช้ Users Java API ได้ ดูข้อมูลเกี่ยวกับการเปลี่ยนเส้นทางผู้ใช้ไปยังหน้าเข้าสู่ระบบหากผู้ใช้ยังไม่ได้ลงชื่อเข้าสู่ระบบได้ที่ความปลอดภัยและการตรวจสอบสิทธิ์ (ใน web.xml)
ความแตกต่างหลักๆ จากเคสของเซิร์ฟเล็ตคือคุณระบุคลาสย่อยที่เป็นรูปธรรมของ AbstractAppEngineAuthorizationCodeServlet และ AbstractAppEngineAuthorizationCodeCallbackServlet (จาก google-oauth-client-appengine) ไลบรารีดังกล่าวจะขยายคลาสเซิร์ฟเล็ตสมมติและใช้เมธอด getUserId
ให้คุณโดยใช้ Users Java API AppEngineDataStoreFactory (จากไลบรารีของไคลเอ็นต์ HTTP ของ Google สำหรับ Java เป็นตัวเลือกที่ดีในการยืนยันข้อมูลเข้าสู่ระบบโดยใช้ Google App Engine Data Store API
โค้ดตัวอย่าง
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(); } }
ขั้นตอนรหัสการให้สิทธิ์ของบรรทัดคำสั่ง
โค้ดตัวอย่างที่เรียบง่ายซึ่งนำมาจาก 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); ... }
ขั้นตอนไคลเอ็นต์บนเบราว์เซอร์
ขั้นตอนทั่วไปของขั้นตอนการให้สิทธิ์แบบไม่แสดงข้อความแจ้งของไคลเอ็นต์ที่ใช้เบราว์เซอร์ที่ระบุไว้ในข้อกําหนดเฉพาะของการให้สิทธิ์โดยนัยมีดังนี้
- ใช้ BrowserClientRequestUrl เพื่อเปลี่ยนเส้นทางเบราว์เซอร์ของผู้ใช้ปลายทางไปยังหน้าการให้สิทธิ์ ซึ่งผู้ใช้ปลายทางสามารถให้สิทธิ์แอปพลิเคชันเข้าถึงข้อมูลที่คุ้มครองได้
- ใช้แอปพลิเคชัน JavaScript เพื่อประมวลผลโทเค็นการเข้าถึงที่พบใน URL ของ URI การเปลี่ยนเส้นทางที่ลงทะเบียนกับเซิร์ฟเวอร์การให้สิทธิ์
ตัวอย่างการใช้งานสำหรับเว็บแอปพลิเคชัน
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); }
กำลังตรวจหาโทเค็นเพื่อการเข้าถึงที่หมดอายุ
ตามข้อกำหนดของ OAuth 2.0 Bearer เมื่อมีการเรียกใช้เซิร์ฟเวอร์เพื่อเข้าถึงทรัพยากรที่ได้รับการปกป้องด้วยโทเค็นการเข้าถึงที่หมดอายุแล้ว โดยปกติแล้วเซิร์ฟเวอร์จะตอบกลับด้วยรหัสสถานะ HTTP 401 Unauthorized
ดังต่อไปนี้
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
อย่างไรก็ตาม ข้อกำหนดมีความยืดหยุ่นมาก ดูรายละเอียดได้ในเอกสารประกอบของผู้ให้บริการ OAuth 2.0
อีกวิธีหนึ่งคือการตรวจสอบพารามิเตอร์ expires_in
ในการตอบกลับของโทเค็นเพื่อการเข้าถึง
ข้อมูลนี้ระบุอายุการใช้งานของโทเค็นการเข้าถึงที่ได้รับเป็นวินาที ซึ่งโดยปกติแล้วคือ 1 ชั่วโมง อย่างไรก็ตาม โทเค็นการเข้าถึงอาจไม่หมดอายุเมื่อสิ้นสุดระยะเวลาดังกล่าว และเซิร์ฟเวอร์อาจอนุญาตให้เข้าถึงต่อไป ด้วยเหตุนี้ เราจึงมักแนะนำให้รอรหัสสถานะ 401 Unauthorized
แทนที่จะถือว่าโทเค็นหมดอายุตามเวลาที่ผ่านไป หรือจะลองรีเฟรชโทเค็นการเข้าถึงก่อนหมดอายุไม่นานก็ได้ และหากเซิร์ฟเวอร์โทเค็นไม่พร้อมใช้งาน ให้ใช้โทเค็นการเข้าถึงต่อไปจนกว่าจะได้รับ 401
นี่คือกลยุทธ์ที่ใช้ในข้อมูลเข้าสู่ระบบโดยค่าเริ่มต้น
อีกตัวเลือกหนึ่งคือการขอโทเค็นเพื่อการเข้าถึงใหม่ก่อนคำขอทุกครั้ง แต่ต้องส่งคำขอ HTTP เพิ่มเติมไปยังเซิร์ฟเวอร์โทเค็นทุกครั้ง ดังนั้นจึงน่าจะเป็นทางเลือกที่ค่อนข้างแย่ในแง่ของความเร็วและการใช้งานเครือข่าย วิธีที่ดีที่สุดคือจัดเก็บโทเค็นการเข้าถึงในที่จัดเก็บข้อมูลถาวรที่ปลอดภัยเพื่อลดคำขอโทเค็นการเข้าถึงใหม่ของแอปพลิเคชัน (แต่สำหรับแอปพลิเคชันที่ติดตั้ง การจัดเก็บที่ปลอดภัยเป็นปัญหาที่ยาก)
โปรดทราบว่าโทเค็นการเข้าถึงอาจใช้งานไม่ได้ด้วยเหตุผลอื่นนอกเหนือจากการหมดอายุ เช่น หากผู้ใช้เพิกถอนโทเค็นอย่างชัดเจน ดังนั้นโปรดตรวจสอบว่าโค้ดการจัดการข้อผิดพลาดมีประสิทธิภาพ เมื่อตรวจพบว่าโทเค็นใช้งานไม่ได้แล้ว เช่น หมดอายุหรือถูกเพิกถอน คุณต้องนำโทเค็นการเข้าถึงออกจากพื้นที่เก็บข้อมูล เช่น ใน Android คุณต้องเรียกใช้ AccountManager.invalidateAuthToken