これは Classroom アドオンに関する 2 番目のチュートリアルです 説明します。
このチュートリアルでは、Google ログインをウェブ アプリケーションに追加します。これは Classroom アドオンで必要な動作について説明しました。認証情報を使用する API への今後のすべての呼び出しでこの承認フローを管理できます。
このチュートリアルでは、次のことを行います。
- セッション データを iframe 内に維持するようにウェブアプリを構成します。
- Google OAuth 2.0 サーバー間ログインフローを実装する。
- OAuth 2.0 API の呼び出しを発行します。
- 承認、ログアウト、テストをサポートする追加のルートを作成する API 呼び出し。
完了したら、ウェブアプリでユーザーを完全に認証して、 Google API
承認フローについて
Google API は認証と認可に OAuth 2.0 プロトコルを使用します。 Google の OAuth 実装について詳しくは、 Google Identity OAuth ガイド
アプリケーションの認証情報は Google Cloud で管理されます。これらが 認証、認可するための 4 ステップのプロセスを実装し、 user:
- 承認をリクエストします。このリクエストの一部としてコールバック URL を指定します。 完了すると、認証 URL が表示されます。
- ユーザーを認証 URL にリダイレクトします。表示されるページで アプリが必要とする権限をユーザーに付与し、アクセスを許可するよう求めるプロンプトを表示します。 完了すると、ユーザーはコールバック URL に転送されます。
- コールバック ルートで認証コードを受け取ります。交換する アクセス トークンと更新トークンの認証コード。
- トークンを使用して Google API を呼び出します。
OAuth 2.0 認証情報を取得する
OAuth 認証情報が作成、ダウンロード済みであることを確認します。 概要ページをご覧ください。プロジェクトでこれらの認証情報を使用してユーザーのログインを行う必要があります。
認可フローを実装する
ウェブアプリにロジックとルートを追加して、記述されているフローを実現します。例: 次の特長があります。
- ランディング ページが表示されたら、承認フローを開始します。
- 認可をリクエストし、認可サーバーのレスポンスを処理します。
- 保存されている認証情報を消去します。
- アプリの権限を取り消します。
- API 呼び出しをテストする。
承認を開始する
必要に応じて、ランディング ページを変更して承認フローを開始します。「 アドオンには次の 2 つの状態があります。保存されているトークンがあるか、 OAuth 2.0 サーバーからトークンを取得する必要がある場合です。パフォーマンス セッション内にトークンがある場合はテスト用の API 呼び出し。それ以外の場合はユーザーにメッセージを表示 ログインします。
Python
routes.py
ファイルを開きます。 まず、いくつかの定数と Cookie を設定します。
iframe のセキュリティに関する推奨事項に沿って設定されていること。
# The file that contains the OAuth 2.0 client_id and client_secret.
CLIENT_SECRETS_FILE = "client_secret.json"
# The OAuth 2.0 access scopes to request.
# These scopes must match the scopes in your Google Cloud project's OAuth Consent
# Screen: https://console.cloud.google.com/apis/credentials/consent
SCOPES = [
"openid",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/classroom.addons.teacher",
"https://www.googleapis.com/auth/classroom.addons.student"
]
# Flask cookie configurations.
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="None",
)
アドオンのランディング ルートに移動します(この例では /classroom-addon
)。
。セッションに含まれない場合にログインページをレンダリングするロジックを追加する
「Credentials」を] キーを押します。
@app.route("/classroom-addon")
def classroom_addon():
if "credentials" not in flask.session:
return flask.render_template("authorization.html")
return flask.render_template(
"addon-discovery.html",
message="You've reached the addon discovery page.")
Java
このチュートリアルのコードは、step_02_sign_in
モジュールにあります。
application.properties
ファイルを開き、セッション構成を追加します。
iframe のセキュリティに関する推奨事項に準拠している。
# iFrame security recommendations call for cookies to have the HttpOnly and
# secure attribute set
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
# Ensures that the session is maintained across the iframe and sign-in pop-up.
server.servlet.session.cookie.same-site=none
サービスクラスを作成する(step_02_sign_in
モジュールの AuthService.java
)
コントローラ ファイル内のエンドポイントの背後にあるロジックを処理し、
リダイレクト URI、クライアント シークレット ファイルの場所、アドオンのスコープ
必要があります。リダイレクト URI は、ユーザーを特定の URI に再ルーティングするために使用します。
ユーザーがアプリを認可した後です詳しくは、
ソースコード内の README.md
:
client_secret.json
ファイル。
@Service
public class AuthService {
private static final String REDIRECT_URI = "https://localhost:5000/callback";
private static final String CLIENT_SECRET_FILE = "client_secret.json";
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
private static final String[] REQUIRED_SCOPES = {
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/classroom.addons.teacher",
"https://www.googleapis.com/auth/classroom.addons.student"
};
/** Creates and returns a Collection object with all requested scopes.
* @return Collection of scopes requested by the application.
*/
public static Collection<String> getScopes() {
return new ArrayList<>(Arrays.asList(REQUIRED_SCOPES));
}
}
コントローラ ファイル(step_02_sign_in
の AuthController.java
)を開きます。
モジュールなど)を指定し、ランディング ルートにロジックを追加して、
セッションに credentials
キーが含まれていないことを確認します。
@GetMapping(value = {"/start-auth-flow"})
public String startAuthFlow(Model model) {
try {
return "authorization";
} catch (Exception e) {
return onError(e.getMessage(), model);
}
}
@GetMapping(value = {"/addon-discovery"})
public String addon_discovery(HttpSession session, Model model) {
try {
if (session == null || session.getAttribute("credentials") == null) {
return startAuthFlow(model);
}
return "addon-discovery";
} catch (Exception e) {
return onError(e.getMessage(), model);
}
}
認証ページには、ユーザーが「署名」するためのリンクまたはボタンが必要です
表示されます。これをクリックすると、ユーザーが authorize
ルートにリダイレクトされます。
承認をリクエストする
承認をリクエストするには、ユーザーを認証ページにリダイレクトします。 URL。この URL には、スコープや Cloud Storage、 リクエストされたルート、認可の後の宛先ルート、ウェブアプリの できます。これらはこちらのサンプル URL で確認できます。
Python
次のインポートを routes.py
ファイルに追加します。
import google_auth_oauthlib.flow
新しいルート /authorize
を作成します。インスタンスを
google_auth_oauthlib.flow.Flow
;付属のドキュメントを使って
from_client_secrets_file
メソッドを使用します。
@app.route("/authorize")
def authorize():
# Create flow instance to manage the OAuth 2.0 Authorization Grant Flow
# steps.
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, scopes=SCOPES)
flow
の redirect_uri
を設定します。これはユーザーが想定するルートです。
アプリの承認後に戻る必要があります。次のバージョンでは /callback
です。
例です。
# The URI created here must exactly match one of the authorized redirect
# URIs for the OAuth 2.0 client, which you configured in the API Console. If
# this value doesn't match an authorized URI, you will get a
# "redirect_uri_mismatch" error.
flow.redirect_uri = flask.url_for("callback", _external=True)
フロー オブジェクトを使用して authorization_url
と state
を作成します。保存
セッション内の state
真正性を検証するために使用され、
サーバーが応答します。最後に、ユーザーを
authorization_url
。
authorization_url, state = flow.authorization_url(
# Enable offline access so that you can refresh an access token without
# re-prompting the user for permission. Recommended for web server apps.
access_type="offline",
# Enable incremental authorization. Recommended as a best practice.
include_granted_scopes="true")
# Store the state so the callback can verify the auth server response.
flask.session["state"] = state
# Redirect the user to the OAuth authorization URL.
return flask.redirect(authorization_url)
Java
次のメソッドを AuthService.java
ファイルに追加して、
使用して認証 URL を取得します。
getClientSecrets()
メソッドはクライアント シークレット ファイルを読み取り、構成します。GoogleClientSecrets
オブジェクト。getFlow()
メソッドはGoogleAuthorizationCodeFlow
のインスタンスを作成します。authorize()
メソッドはGoogleAuthorizationCodeFlow
オブジェクト(state
パラメータ、リダイレクト URI を指定して、認証 URL を取得します。state
パラメータは、レスポンスの信頼性を検証するために使用されます。 認可サーバーから取得できます。このメソッドは、 認証 URL とstate
パラメータ。
/** Reads the client secret file downloaded from Google Cloud.
* @return GoogleClientSecrets read in from client secret file. */
public GoogleClientSecrets getClientSecrets() throws Exception {
try {
InputStream in = SignInApplication.class.getClassLoader()
.getResourceAsStream(CLIENT_SECRET_FILE);
if (in == null) {
throw new FileNotFoundException("Client secret file not found: "
+ CLIENT_SECRET_FILE);
}
GoogleClientSecrets clientSecrets = GoogleClientSecrets
.load(JSON_FACTORY, new InputStreamReader(in));
return clientSecrets;
} catch (Exception e) {
throw e;
}
}
/** Builds and returns authorization code flow.
* @return GoogleAuthorizationCodeFlow object used to retrieve an access
* token and refresh token for the application.
* @throws Exception if reading client secrets or building code flow object
* is unsuccessful.
*/
public GoogleAuthorizationCodeFlow getFlow() throws Exception {
try {
GoogleAuthorizationCodeFlow authorizationCodeFlow =
new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT,
JSON_FACTORY,
getClientSecrets(),
getScopes())
.setAccessType("offline")
.build();
return authorizationCodeFlow;
} catch (Exception e) {
throw e;
}
}
/** Builds and returns a map with the authorization URL, which allows the
* user to give the app permission to their account, and the state parameter,
* which is used to prevent cross site request forgery.
* @return map with authorization URL and state parameter.
* @throws Exception if building the authorization URL is unsuccessful.
*/
public HashMap authorize() throws Exception {
HashMap<String, String> authDataMap = new HashMap<>();
try {
String state = new BigInteger(130, new SecureRandom()).toString(32);
authDataMap.put("state", state);
GoogleAuthorizationCodeFlow flow = getFlow();
String authUrl = flow
.newAuthorizationUrl()
.setState(state)
.setRedirectUri(REDIRECT_URI)
.build();
String url = authUrl;
authDataMap.put("url", url);
return authDataMap;
} catch (Exception e) {
throw e;
}
}
コンストラクタ インジェクションを使用して、 コントローラ クラスを使用します。
/** Declare AuthService to be used in the Controller class constructor. */
private final AuthService authService;
/** AuthController constructor. Uses constructor injection to instantiate
* the AuthService and UserRepository classes.
* @param authService the service class that handles the implementation logic
* of requests.
*/
public AuthController(AuthService authService) {
this.authService = authService;
}
/authorize
エンドポイントをコントローラ クラスに追加します。このエンドポイントは
state
パラメータを取得する AuthService authorize()
メソッド
認可 URL が含まれます。その後、エンドポイントは state
を保存します。
パラメータを追加し、ユーザーを認証 URL にリダイレクトします。
/** Redirects the sign-in pop-up to the authorization URL.
* @param response the current response to pass information to.
* @param session the current session.
* @throws Exception if redirection to the authorization URL is unsuccessful.
*/
@GetMapping(value = {"/authorize"})
public void authorize(HttpServletResponse response, HttpSession session)
throws Exception {
try {
HashMap authDataMap = authService.authorize();
String authUrl = authDataMap.get("url").toString();
String state = authDataMap.get("state").toString();
session.setAttribute("state", state);
response.sendRedirect(authUrl);
} catch (Exception e) {
throw e;
}
}
サーバー レスポンスを処理する
承認後、ユーザーはルートからredirect_uri
ルートに戻ります
確認できます。上記の例では、このルートは /callback
です。
ユーザーがcode
表示されます。次に、コードをアクセス トークンと更新トークンと交換します。
Python
Flask サーバー ファイルに次のインポートを追加します。
import google.oauth2.credentials
import googleapiclient.discovery
ルートをサーバーに追加します。別のインスタンスを
google_auth_oauthlib.flow.Flow
ですが、今回は
確認できます。
@app.route("/callback")
def callback():
state = flask.session["state"]
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
flow.redirect_uri = flask.url_for("callback", _external=True)
次に、アクセス トークンと更新トークンをリクエストします。幸いなことに、flow
オブジェクトは
これを実現する fetch_token
メソッドが含まれています。このメソッドでは、
code
引数か authorization_response
引数のいずれかを指定できます。こちらの
authorization_response
。これはリクエストの完全な URL であるためです。
authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)
これで認証情報は完成です。それらをセッションに保存し、 他の方法またはルートで取得してから、アドオンにリダイレクトできる ランディングページに誘導できます
credentials = flow.credentials
flask.session["credentials"] = {
"token": credentials.token,
"refresh_token": credentials.refresh_token,
"token_uri": credentials.token_uri,
"client_id": credentials.client_id,
"client_secret": credentials.client_secret,
"scopes": credentials.scopes
}
# Close the pop-up by rendering an HTML page with a script that redirects
# the owner and closes itself. This can be done with a bit of JavaScript:
# <script>
# window.opener.location.href = "{{ url_for('classroom_addon') }}";
# window.close();
# </script>
return flask.render_template("close-me.html")
Java
Credentials
オブジェクトを返すメソッドをサービスクラスに追加します。
アプリケーションによって実行されたリダイレクトから取得された認証コードを渡します。
指定します。この Credentials
オブジェクトは、後で取得するために使用されます。
更新トークンが含まれています。
/** Returns the required credentials to access Google APIs.
* @param authorizationCode the authorization code provided by the
* authorization URL that's used to obtain credentials.
* @return the credentials that were retrieved from the authorization flow.
* @throws Exception if retrieving credentials is unsuccessful.
*/
public Credential getAndSaveCredentials(String authorizationCode) throws Exception {
try {
GoogleAuthorizationCodeFlow flow = getFlow();
GoogleClientSecrets googleClientSecrets = getClientSecrets();
TokenResponse tokenResponse = flow.newTokenRequest(authorizationCode)
.setClientAuthentication(new ClientParametersAuthentication(
googleClientSecrets.getWeb().getClientId(),
googleClientSecrets.getWeb().getClientSecret()))
.setRedirectUri(REDIRECT_URI)
.execute();
Credential credential = flow.createAndStoreCredential(tokenResponse, null);
return credential;
} catch (Exception e) {
throw e;
}
}
リダイレクト URI のエンドポイントをコントローラに追加します。取得
リクエストの認証コードと state
パラメータ。比較
セッションに格納される state
属性に state
パラメータ。もし
認証フローに進みます。一致しない場合、
エラーが返されます。
次に、AuthService
getAndSaveCredentials
メソッドを呼び出して、
パラメータとして指定します。Credentials
の取得後
セッションに格納します次に、ダイアログを閉じて、
ユーザーをアドオンのランディングページに
誘導します
/** Handles the redirect URL to grant the application access to the user's
* account.
* @param request the current request used to obtain the authorization code
* and state parameter from.
* @param session the current session.
* @param response the current response to pass information to.
* @param model the Model interface to pass error information that's
* displayed on the error page.
* @return the close-pop-up template if authorization is successful, or the
* onError method to handle and display the error message.
*/
@GetMapping(value = {"/callback"})
public String callback(HttpServletRequest request, HttpSession session,
HttpServletResponse response, Model model) {
try {
String authCode = request.getParameter("code");
String requestState = request.getParameter("state");
String sessionState = session.getAttribute("state").toString();
if (!requestState.equals(sessionState)) {
response.setStatus(401);
return onError("Invalid state parameter.", model);
}
Credential credentials = authService.getAndSaveCredentials(authCode);
session.setAttribute("credentials", credentials);
return "close-pop-up";
} catch (Exception e) {
return onError(e.getMessage(), model);
}
}
API 呼び出しをテストする
フローが完了すると、Google API の呼び出しを発行できるようになります。
たとえば、ユーザーのプロフィール情報をリクエストします。こちらから ユーザーの情報を取得します。
Python
次のドキュメントをご覧ください: OAuth 2.0 Discovery API これを使用して、入力された UserInfo オブジェクトを取得します。
# Retrieve the credentials from the session data and construct a
# Credentials instance.
credentials = google.oauth2.credentials.Credentials(
**flask.session["credentials"])
# Construct the OAuth 2.0 v2 discovery API library.
user_info_service = googleapiclient.discovery.build(
serviceName="oauth2", version="v2", credentials=credentials)
# Request and store the username in the session.
# This allows it to be used in other methods or in an HTML template.
flask.session["username"] = (
user_info_service.userinfo().get().execute().get("name"))
Java
次のコマンドを使用して、UserInfo
オブジェクトを構築するメソッドをサービスクラスに作成します。
パラメータとして Credentials
を指定します。
/** Obtains the Userinfo object by passing in the required credentials.
* @param credentials retrieved from the authorization flow.
* @return the Userinfo object for the currently signed-in user.
* @throws IOException if creating UserInfo service or obtaining the
* Userinfo object is unsuccessful.
*/
public Userinfo getUserInfo(Credential credentials) throws IOException {
try {
Oauth2 userInfoService = new Oauth2.Builder(
new NetHttpTransport(),
new GsonFactory(),
credentials).build();
Userinfo userinfo = userInfoService.userinfo().get().execute();
return userinfo;
} catch (Exception e) {
throw e;
}
}
ユーザーのメールアドレスを表示するコントローラに /test
エンドポイントを追加します。
/** Returns the test request page with the user's email.
* @param session the current session.
* @param model the Model interface to pass error information that's
* displayed on the error page.
* @return the test page that displays the current user's email or the
* onError method to handle and display the error message.
*/
@GetMapping(value = {"/test"})
public String test(HttpSession session, Model model) {
try {
Credential credentials = (Credential) session.getAttribute("credentials");
Userinfo userInfo = authService.getUserInfo(credentials);
String userInfoEmail = userInfo.getEmail();
if (userInfoEmail != null) {
model.addAttribute("userEmail", userInfoEmail);
} else {
return onError("Could not get user email.", model);
}
return "test";
} catch (Exception e) {
return onError(e.getMessage(), model);
}
}
認証ストレージの消去
「消去」できます。現在のセッションからユーザーの認証情報を削除します。 これにより、アドオンのランディング ページでルーティングをテストできます。
ユーザーが以前にログアウトしたことを示す表示をおすすめします。 アドオンのランディングページにリダイレクトしますアプリは 承認フローで認証情報を取得できますが、ユーザーは認証情報を アプリを再承認する必要があります。
Python
@app.route("/clear")
def clear_credentials():
if "credentials" in flask.session:
del flask.session["credentials"]
del flask.session["username"]
return flask.render_template("signed-out.html")
代わりに flask.session.clear()
を使用しますが、これは意図していない可能性があります
セッションに格納されている他の値がある場合、
Java
コントローラに、/clear
エンドポイントを追加します。
/** Clears the credentials in the session and returns the sign-out
* confirmation page.
* @param session the current session.
* @return the sign-out confirmation page.
*/
@GetMapping(value = {"/clear"})
public String clear(HttpSession session) {
try {
if (session != null && session.getAttribute("credentials") != null) {
session.removeAttribute("credentials");
}
return "sign-out";
} catch (Exception e) {
return onError(e.getMessage(), model);
}
}
アプリの権限を取り消す
ユーザーは、POST
リクエストを送信してアプリの権限を取り消すことができます。
https://oauth2.googleapis.com/revoke
。リクエストには、ユーザーの
できます。
Python
import requests
@app.route("/revoke")
def revoke():
if "credentials" not in flask.session:
return flask.render_template("addon-discovery.html",
message="You need to authorize before " +
"attempting to revoke credentials.")
credentials = google.oauth2.credentials.Credentials(
**flask.session["credentials"])
revoke = requests.post(
"https://oauth2.googleapis.com/revoke",
params={"token": credentials.token},
headers={"content-type": "application/x-www-form-urlencoded"})
if "credentials" in flask.session:
del flask.session["credentials"]
del flask.session["username"]
status_code = getattr(revoke, "status_code")
if status_code == 200:
return flask.render_template("authorization.html")
else:
return flask.render_template(
"index.html", message="An error occurred during revocation!")
Java
取り消しエンドポイントを呼び出すメソッドをサービスクラスに追加します。
/** Revokes the app's permissions to the user's account.
* @param credentials retrieved from the authorization flow.
* @return response entity returned from the HTTP call to obtain response
* information.
* @throws RestClientException if the POST request to the revoke endpoint is
* unsuccessful.
*/
public ResponseEntity<String> revokeCredentials(Credential credentials) throws RestClientException {
try {
String accessToken = credentials.getAccessToken();
String url = "https://oauth2.googleapis.com/revoke?token=" + accessToken;
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
HttpEntity<Object> httpEntity = new HttpEntity<Object>(httpHeaders);
ResponseEntity<String> responseEntity = new RestTemplate().exchange(
url,
HttpMethod.POST,
httpEntity,
String.class);
return responseEntity;
} catch (RestClientException e) {
throw e;
}
}
セッションをクリアするエンドポイント /revoke
をコントローラに追加します。
取り消しが取り消された場合は、ユーザーを認証ページにリダイレクトする
成功です。
/** Revokes the app's permissions and returns the authorization page.
* @param session the current session.
* @return the authorization page.
* @throws Exception if revoking access is unsuccessful.
*/
@GetMapping(value = {"/revoke"})
public String revoke(HttpSession session) throws Exception {
try {
if (session != null && session.getAttribute("credentials") != null) {
Credential credentials = (Credential) session.getAttribute("credentials");
ResponseEntity responseEntity = authService.revokeCredentials(credentials);
Integer httpStatusCode = responseEntity.getStatusCodeValue();
if (httpStatusCode != 200) {
return onError("There was an issue revoking access: " +
responseEntity.getStatusCode(), model);
}
session.removeAttribute("credentials");
}
return startAuthFlow(model);
} catch (Exception e) {
return onError(e.getMessage(), model);
}
}
アドオンをテストする
Google Classroom にログインします。 教師テストユーザーの 1 人としてログインさせます。[授業] タブに移動し、 新しい課題を作成します。テキスト領域の下にある [アドオン] ボタンをクリックします。 アドオンを選択しますiframe が開き、アドオンが Attachment Setup URI: GWM SDK の App Engine 構成 できます。
これで、次のステップ、繰り返しの処理を開始する準備が整いました。 確認できます。