OAuth2更新トークンで私を覚えている

OAuth2リフレッシュトークンで記憶する

1. 概要

この記事では、OAuth 2リフレッシュトークンを利用して、OAuth 2で保護されたアプリケーションに「Remember Me」機能を追加します。

この記事は、AngularJSクライアントを介してアクセスされるSpring REST APIを保護するためにOAuth 2を使用するシリーズの続きです。 承認サーバー、リソースサーバー、およびフロントエンドクライアントの設定については、the introductory articleに従うことができます。

次に、Zuulプロキシを使用してhandling the refresh tokenに関する記事を続けることができます。

2. OAuth2アクセストークンと更新トークン

まず、OAuth 2トークンとその使用方法について簡単に要約してみましょう。

password許可タイプを使用した最初の認証試行で、ユーザーは有効なユーザー名とパスワード、およびクライアントIDとシークレットを送信する必要があります。 認証要求が成功すると、サーバーは次の形式の応答を送り返します。

{
    "access_token": "2e17505e-1c34-4ea6-a901-40e49ba786fa",
    "token_type": "bearer",
    "refresh_token": "e5f19364-862d-4212-ad14-9d6275ab1a62",
    "expires_in": 59,
    "scope": "read write",
}

サーバーレスポンスには、アクセストークンと更新トークンの両方が含まれていることがわかります。 アクセストークンは、認証を必要とする後続のAPI呼び出しに使用されますが、the purpose of the refresh token is to obtain a new valid access tokenまたは単に前のAPI呼び出しを取り消します。

refresh_token付与タイプを使用して新しいアクセストークンを受信するために、ユーザーは資格情報を入力する必要がなくなり、クライアントID、シークレット、そしてもちろん更新トークンのみを入力する必要があります。

The goal of using two types of tokens is to enhance user security.通常、アクセストークンの有効期間は短いため、攻撃者がアクセストークンを取得した場合、そのトークンを使用する時間は限られています。 一方、リフレッシュトークンが危険にさらされた場合、クライアントIDとシークレットも必要になるため、これは役に立ちません。

リフレッシュトークンのもう1つの利点は、アクセストークンを取り消すことができ、ユーザーが新しいIPからログインするなどの異常な動作を表示した場合、別のトークンを送り返さないことです。

3. 更新トークンを使用したRemember-Me機能

ユーザーは通常、アプリケーションにアクセスするたびに資格情報を入力する必要がないため、セッションを保持するオプションがあると便利です。

アクセストークンの有効期間は短いため、代わりに更新トークンを使用して新しいアクセストークンを生成し、アクセストークンの有効期限が切れるたびにユーザーに資格情報を要求する必要がなくなります。

次のセクションでは、この機能を実装する2つの方法について説明します。

  • まず、401ステータスコードを返すユーザーリクエストをインターセプトします。これは、アクセストークンが無効であることを意味します。 これが発生した場合、ユーザーが[remember me]オプションをオンにすると、refresh_token付与タイプを使用して新しいアクセストークンのリクエストが自動的に発行され、最初のリクエストが再度実行されます。

  • 次に、アクセストークンを事前に更新できます。トークンの有効期限が切れる数秒前に、トークンを更新するリクエストを送信します。

2番目のオプションには、ユーザーのリクエストが遅れないという利点があります。

4. 更新トークンの保存

前回の記事では、OAuthサーバーへのリクエストをインターセプトし、認証時に返送された更新トークンを抽出してサーバー側のCookieに保存するCustomPostZuulFilterを追加しました。

@Component
public class CustomPostZuulFilter extends ZuulFilter {

    @Override
    public Object run() {
        //...
        Cookie cookie = new Cookie("refreshToken", refreshToken);
        cookie.setHttpOnly(true);
        cookie.setPath(ctx.getRequest().getContextPath() + "/oauth/token");
        cookie.setMaxAge(2592000); // 30 days
        ctx.getResponse().addCookie(cookie);
        //...
    }
}

次に、loginData.remember変数にデータバインディングするチェックボックスをログインフォームに追加しましょう。


ログインフォームに追加のチェックボックスが表示されます。

image

loginDataオブジェクトは認証要求とともに送信されるため、rememberパラメータが含まれます。 認証要求が送信される前に、パラメーターに基づいてrememberという名前のCookieを設定します。

function obtainAccessToken(params){
    if (params.username != null){
        if (params.remember != null){
            $cookies.put("remember","yes");
        }
        else {
            $cookies.remove("remember");
        }
    }
    //...
}

結果として、このCookieをチェックして、ユーザーが記憶を希望するかどうかに応じて、アクセストークンの更新を試みる必要があるかどうかを判断します。

5. 401応答をインターセプトしてトークンを更新する

401応答で返されるリクエストをインターセプトするには、AngularJSアプリケーションを変更して、responseError関数を持つインターセプターを追加しましょう。

app.factory('rememberMeInterceptor', ['$q', '$injector', '$httpParamSerializer',
  function($q, $injector, $httpParamSerializer) {
    var interceptor = {
        responseError: function(response) {
            if (response.status == 401){

                // refresh access token

                // make the backend call again and chain the request
                return deferred.promise.then(function() {
                    return $http(response.config);
                });
            }
            return $q.reject(response);
        }
    };
    return interceptor;
}]);

この関数は、ステータスが401かどうかを確認します。これは、アクセストークンが無効であることを意味し、有効な場合は、新しい有効なアクセストークンを取得するためにリフレッシュトークンを使用しようとします。

これが成功した場合、関数は401エラーになった初期要求を再試行し続けます。 これにより、ユーザーのシームレスなエクスペリエンスが保証されます。

アクセストークンを更新するプロセスを詳しく見てみましょう。 まず、必要な変数を初期化します。

var $http = $injector.get('$http');
var $cookies = $injector.get('$cookies');
var deferred = $q.defer();

var refreshData = {grant_type:"refresh_token"};

var req = {
    method: 'POST',
    url: "oauth/token",
    headers: {"Content-type": "application/x-www-form-urlencoded; charset=utf-8"},
    data: $httpParamSerializer(refreshData)
}

パラメータgrant_type=refresh_tokenを使用して/ oauth / tokenエンドポイントにPOSTリクエストを送信するために使用するreq変数を確認できます。

次に、挿入した$httpモジュールを使用してリクエストを送信しましょう。 リクエストが成功すると、新しいアクセストークン値とaccess_token Cookieの新しい値を使用して新しいAuthenticationヘッダーを設定します。 リクエストが失敗した場合、リフレッシュトークンも最終的に期限切れになる可能性があり、ユーザーはログインページにリダイレクトされます。

$http(req).then(
    function(data){
        $http.defaults.headers.common.Authorization= 'Bearer ' + data.data.access_token;
        var expireDate = new Date (new Date().getTime() + (1000 * data.data.expires_in));
        $cookies.put("access_token", data.data.access_token, {'expires': expireDate});
        window.location.href="index";
    },function(){
        console.log("error");
        $cookies.remove("access_token");
        window.location.href = "login";
    }
);

更新トークンは、前の記事で実装したCustomPreZuulFilterによってリクエストに追加されます。

@Component
public class CustomPreZuulFilter extends ZuulFilter {

    @Override
    public Object run() {
        //...
        String refreshToken = extractRefreshToken(req);
        if (refreshToken != null) {
            Map param = new HashMap();
            param.put("refresh_token", new String[] { refreshToken });
            param.put("grant_type", new String[] { "refresh_token" });

            ctx.setRequest(new CustomHttpServletRequest(req, param));
        }
        //...
    }
}

インターセプターを定義することに加えて、それを$httpProviderに登録する必要があります。

app.config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('rememberMeInterceptor');
}]);

6. トークンをプロアクティブに更新する

「remember-me」機能を実装する別の方法は、現在のトークンが期限切れになる前に新しいアクセストークンを要求することです。

アクセストークンを受信すると、JSON応答には、トークンが有効になる秒数を指定するexpires_in値が含まれます。

この値を認証ごとにCookieに保存しましょう。

$cookies.put("validity", data.data.expires_in);

次に、更新リクエストを送信するために、AngularJS $timeoutサービスを使用して、トークンの有効期限が切れる10秒前に更新呼び出しをスケジュールします。

if ($cookies.get("remember") == "yes"){
    var validity = $cookies.get("validity");
    if (validity >10) validity -= 10;
    $timeout( function(){ $scope.refreshAccessToken(); }, validity * 1000);
}

7. 結論

このチュートリアルでは、OAuth2アプリケーションとAngularJSフロントエンドを使用して「RememberMe」機能を実装する2つの方法について説明しました。

例の完全なソースコードはover on GitHubにあります。 URL/login_rememberで「rememberme」機能を使用してログインページにアクセスできます。