Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/wcf/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Angularjs 如何刷新用户';s会话使用JWT令牌_Angularjs_Oauth 2.0_Owin_Jwt - Fatal编程技术网

Angularjs 如何刷新用户';s会话使用JWT令牌

Angularjs 如何刷新用户';s会话使用JWT令牌,angularjs,oauth-2.0,owin,jwt,Angularjs,Oauth 2.0,Owin,Jwt,我是Angular的新手,我正在尝试实现一种机制,让活动用户在活动期间保持登录状态 我有一个令牌端点,它向用户发出JWT令牌 { "access_token": "base64encodedandsignedstring", "token_type": "bearer", "expires_in": 299, "refresh_token": "f87ae3bee04b4ca39af6f22a198274df", "as:clien

我是Angular的新手,我正在尝试实现一种机制,让活动用户在活动期间保持登录状态

我有一个令牌端点,它向用户发出JWT令牌

{  
      "access_token": "base64encodedandsignedstring",
      "token_type": "bearer",
      "expires_in": 299,
      "refresh_token": "f87ae3bee04b4ca39af6f22a198274df",
      "as:client_id": "mysite",
      "userName": "me@email.com",
      ".issued": "Wed, 19 Apr 2017 20:15:58 GMT",
      ".expires": "Wed, 19 Apr 2017 20:20:58 GMT"
}
以及另一个调用,该调用获取刷新令牌并使用它生成新的访问令牌。从Api的角度来看,这应该使我能够传入refresh_令牌,并生成一个新的JWT和一个新的过期日期

我不能100%确定如何连接角度侧以支持我的登录功能:

var _login = function (LoginData) {

    var data = "grant_type=password&username=" + LoginData.UserName + "&password=" + LoginData.Password + "&client_id=4TierWeb";

    var deferred = $q.defer();

    $http.post(serviceBase + 'authToken', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function (response) {

        localStorageService.set('authorizationData', { token: response.data.access_token, userName: LoginData.userName, refreshToken: response.data.refresh_token, useRefreshTokens: true });

        _authentication.isAuth = true;
        _authentication.userName = LoginData.UserName;

        deferred.resolve(response);

    }, function (err, status) {
        _logOut();
        deferred.reject(err);
    });

    return deferred.promise;

};
var _refreshToken = function () {
    var deferred = $q.defer();

    var authData = localStorageService.get('authorizationData');

    if (authData) {

        if (authData.useRefreshTokens) {

            var data = "grant_type=refresh_token&refresh_token=" + authData.refreshToken + "&client_id=4TierWeb";

            localStorageService.remove('authorizationData');

            $http.post(serviceBase + 'authToken', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function (response) {

                localStorageService.set('authorizationData', { token: response.data.access_token, userName: response.data.userName, refreshToken: response.data.refresh_token, useRefreshTokens: true });
               // response.headers.Authorization = 'Bearer ' + response.token;
                deferred.resolve(response);

            }, function (err, status) {
                _logOut();
                deferred.reject(err);
            });
        }
    }

    return deferred.promise;
};
我的刷新功能:

var _login = function (LoginData) {

    var data = "grant_type=password&username=" + LoginData.UserName + "&password=" + LoginData.Password + "&client_id=4TierWeb";

    var deferred = $q.defer();

    $http.post(serviceBase + 'authToken', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function (response) {

        localStorageService.set('authorizationData', { token: response.data.access_token, userName: LoginData.userName, refreshToken: response.data.refresh_token, useRefreshTokens: true });

        _authentication.isAuth = true;
        _authentication.userName = LoginData.UserName;

        deferred.resolve(response);

    }, function (err, status) {
        _logOut();
        deferred.reject(err);
    });

    return deferred.promise;

};
var _refreshToken = function () {
    var deferred = $q.defer();

    var authData = localStorageService.get('authorizationData');

    if (authData) {

        if (authData.useRefreshTokens) {

            var data = "grant_type=refresh_token&refresh_token=" + authData.refreshToken + "&client_id=4TierWeb";

            localStorageService.remove('authorizationData');

            $http.post(serviceBase + 'authToken', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function (response) {

                localStorageService.set('authorizationData', { token: response.data.access_token, userName: response.data.userName, refreshToken: response.data.refresh_token, useRefreshTokens: true });
               // response.headers.Authorization = 'Bearer ' + response.token;
                deferred.resolve(response);

            }, function (err, status) {
                _logOut();
                deferred.reject(err);
            });
        }
    }

    return deferred.promise;
};
还有我的拦截器:

app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
    var authInterceptorServiceFactory = {
        request: function (config) {

            config.headers = config.headers || {};

            var authData = localStorageService.get('authorizationData');
            if (authData) {
                    config.headers.Authorization = 'Bearer ' + authData.token;
                    }
            return config;
        },
        responseError: function (error) {
            if (error.status === 401) {
                        $location.path('/login');
                    }
            return $q.reject(error);
        }
    };
    return authInterceptorServiceFactory;
}]);
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
        return {
            request: function(config) {

                config.headers = config.headers || {};

                var authData = localStorageService.get('authorizationData');
                if (authData) {
                    config.headers.Authorization = 'Bearer ' + authData.token;
                }
                return config;
            },
            responseError: function(rejection) {
                //var promise = $q.reject(rejection);

                if (rejection.status === 401) {
                    var authService = $injector.get('authService');
                    // refresh the token
                    authService.refreshToken().then(function() {
                        // retry the request
                        var $http = $injector.get('$http');
                        return $http(rejection.config);
                    });
                }
                return $q.reject(rejection);
            }
        };
    }
]);
我的拦截器在没有上述刷新机制的情况下工作得很好,但是当我添加刷新机制时:

   authService.RefreshToken();
   config.headers.Authorization = 'Bearer ' + authData.token;
我能够拉下一个新的JWT,但下一行似乎不再正常工作,我的登录页上有401,有效载荷中没有承载令牌,我在这里遗漏了什么

更新的拦截器:

app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
    var authInterceptorServiceFactory = {
        request: function (config) {

            config.headers = config.headers || {};

            var authData = localStorageService.get('authorizationData');
            if (authData) {
                    config.headers.Authorization = 'Bearer ' + authData.token;
                    }
            return config;
        },
        responseError: function (error) {
            if (error.status === 401) {
                        $location.path('/login');
                    }
            return $q.reject(error);
        }
    };
    return authInterceptorServiceFactory;
}]);
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
        return {
            request: function(config) {

                config.headers = config.headers || {};

                var authData = localStorageService.get('authorizationData');
                if (authData) {
                    config.headers.Authorization = 'Bearer ' + authData.token;
                }
                return config;
            },
            responseError: function(rejection) {
                //var promise = $q.reject(rejection);

                if (rejection.status === 401) {
                    var authService = $injector.get('authService');
                    // refresh the token
                    authService.refreshToken().then(function() {
                        // retry the request
                        var $http = $injector.get('$http');
                        return $http(rejection.config);
                    });
                }
                return $q.reject(rejection);
            }
        };
    }
]);

您需要等待刷新令牌请求完成获取新访问令牌,然后使用响应发出新请求

比如:
authService.refreshToken()。然后(doRequest())

假设您在authService中有两个函数:

函数getAccessToken(){…获取登录()中的访问令牌。}
-返回承诺

函数refreshtToken(){…现有逻辑…}
-返回承诺

我们假设您将使用来解码JWT令牌

我认为您可以通过两种方式实现:

第一种方式:获取令牌并立即订阅,以便在到期时刷新

function getAccessToken() {
  ...
  return $http(...)
    .then(function(response) {
       // ...correct credentials logic...

       if(authService.refreshTimeout) {
         $window.clearTimeout(authService.refreshTimeout);
       }

       // decode JWT token
       const access_token_jwt_data = jwt_decode(response.data.access_token);

       // myOffset is an offset you choose so you can refresh the token before expiry
       const expirationDate = new Date(access_token_jwt_data * 1000 - myOffset);

       // refresh the token when expired
       authService.refreshTimeout = $window.setTimeout(function() {
         authService.refreshToken();
       });

       return response.data;
    })
    .catch(function(error) {
      // ...invalid credentials logic...
      return $q.reject(error);
    });
}
注意:您可以使用
window
而不是
$window
。我不认为你当时真的需要一个新的消化周期。无论$http请求是否成功完成,都将启动新摘要

注意:这意味着您在重新加载页面时还需要注意大小写。从而重新启用刷新超时。因此,您可以重用
getAccessToken()
中的逻辑来订阅到期日期,但这次您可以从
localStorage
获取令牌。这意味着您可以将此逻辑重构为一个名为
函数subscribeToTokenExpiry(accessToken)
的新函数。因此,如果本地存储中有访问令牌,则可以在
authService
构造函数中调用此函数

第二种方式:从服务器收到错误代码后,刷新HTTP侦听器中的令牌

如果拦截器接收到与令牌到期情况匹配的错误,则可以刷新令牌。这在很大程度上取决于后端实现,因此您可能会收到HTTP 401或400或其他任何内容以及一些自定义错误消息或代码。因此,您需要检查您的后端。还要检查它们在返回HTTP状态和错误代码时是否一致。一些实现细节可能会随着时间的推移而改变,框架开发人员可能会建议用户不要依赖于特定的实现,因为它仅用于内部使用。在这种情况下,您可以只保留HTTP状态并省略代码,因为将来您将有更好的机会拥有相同的代码。但请询问您的后端或创建框架的后端

注意:关于Spring OAuth2后端实现,请在回答的末尾找到详细信息

回到您的代码,您的拦截器应该如下所示:

app.factory('authInterceptorService',
    ['$q', '$location', 'localStorageService', 'authService', '$injector',
    function ($q, $location, localStorageService, authService, $injector) {
    var authInterceptorServiceFactory = {
        request: function (config) {

            config.headers = config.headers || {};

            var authData = localStorageService.get('authorizationData');
            if (authData) {
                config.headers.Authorization = 'Bearer ' + authData.token;
            }
            return config;
        },
        responseError: function (response) {
            let promise = $q.reject(response);

            if (response.status === 401
                && response.data 
                && response.data.error === 'invalid_token') {

                // refresh the token
                promise = authService.refreshToken().then(function () {
                    // retry the request
                    const $http = $injector.get('$http');
                    return $http(response.config);
                });
            }

            return promise.catch(function () {
                $location.path('/login');
                return $q.reject(response);
            });
        }
    };
    return authInterceptorServiceFactory;
}]);
Spring Security OAuth2后端相关:

我为那些对Spring授权服务器实现感兴趣的人添加了这一部分,因为Spring是Java世界中非常流行的框架

1)有效期

关于有效期,以秒表示。JWT解码字符串后,您将在access\u令牌中找到“exp”键,并刷新\u令牌

这是以秒为单位的,因为您添加了使用以下功能的:

if (token.getExpiration() != null) {
  response.put(EXP, token.getExpiration().getTime() / 1000);
}
在配置授权服务器时添加:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    // ...
    endpoints.accessTokenConverter(jwtAccessTokenConverter)
    // ...
  }
}
2)访问令牌过期响应

您可能需要处理
HTTP400
HTTP401
状态中的一个或两个,并依赖
{“错误”:“无效的\u令牌”}
。但这在很大程度上取决于后端是如何使用Spring实现的

请参见下面的解释:

关于资源服务器配置(我们向其发送请求以获取所需资源的配置),流程如下:

  • 用于从请求获取访问令牌的servlet筛选器
  • 解析令牌字符串的步骤
  • 获取访问令牌对象
  • OAuth2AuthenticationProcessingFilter
    try catch将委托创建异常响应的异常
  • DefaultTokenServices
    是一种
    ResourceServerTokenServices
    实现。 有两种可能的实现,一种是this
    DefaultTokenServices
    ,另一种是

    如果我们使用
    DefaultTokenServices
    ,那么将在资源服务器上检查令牌。这意味着资源服务器知道为令牌签名的密钥,以便检查令牌的有效性。这种方法意味着将密钥分发给想要这种行为的各方

    如果我们使用
    RemoteTokenServices
    ,那么将根据所处理的
    /oauth/check_token
    端点检查令牌

    到期时,
    CheckTokenEndpoint
    将创建一个带有HTTP 400的文件,该文件将被转换为带有数据的
    HTTP 400
    {“错误”:“无效的令牌”,“错误描述”:“令牌已过期”}

    另一方面,
    DefaultTokenServices
    也将创建一个
    InvalidTokenException
    异常,但带有其他消息且不覆盖HTTP stat