สร้างเครื่องจัดการโค้ดเรียกกลับการให้สิทธิ์

เอกสารนี้อธิบายวิธีใช้งานตัวแฮนเดิล Callback สำหรับการให้สิทธิ์ OAuth 2.0 โดยใช้เซิร์ฟเล็ต Java ผ่านเว็บแอปพลิเคชันตัวอย่างที่จะแสดงงานของผู้ใช้โดยใช้ Google Tasks API แอปพลิเคชันตัวอย่างจะขอสิทธิ์ในการเข้าถึง Google Tasks ของผู้ใช้ก่อน จากนั้นจะแสดงงานของผู้ใช้ในรายการงานเริ่มต้น

ผู้ชม

เอกสารนี้ออกแบบมาเพื่อผู้ที่คุ้นเคยกับสถาปัตยกรรมเว็บแอปพลิเคชัน Java และ J2EE และขอแนะนําให้ทราบถึงขั้นตอนการให้สิทธิ์ OAuth 2.0

เนื้อหา

หากต้องการได้รับตัวอย่างหลายๆ ขั้นตอนที่สมบูรณ์ดังกล่าว คุณจะต้องทำดังนี้

ประกาศการแมปเซิร์ฟเล็ตในไฟล์ web.xml

เราจะใช้เซิร์ฟเล็ต 2 ตัวในแอปพลิเคชันของเรา

  • PrintTasksTitlesServlet (แมปกับ /): จุดแรกเข้าของแอปพลิเคชันที่จะจัดการการตรวจสอบสิทธิ์ของผู้ใช้และจะแสดงงานของผู้ใช้
  • OAuthCodeCallbackHandlerServlet (แมปกับ /oauth2callback): Callback ของ OAuth 2.0 ที่จัดการการตอบกลับจากอุปกรณ์ปลายทางการให้สิทธิ์ OAuth

ด้านล่างคือไฟล์ web.xml ซึ่งแมปเซิร์ฟเล็ต 2 รายการนี้กับ URL ในแอปพลิเคชันของเรา

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
   
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

 
<servlet>
   
<servlet-name>PrintTasksTitles</servlet-name>
   
<servlet-class>com.google.oauthsample.PrintTasksTitlesServlet</servlet-class>
 
</servlet>

 
<servlet-mapping>
   
<servlet-name>PrintTasksTitles</servlet-name>
   
<url-pattern>/</url-pattern>
 
</servlet-mapping>

 
<servlet>
   
<servlet-name>OAuthCodeCallbackHandlerServlet</servlet-name>
   
<servlet-class>com.google.oauthsample.OAuthCodeCallbackHandlerServlet</servlet-class>
 
</servlet>

 
<servlet-mapping>
   
<servlet-name>OAuthCodeCallbackHandlerServlet</servlet-name>
   
<url-pattern>/oauth2callback</url-pattern>
 
</servlet-mapping>

</web-app>
ไฟล์ /WEB-INF/web.xml

ตรวจสอบสิทธิ์ผู้ใช้ในระบบและขอสิทธิ์ในการเข้าถึงงาน

ผู้ใช้เข้าสู่แอปพลิเคชันผ่านทางราก "/" URL ที่แมปกับเซิร์ฟเล็ต PrintTaskListsTitlesServlet ในเซิร์ฟเล็ตดังกล่าว จะมีการดำเนินการต่อไปนี้

  • ตรวจสอบว่าผู้ใช้ผ่านการตรวจสอบสิทธิ์ในระบบหรือไม่
  • ถ้าผู้ใช้ไม่ผ่านการตรวจสอบสิทธิ์ ระบบจะเปลี่ยนเส้นทางไปยังหน้าการตรวจสอบสิทธิ์
  • หากผู้ใช้ตรวจสอบสิทธิ์แล้ว เราจะตรวจสอบว่ามีโทเค็นการรีเฟรชอยู่ในพื้นที่เก็บข้อมูลของเราแล้วหรือยัง ซึ่งจัดการโดย OAuthTokenDao ด้านล่าง หากไม่มีโทเค็นการรีเฟรชสำหรับผู้ใช้ แสดงว่าผู้ใช้ยังไม่ได้ให้สิทธิ์แอปพลิเคชันในการเข้าถึงงาน ในกรณีนี้ ระบบจะเปลี่ยนเส้นทางผู้ใช้ไปยังปลายทางการให้สิทธิ์ OAuth 2.0 ของ Google
โดยวิธีการมีดังนี้

package com.google.oauthsample;

import ...

/**
 * Simple sample Servlet which will display the tasks in the default task list of the user.
 */

@SuppressWarnings("serial")
public class PrintTasksTitlesServlet extends HttpServlet {

 
/**
   * The OAuth Token DAO implementation, used to persist the OAuth refresh token.
   * Consider injecting it instead of using a static initialization. Also we are
   * using a simple memory implementation as a mock. Change the implementation to
   * using your database system.
   */

 
public static OAuthTokenDao oauthTokenDao = new OAuthTokenDaoMemoryImpl();

 
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
   
// Getting the current user
   
// This is using App Engine's User Service but you should replace this to
   
// your own user/login implementation
   
UserService userService = UserServiceFactory.getUserService();
   
User user = userService.getCurrentUser();

   
// If the user is not logged-in it is redirected to the login service, then back to this page
   
if (user == null) {
      resp
.sendRedirect(userService.createLoginURL(getFullRequestUrl(req)));
     
return;
   
}

   
// Checking if we already have tokens for this user in store
   
AccessTokenResponse accessTokenResponse = oauthTokenDao.getKeys(user.getEmail());

   
// If we don't have tokens for this user
   
if (accessTokenResponse == null) {
     
OAuthProperties oauthProperties = new OAuthProperties();
     
// Redirect to the Google OAuth 2.0 authorization endpoint
      resp
.sendRedirect(new GoogleAuthorizationRequestUrl(oauthProperties.getClientId(),
         
OAuthCodeCallbackHandlerServlet.getOAuthCodeCallbackHandlerUrl(req), oauthProperties
             
.getScopesAsString()).build());
     
return;
   
}
 
}

 
/**
   * Construct the request's URL without the parameter part.
   *
   * @param req the HttpRequest object
   * @return The constructed request's URL
   */

 
public static String getFullRequestUrl(HttpServletRequest req) {
   
String scheme = req.getScheme() + "://";
   
String serverName = req.getServerName();
   
String serverPort = (req.getServerPort() == 80) ? "" : ":" + req.getServerPort();
   
String contextPath = req.getContextPath();
   
String servletPath = req.getServletPath();
   
String pathInfo = (req.getPathInfo() == null) ? "" : req.getPathInfo();
   
String queryString = (req.getQueryString() == null) ? "" : "?" + req.getQueryString();
   
return scheme + serverName + serverPort + contextPath + servletPath + pathInfo + queryString;
 
}
}
ไฟล์ PrintTasksTitlesServlet.java

หมายเหตุ: การใช้งานข้างต้นใช้ไลบรารี App Engine บางรายการ ซึ่งใช้ในการลดความซับซ้อน หากคุณพัฒนาสำหรับแพลตฟอร์มอื่น คุณสามารถนำอินเทอร์เฟซ UserService ที่จัดการการตรวจสอบสิทธิ์ผู้ใช้กลับมาใช้ใหม่ได้

แอปพลิเคชันจะใช้ DAO เพื่อคงและเข้าถึงโทเค็นการให้สิทธิ์ของผู้ใช้ ด้านล่างนี้คืออินเทอร์เฟซ - OAuthTokenDao - และการใช้งานการจำลอง (ในหน่วยความจำ) - OAuthTokenDaoMemoryImpl - ที่ใช้ในตัวอย่างนี้

package com.google.oauthsample;

import com.google.api.client.auth.oauth2.draft10.AccessTokenResponse;

/**
 * Allows easy storage and access of authorization tokens.
 */

public interface OAuthTokenDao {

 
/**
   * Stores the given AccessTokenResponse using the {@code username}, the OAuth
   * {@code clientID} and the tokens scopes as keys.
   *
   * @param tokens The AccessTokenResponse to store
   * @param userName The userName associated wit the token
   */

 
public void saveKeys(AccessTokenResponse tokens, String userName);

 
/**
   * Returns the AccessTokenResponse stored for the given username, clientId and
   * scopes. Returns {@code null} if there is no AccessTokenResponse for this
   * user and scopes.
   *
   * @param userName The username of which to get the stored AccessTokenResponse
   * @return The AccessTokenResponse of the given username
   */

 
public AccessTokenResponse getKeys(String userName);
}
ไฟล์ OAuthTokenDao.java
package com.google.oauthsample;

import com.google.api.client.auth.oauth2.draft10.AccessTokenResponse;
...

/**
 * Quick and Dirty memory implementation of {@link OAuthTokenDao} based on
 * HashMaps.
 */

public class OAuthTokenDaoMemoryImpl implements OAuthTokenDao {

 
/** Object where all the Tokens will be stored */
 
private static Map
ไฟล์ OAuthTokenDaoMemoryImpl.java

นอกจากนี้ ข้อมูลเข้าสู่ระบบ OAuth 2.0 สำหรับแอปพลิเคชันจะเก็บไว้ในไฟล์พร็อพเพอร์ตี้ หรือคุณอาจให้ค่าเหล่านี้เป็นค่าคงที่ในคลาส Java อย่างใดอย่างหนึ่งก็ได้ แต่นี่คือคลาส OAuthProperties และไฟล์ oauth.properties ที่ใช้ในตัวอย่าง

package com.google.oauthsample;

import ...

/**
 * Object representation of an OAuth properties file.
 */

public class OAuthProperties {

 
public static final String DEFAULT_OAUTH_PROPERTIES_FILE_NAME = "oauth.properties";

 
/** The OAuth 2.0 Client ID */
 
private String clientId;

 
/** The OAuth 2.0 Client Secret */
 
private String clientSecret;

 
/** The Google APIs scopes to access */
 
private String scopes;

 
/**
   * Instantiates a new OauthProperties object reading its values from the
   * {@code OAUTH_PROPERTIES_FILE_NAME} properties file.
   *
   * @throws IOException IF there is an issue reading the {@code propertiesFile}
   * @throws OauthPropertiesFormatException If the given {@code propertiesFile}
   *           is not of the right format (does not contains the keys {@code
   *           clientId}, {@code clientSecret} and {@code scopes})
   */

 
public OAuthProperties() throws IOException {
   
this(OAuthProperties.class.getResourceAsStream(DEFAULT_OAUTH_PROPERTIES_FILE_NAME));
 
}

 
/**
   * Instantiates a new OauthProperties object reading its values from the given
   * properties file.
   *
   * @param propertiesFile the InputStream to read an OAuth Properties file. The
   *          file should contain the keys {@code clientId}, {@code
   *          clientSecret} and {@code scopes}
   * @throws IOException IF there is an issue reading the {@code propertiesFile}
   * @throws OAuthPropertiesFormatException If the given {@code propertiesFile}
   *           is not of the right format (does not contains the keys {@code
   *           clientId}, {@code clientSecret} and {@code scopes})
   */

 
public OAuthProperties(InputStream propertiesFile) throws IOException {
   
Properties oauthProperties = new Properties();
    oauthProperties
.load(propertiesFile);
    clientId
= oauthProperties.getProperty("clientId");
    clientSecret
= oauthProperties.getProperty("clientSecret");
    scopes
= oauthProperties.getProperty("scopes");
   
if ((clientId == null) || (clientSecret == null) || (scopes == null)) {
     
throw new OAuthPropertiesFormatException();
   
}
 
}

 
/**
   * @return the clientId
   */

 
public String getClientId() {
   
return clientId;
 
}

 
/**
   * @return the clientSecret
   */

 
public String getClientSecret() {
   
return clientSecret;
 
}

 
/**
   * @return the scopes
   */

 
public String getScopesAsString() {
   
return scopes;
 
}

 
/**
   * Thrown when the OAuth properties file was not at the right format, i.e not
   * having the right properties names.
   */

 
@SuppressWarnings("serial")
 
public class OAuthPropertiesFormatException extends RuntimeException {
 
}
}
ไฟล์ OAuthProperties.java

ด้านล่างนี้คือไฟล์ oauth.properties ที่มีข้อมูลเข้าสู่ระบบ OAuth 2.0 ของแอปของคุณ คุณต้องเปลี่ยนค่าด้านล่างด้วยตนเอง

# Client ID and secret. They can be found in the APIs console.
clientId
=1234567890.apps.googleusercontent.com
clientSecret
=aBcDeFgHiJkLmNoPqRsTuVwXyZ
# API scopes. Space separated.
scopes
=https://www.googleapis.com/auth/tasks
ไฟล์ oauth.properties

รหัสไคลเอ็นต์ OAuth 2.0 และรหัสลับไคลเอ็นต์จะระบุแอปพลิเคชันของคุณและอนุญาตให้ Tasks API ใช้ตัวกรองและกฎโควต้าที่กำหนดไว้สำหรับแอปพลิเคชัน คุณสามารถดูรหัสไคลเอ็นต์และรหัสลับได้ในคอนโซล Google APIs เมื่ออยู่ในคอนโซลแล้ว คุณจะต้องดำเนินการต่อไปนี้

  • สร้างหรือเลือกโปรเจ็กต์
  • เปิดใช้ Tasks API โดยสลับสถานะ Tasks API เป็นเปิดในรายการบริการ
  • ในส่วนการเข้าถึง API ให้สร้างรหัสไคลเอ็นต์ OAuth 2.0 หากยังไม่ได้สร้าง
  • ตรวจสอบว่า URL ของตัวแฮนเดิล Callback ของโค้ด OAuth 2.0 ของโปรเจ็กต์ได้รับการลงทะเบียน/อนุญาตพิเศษใน URL การเปลี่ยนเส้นทาง ตัวอย่างเช่น ในโปรเจ็กต์ตัวอย่างนี้ คุณจะต้องลงทะเบียน https://www.example.com/oauth2callback หากเว็บแอปพลิเคชันของคุณให้บริการจากโดเมน https://www.example.com

เปลี่ยนเส้นทาง URI ในคอนโซล API
เปลี่ยนเส้นทาง URI ในคอนโซล API

ฟังรหัสการให้สิทธิ์จากอุปกรณ์ปลายทางการให้สิทธิ์ของ Google

ในกรณีที่ผู้ใช้ยังไม่ได้ให้สิทธิ์แอปพลิเคชันเข้าถึงงาน ระบบจึงเปลี่ยนเส้นทางไปยังปลายทางการให้สิทธิ์ OAuth 2.0 ของ Google ผู้ใช้จะแสดงกล่องโต้ตอบการให้สิทธิ์จาก Google เพื่อขอให้ผู้ใช้ให้สิทธิ์แก่แอปพลิเคชันในการเข้าถึงงานของตน

กล่องโต้ตอบการให้สิทธิ์ของ Google
กล่องโต้ตอบการให้สิทธิ์ของ Google

หลังจากให้สิทธิ์หรือปฏิเสธการเข้าถึง ผู้ใช้จะเปลี่ยนเส้นทางกลับไปยังตัวแฮนเดิล Callback ของโค้ด OAuth 2.0 ซึ่งระบุเป็นการเปลี่ยนเส้นทาง/ติดต่อกลับเมื่อสร้าง URL การให้สิทธิ์ของ Google

new GoogleAuthorizationRequestUrl(oauthProperties.getClientId(),
     
OAuthCodeCallbackHandlerServlet.getOAuthCodeCallbackHandlerUrl(req), oauthProperties
         
.getScopesAsString()).build()

เครื่องจัดการการเรียกกลับของรหัส OAuth 2.0 - OAuthCodeCallbackHandlerServlet - จัดการการเปลี่ยนเส้นทางจากปลายทาง OAuth 2.0 ของ Google มี 2 กรณีที่ต้องจัดการ ได้แก่

  • ผู้ใช้ให้สิทธิ์เข้าถึง: แยกวิเคราะห์คำขอรับโค้ด OAuth 2.0 จากพารามิเตอร์ของ URL
  • ผู้ใช้ปฏิเสธการเข้าถึง: แสดงข้อความต่อผู้ใช้

package com.google.oauthsample;

import ...

/**
 * Servlet handling the OAuth callback from the authentication service. We are
 * retrieving the OAuth code, then exchanging it for a refresh and an access
 * token and saving it.
 */

@SuppressWarnings("serial")
public class OAuthCodeCallbackHandlerServlet extends HttpServlet {

 
/** The name of the Oauth code URL parameter */
 
public static final String CODE_URL_PARAM_NAME = "code";

 
/** The name of the OAuth error URL parameter */
 
public static final String ERROR_URL_PARAM_NAME = "error";

 
/** The URL suffix of the servlet */
 
public static final String URL_MAPPING = "/oauth2callback";

 
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
   
// Getting the "error" URL parameter
   
String[] error = req.getParameterValues(ERROR_URL_PARAM_NAME);

   
// Checking if there was an error such as the user denied access
   
if (error != null && error.length > 0) {
      resp
.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, "There was an error: \""+error[0]+"\".");
     
return;
   
}
   
// Getting the "code" URL parameter
   
String[] code = req.getParameterValues(CODE_URL_PARAM_NAME);

   
// Checking conditions on the "code" URL parameter
   
if (code == null || code.length == 0) {
      resp
.sendError(HttpServletResponse.SC_BAD_REQUEST, "The \"code\" URL parameter is missing");
     
return;
   
}
 
}

 
/**
   * Construct the OAuth code callback handler URL.
   *
   * @param req the HttpRequest object
   * @return The constructed request's URL
   */

 
public static String getOAuthCodeCallbackHandlerUrl(HttpServletRequest req) {
   
String scheme = req.getScheme() + "://";
   
String serverName = req.getServerName();
   
String serverPort = (req.getServerPort() == 80) ? "" : ":" + req.getServerPort();
   
String contextPath = req.getContextPath();
   
String servletPath = URL_MAPPING;
   
String pathInfo = (req.getPathInfo() == null) ? "" : req.getPathInfo();
   
return scheme + serverName + serverPort + contextPath + servletPath + pathInfo;
 
}
}
ไฟล์ OAuthCodeCallbackHandlerServlet.java

แลกเปลี่ยนรหัสการให้สิทธิ์เพื่อรีเฟรชและโทเค็นเพื่อการเข้าถึง

จากนั้น OAuthCodeCallbackHandlerServlet จะแลกเปลี่ยนรหัส Auth 2.0 สำหรับโทเค็นการรีเฟรชและการเข้าถึง เก็บรหัสนี้ไว้ในพื้นที่เก็บข้อมูล และเปลี่ยนเส้นทางผู้ใช้กลับไปยัง URL PrintTaskListsTitlesServlet ดังนี้

โค้ดที่เพิ่มลงในไฟล์ด้านล่างมีการไฮไลต์ไวยากรณ์ โค้ดที่มีอยู่แล้วจึงเป็นสีเทา


 
/** URL ที่จะเปลี่ยนเส้นทางผู้ใช้ไปหลังจากจัดการการติดต่อกลับ พิจารณา
   * การบันทึกข้อมูลนี้ไว้ในคุกกี้ก่อนที่จะเปลี่ยนเส้นทางผู้ใช้ไปยัง Google
   * URL การให้สิทธิ์ ถ้าคุณมี URL หลายรายการที่สามารถเปลี่ยนเส้นทางผู้ชมได้ */

 
สตริงสุดท้ายแบบคงที่สาธารณะ REDIRECT_URL = "/";

 
/** การใช้งาน DAO ของโทเค็น OAuth ให้พิจารณาใช้การแทรกโค้ดแทนการใช้
   * การเริ่มต้นแบบคงที่ นอกจากนี้เรายังใช้หน่วยความจำที่เรียบง่าย
   * เป็นตัวอย่าง เปลี่ยนการใช้งานเป็นการใช้ระบบฐานข้อมูลของคุณ */

 
Public static OAuthTokenDao oauthTokenDao = OAuthTokenDaoMemoryImpl();


   
// สร้าง URL คำขอขาเข้า
   
สตริง requestUrl = getOAuthCodeCallbackHandlerUrl(req);

   
// แลกเปลี่ยนโค้ดสำหรับโทเค็น OAuth
   
AccessTokenResponse AccessTokenResponse = ExchangeCodeForAccessAndRefreshTokens(code[0],
        requestUrl
);

   
// การดึงข้อมูลผู้ใช้ปัจจุบัน
   
// ส่วนนี้กำลังใช้บริการผู้ใช้ของ App Engine แต่คุณควรแทนที่ค่านี้ด้วย
   
// การใช้งานผู้ใช้/การเข้าสู่ระบบของคุณเอง
   
UserService userService = UserServiceFile.getUserService();

   
อีเมลสตริง = userService.getCurrentUser().getEmail();

   
// บันทึกโทเค็น
    oauthTokenDao
.saveKeys(accessTokenResponse, อีเมล);

    resp
.sendRedirect(REDIRECT_URL);
 
}

วันที่
 
/**
   * แลกเปลี่ยนโค้ดที่ระบุสำหรับการแลกเปลี่ยนและโทเค็นการรีเฟรช
   *
   * @param code โค้ดที่ได้รับกลับจากบริการการให้สิทธิ์
   * @param CurrentsUrl ของ URL ของ Callback
   * @param oauthProperties ออบเจ็กต์ที่มีการกำหนดค่า OAuth
   * @return ออบเจ็กต์ที่มีทั้งโทเค็นเพื่อการเข้าถึงและการรีเฟรช
   * @throws IOException
   */

 
Public AccessTokenResponse ExchangeCodeForAccessAndRefreshTokens(รหัสสตริง, สตริง CurrentUrl)
     
throws IOException {

   
HttpTransport httpTransport = ใหม่ NetHttpTransport();
   
Jacksonfactor jsonElement = ใหม่ JacksonFactory();

   
// กำลังโหลดไฟล์การกำหนดค่า oauth
   
OAuthProperties oauthProperties = new OAuthProperties();

   
ส่งคืน GoogleAuthorizationCodeGrant(httpTransport, jsonFactory, oauthProperties ใหม่
       
.getClientId(), oauthProperties.getClientSecret(), code, currentUrl).exeโดยสมบูรณ์();
 
}
}

ไฟล์ OAuthCodeCallbackHandlerServlet.java


หมายเหตุ: การใช้งานข้างต้นใช้ไลบรารี App Engine บางรายการ ซึ่งใช้ในการลดความซับซ้อน หากคุณพัฒนาสำหรับแพลตฟอร์มอื่น คุณสามารถนำอินเทอร์เฟซ UserService ที่จัดการการตรวจสอบสิทธิ์ผู้ใช้กลับมาใช้ใหม่ได้



อ่านงานของผู้ใช้และแสดง



ผู้ใช้ได้ให้สิทธิ์แอปพลิเคชันเข้าถึงงาน แอปพลิเคชันมีโทเค็นการรีเฟรชที่บันทึกไว้ในพื้นที่เก็บข้อมูลที่เข้าถึงได้ผ่าน OAuthTokenDao เซิร์ฟเล็ต PrintTaskListsTitlesServlet สามารถใช้โทเค็นเหล่านี้เพื่อเข้าถึงงานของผู้ใช้และแสดง



โค้ดที่เพิ่มลงในไฟล์ด้านล่างมีการไฮไลต์ไวยากรณ์ โค้ดที่มีอยู่แล้วจึงเป็นสีเทา