OAuth2 Erinnere dich an mich mit Refresh Token

OAuth2 Erinnere dich an mich mit Refresh Token

1. Überblick

In diesem Artikel fügen wir einer gesicherten Anwendung vonOAuth 2eine "Remember Me" -Funktion hinzu, indem wir das Aktualisierungstoken vonOAuth 2nutzen.

Dieser Artikel ist eine Fortsetzung unserer Reihe zur Verwendung vonOAuth 2 zum Sichern einer Spring REST-API, auf die über einenAngularJS-Client zugegriffen wird. Zum Einrichten des Autorisierungsservers, des Ressourcenservers und des Front-End-Clients können Siethe introductory article folgen.

Anschließend können Sie mit unserem Artikel zuhandling the refresh token fortfahren, indem Sie einenZuul-Proxy verwenden.

2. OAuth 2 Zugriffstoken und Aktualisierungstoken

Lassen Sie uns zunächst einen kurzen Rückblick auf dieOAuth 2-Token geben und wie sie verwendet werden können.

Bei einem ersten Authentifizierungsversuch unter Verwendung des Grant-Typspasswordmuss der Benutzer einen gültigen Benutzernamen und ein gültiges Kennwort sowie die Client-ID und das Geheimnis senden. Wenn die Authentifizierungsanforderung erfolgreich ist, sendet der Server eine Antwort in der folgenden Form zurück:

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

Wir können sehen, dass die Serverantwort sowohl ein Zugriffstoken als auch ein Aktualisierungstoken enthält. Das Zugriffstoken wird für nachfolgende API-Aufrufe verwendet, für die eine Authentifizierung erforderlich ist, währendthe purpose of the refresh token is to obtain a new valid access token oder nur das vorherige widerrufen.

Um ein neues Zugriffstoken mit dem Grant-Typrefresh_tokenzu erhalten, muss der Benutzer nicht mehr seine Anmeldeinformationen eingeben, sondern nur noch die Client-ID, das Geheimnis und natürlich das Aktualisierungstoken.

The goal of using two types of tokens is to enhance user security. Normalerweise hat das Zugriffstoken eine kürzere Gültigkeitsdauer, sodass ein Angreifer, wenn er das Zugriffstoken erhält, nur eine begrenzte Zeit hat, es zu verwenden. Wenn andererseits das Aktualisierungstoken kompromittiert ist, ist dies nutzlos, da auch die Client-ID und das Geheimnis benötigt werden.

Ein weiterer Vorteil von Aktualisierungstoken besteht darin, dass der Zugriffstoken widerrufen werden kann und kein weiterer zurückgesendet werden kann, wenn der Benutzer ein ungewöhnliches Verhalten aufweist, z. B. das Anmelden von einer neuen IP-Adresse.

3. Remember-Me-Funktionalität mit Aktualisierungstoken

Benutzer finden es normalerweise nützlich, die Option zu haben, ihre Sitzung beizubehalten, da sie ihre Anmeldeinformationen nicht jedes Mal eingeben müssen, wenn sie auf die Anwendung zugreifen.

Da das Zugriffstoken eine kürzere Gültigkeitsdauer hat, können wir stattdessen Aktualisierungstoken verwenden, um neue Zugriffstoken zu generieren, und müssen den Benutzer nicht jedes Mal nach seinen Anmeldeinformationen fragen, wenn ein Zugriffstoken abläuft.

In den nächsten Abschnitten werden zwei Möglichkeiten zur Implementierung dieser Funktionalität erläutert:

  • Zuerst durch Abfangen einer Benutzeranforderung, die einen 401-Statuscode zurückgibt, was bedeutet, dass das Zugriffstoken ungültig ist. Wenn der Benutzer in diesem Fall die Option "An mich erinnern" aktiviert hat, wird automatisch eine Anforderung für ein neues Zugriffstoken unter Verwendung des Grant-Typs vonrefresh_tokenausgegeben und die erste Anforderung erneut ausgeführt.

  • Zweitens können wir das Zugriffstoken proaktiv aktualisieren. Wir senden eine Anforderung zum Aktualisieren des Tokens einige Sekunden vor dessen Ablauf

Die zweite Option hat den Vorteil, dass die Anforderungen des Benutzers nicht verzögert werden.

4. Speichern des Aktualisierungstokens

Im vorherigen Artikel haben wir einCustomPostZuulFilter hinzugefügt, das Anforderungen an denOAuth-Server abfängt, das bei der Authentifizierung zurückgesendete Aktualisierungstoken extrahiert und in einem serverseitigen Cookie speichert:

@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);
        //...
    }
}

Fügen Sie als Nächstes ein Kontrollkästchen in unserem Anmeldeformular hinzu, das eine Datenbindung an die VariableloginData.rememberenthält:


In unserem Anmeldeformular wird nun ein zusätzliches Kontrollkästchen angezeigt:

image

DasloginData-Objekt wird mit der Authentifizierungsanforderung gesendet, sodass es denremember-Parameter enthält. Bevor die Authentifizierungsanforderung gesendet wird, setzen wir ein Cookie mit dem Namenremember basierend auf dem Parameter:

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

Infolgedessen überprüfen wir dieses Cookie, um festzustellen, ob wir versuchen sollten, das Zugriffstoken zu aktualisieren oder nicht, je nachdem, ob der Benutzer erinnert werden möchte oder nicht.

5. Aktualisieren von Token durch Abfangen von 401 Antworten

Um Anforderungen abzufangen, die mit einer 401-Antwort zurückkommen, ändern wir die AnwendungAngularJS, um einen Interceptor mit der FunktionresponseErrorhinzuzufügen:

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;
}]);

Unsere Funktion prüft, ob der Status 401 lautet - was bedeutet, dass das Zugriffstoken ungültig ist. In diesem Fall wird versucht, das Aktualisierungstoken zu verwenden, um ein neues gültiges Zugriffstoken zu erhalten.

Wenn dies erfolgreich ist, wiederholt die Funktion die ursprüngliche Anforderung, die zum Fehler 401 geführt hat. Dies stellt eine nahtlose Erfahrung für den Benutzer sicher.

Schauen wir uns den Vorgang des Aktualisierens des Zugriffstokens genauer an. Zuerst werden wir die notwendigen Variablen initialisieren:

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)
}

Sie können die Variablereq sehen, mit der wir eine POST-Anforderung mit dem Parametergrant_type=refresh_token an den Endpunkt / oauth / token senden.

Verwenden Sie als Nächstes das$http-Modul, das wir injiziert haben, um die Anforderung zu senden. Wenn die Anforderung erfolgreich ist, setzen wir einen neuenAuthentication-Header mit dem neuen Zugriffstokenwert sowie einen neuen Wert für dasaccess_token-Cookie. Wenn die Anforderung fehlschlägt, was passieren kann, wenn das Aktualisierungstoken auch irgendwann abläuft, wird der Benutzer auf die Anmeldeseite umgeleitet:

$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";
    }
);

Das Aktualisierungstoken wird der Anforderung durch dieCustomPreZuulFilterhinzugefügt, die wir im vorherigen Artikel implementiert haben:

@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));
        }
        //...
    }
}

Zusätzlich zur Definition des Interceptors müssen wir ihn mit$httpProvider registrieren:

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

6. Token proaktiv aktualisieren

Eine andere Möglichkeit zum Implementieren der Funktion "Merken" besteht darin, ein neues Zugriffstoken anzufordern, bevor das aktuelle abläuft.

Beim Empfang eines Zugriffstokens enthält die JSON-Antwort einenexpires_in-Wert, der die Anzahl der Sekunden angibt, für die das Token gültig ist.

Speichern Sie diesen Wert für jede Authentifizierung in einem Cookie:

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

Um eine Aktualisierungsanforderung zu senden, verwenden wir den DienstAngularJS $timeout, um einen Aktualisierungsaufruf 10 Sekunden vor Ablauf des Tokens zu planen:

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

7. Fazit

In diesem Tutorial haben wir zwei Möglichkeiten untersucht, wie wir die "Remember Me" -Funktionalität mit einer OAuth2-Anwendung und einemAngularJS-Frontend implementieren können.

Der vollständige Quellcode der Beispiele istover on GitHub. Sie können auf die Anmeldeseite mit der Funktion "An mich erinnern" unter der URL/login_remember zugreifen.