ภาพรวม
วัตถุประสงค์: เอกสารนี้จะอธิบายฟังก์ชัน 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 (from google-oauth-client-servlet)
- com.google.api.client.extensions.appengine.auth.oauth2 (from google-oauth-client-appengine)
การลงทะเบียนลูกค้า
ก่อนที่จะใช้ไลบรารีของไคลเอ็นต์ Google OAuth สำหรับ Java คุณอาจต้อง ลงทะเบียนแอปพลิเคชันกับเซิร์ฟเวอร์การให้สิทธิ์เพื่อรับรหัสไคลเอ็นต์และ รหัสลับไคลเอ็นต์ (สำหรับข้อมูลทั่วไปเกี่ยวกับกระบวนการนี้ โปรดดูที่ ไคลเอ็นต์ ข้อกำหนดในการลงทะเบียน)
ที่เก็บข้อมูลเข้าสู่ระบบและข้อมูลเข้าสู่ระบบ
ข้อมูลเข้าสู่ระบบ
เป็นคลาสตัวช่วย OAuth 2.0 ที่ปลอดภัยระดับ Thread สำหรับการเข้าถึงทรัพยากรที่มีการป้องกันโดยใช้
โทเค็นเพื่อการเข้าถึง เมื่อใช้โทเค็นการรีเฟรช 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(); }
แอปพลิเคชันส่วนใหญ่ต้องยืนยันโทเค็นเพื่อการเข้าถึงของข้อมูลเข้าสู่ระบบและ โทเค็นการรีเฟรชเพื่อที่จะหลีกเลี่ยงการเปลี่ยนเส้นทางไปยังการให้สิทธิ์ในอนาคต ในเบราว์เซอร์ CredentialStore การใช้งานในไลบรารีนี้เลิกใช้งานแล้วและจะถูกนำออกในอนาคต รุ่น อีกวิธีหนึ่งคือการใช้ DataStoreFactory และ DataStore อินเทอร์เฟซที่มี StoredCredential ซึ่งมาจาก ไลบรารีของไคลเอ็นต์ HTTP ของ Google สำหรับ Java
คุณสามารถใช้การติดตั้งใช้งานอย่างใดอย่างหนึ่งต่อไปนี้ที่ไลบรารีมีให้
- JdoDataStoreFactory เก็บเอกสารรับรองโดยใช้ JDO
- AppEngineDataStoreFactory เก็บเอกสารรับรองโดยใช้ Google App Engine Data Store API
- MemoryDataStoreFactory "ถาวร" ข้อมูลเข้าสู่ระบบในหน่วยความจำ ซึ่งจะเป็นประโยชน์เพียงระยะสั้นเท่านั้น ตลอดอายุของกระบวนการ
- FileDataStoreFactory เก็บข้อมูลเข้าสู่ระบบในไฟล์
ผู้ใช้ Google App Engine:
AppEngineCredentialStore เลิกใช้งานแล้วและจะถูกนำออก
เราขอแนะนำให้คุณใช้ AppEngineDataStoreFactory ที่มี StoredCredential หากคุณมีข้อมูลเข้าสู่ระบบที่จัดเก็บไว้ด้วยวิธีแบบเดิม คุณสามารถใช้เมธอดผู้ช่วยที่เพิ่มเข้ามาได้ migrateTo(AppEngineDataStoreFactory) หรือ migrateTo(DataStore) ย้ายข้อมูลได้
ใช้ DataStoreCredentialRefreshListener และตั้งค่าให้กับ การรับรองโดยใช้ GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).
ขั้นตอนสำหรับรหัสการให้สิทธิ์
ใช้ขั้นตอนรหัสการให้สิทธิ์เพื่ออนุญาตให้ผู้ใช้ปลายทางให้สิทธิ์แอปพลิเคชันของคุณ เข้าถึงข้อมูลที่มีการป้องกัน โปรโตคอลสำหรับขั้นตอนนี้จะได้รับการระบุไว้ในฟิลด์ ข้อกำหนดเกี่ยวกับการให้สิทธิ์รหัสการให้สิทธิ์
ขั้นตอนนี้ดำเนินการโดยใช้ AuthorizationCodeFlow ขั้นตอนมีดังต่อไปนี้
- ผู้ใช้ปลายทางเข้าสู่ระบบแอปพลิเคชันของคุณ คุณต้องเชื่อมโยงผู้ใช้รายนั้นกับ รหัสผู้ใช้สำหรับแอปพลิเคชันของคุณโดยเฉพาะ
- โทร AuthorizationCodeFlow.loadCredential(String), ตาม User-ID เพื่อตรวจสอบว่าทราบข้อมูลเข้าสู่ระบบของผู้ใช้แล้วหรือยัง หากใช้ได้ แสดงว่าเชื่อมต่อเสร็จแล้ว
- หากไม่เห็น โปรดเรียกใช้ AuthorizationCodeFlow.newAuthorizationUrl() และให้เบราว์เซอร์ของผู้ใช้ปลายทางไปยังหน้าการให้สิทธิ์ ให้แอปพลิเคชันของคุณเข้าถึงข้อมูลที่มีการป้องกัน
- จากนั้นเว็บเบราว์เซอร์จะเปลี่ยนเส้นทางไปยัง URL เปลี่ยนเส้นทางด้วย "รหัส" คำถาม พารามิเตอร์ที่สามารถใช้เพื่อขอโทเค็นเพื่อการเข้าถึงได้โดยใช้ AuthorizationCodeFlow.newTokenRequest(String)
- ใช้ AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, สตริง) เพื่อจัดเก็บและรับข้อมูลรับรองสำหรับการเข้าถึงทรัพยากรที่มีการป้องกัน
ในกรณีที่คุณไม่ได้ใช้ AuthorizationCodeFlow คุณสามารถใช้คลาสระดับล่าง:
- ใช้ DataStore.get(String) เพื่อโหลดข้อมูลเข้าสู่ระบบจาก Store ตามรหัสผู้ใช้
- ใช้ AuthorizationCodeRequestUrl เพื่อกำหนดเส้นทางเบราว์เซอร์ไปยังหน้าการให้สิทธิ์
- ใช้ AuthorizationCodeResponseUrl เพื่อประมวลผลการตอบกลับการให้สิทธิ์และแยกวิเคราะห์รหัสการให้สิทธิ์
- ใช้ AuthorizationCodeTokenRequest เพื่อขอโทเค็นเพื่อการเข้าถึง และอาจรวมถึงโทเค็นการรีเฟรช
- สร้างข้อมูลเข้าสู่ระบบใหม่และจัดเก็บไว้โดยใช้ DataStore.set(String, V)
- เข้าถึงทรัพยากรที่มีการป้องกันโดยใช้ข้อมูลเข้าสู่ระบบ ระบบจะรีเฟรชโทเค็นเพื่อการเข้าถึงที่หมดอายุโดยอัตโนมัติโดยใช้โทเค็นการรีเฟรช หาก ที่เกี่ยวข้อง อย่าลืมใช้ DataStoreCredentialRefreshListener และตั้งค่าให้กับ การรับรองโดยใช้ Credential.Builder.addRefreshListener(CredentialRefreshListener).
ขั้นตอนรหัสการให้สิทธิ์ของ Servlet
ไลบรารีนี้มีชั้นเรียนตัวช่วยของเซิร์ฟเล็ตเพื่อลดความซับซ้อนของ ขั้นตอนรหัสการให้สิทธิ์สำหรับ Use Case พื้นฐาน คุณเพียงแค่ให้คลาสย่อยที่เป็นรูปธรรม จาก 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 แทบจะเหมือนกันทุกประการกับเซิร์ฟเล็ต ขั้นตอนรหัสการให้สิทธิ์ ยกเว้นว่าเราสามารถใช้ประโยชน์จาก JavaScript API ของผู้ใช้ ผู้ใช้ต้องเข้าสู่ระบบเพื่อเปิดใช้ 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
เมื่อมีการเรียกเซิร์ฟเวอร์ให้เข้าถึงทรัพยากรที่มีการป้องกันด้วยสิทธิ์เข้าถึงที่หมดอายุ
โทเค็น เซิร์ฟเวอร์มักจะตอบกลับด้วยรหัสสถานะ 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