TLDR:我的任务是完成3个请求而不是1,并返回最后一个响应作为对第一个请求的响应,而不对请求发起者进行任何其他修改。
我扩展了Angular Http
类,以自动将授权标头附加到我的所有请求中,并实现我自己的授权错误处理。
看起来像这样:
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
// ... append some headers
super.request(url, options).catch((error: Response) => {
if (error.status === 401 || error.status === 403 ) {
// todo: Send refreshToken request to get new credentials
// todo: Send current request again with new credentials
// todo: If request is completed properly pretend everything was fine and return response
}
});
}
我想捕获授权错误,通过发送令牌刷新请求来修复它们并返回对初始请求的正确响应。
现在有很多代码使用http
并且我不想更改它,因此必须返回固定的响应,因为初始化时没有任何人知道它。< / p>
其中一种方法是使用同步请求,但我认为这不是一个好主意。
如果解决方案可行,请告诉我们如何实现?
PS。在刷新令牌时执行另一个请求并且崩溃到授权中导致另一个令牌刷新时,可能会出现问题。但现在这并不重要。
答案 0 :(得分:2)
目标主要通过使用flatMap
撰写请求来实现。
主要功能:
它旨在使用REST身份验证模型,其中包括:
gToken
)aToken
)refresh_token
)您很可能需要重写请求以适合您的后端,但这里提供的是评论良好的服务而非默认Http
:
import {Injectable} from '@angular/core';
import {
Http, XHRBackend, RequestOptions, RequestOptionsArgs, Request, Response, RequestMethod,
Headers
} from "@angular/http";
import { Observable } from "rxjs";
import { StorageService } from "../storage.service";
import { AppService } from "./app.service";
@Injectable()
export class HttpClientService extends Http {
private autoAppendHeadersDefault = true;
constructor(
backend: XHRBackend,
defaultOptions: RequestOptions,
private storageService: StorageService,
private appState: AppService,
) {
super(backend, defaultOptions);
this.autoAppendHeadersDefault = this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS;
}
request(url: string | Request, options?: RequestOptionsArgs, disableTryFix = false): Observable<Response> {
// Checking if the request needs headers to be appended
let assetRequest = false;
if(url instanceof Request) {
if(url.url.startsWith("/assets")) {
assetRequest = true;
}
}
// Appending headers
if(!assetRequest && this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS && url instanceof Request) {
// append aToken || gToken
let token = this.storageService.get('aToken');
if('undefined' === typeof token || !token) {
token = this.storageService.get('gToken');
}
if('undefined' !== typeof token && token) {
url.headers.set('Authorization', `Bearer ${token}`);
} else {
// neither aToken nor gToken are set
if(disableTryFix) {
this.removeAllTokens();
return Observable.throw({error: "Can't reauth: 01"});
}
return this.tryFixAuth().flatMap(
(res:any) => {
res = res.json();
this.storageService.set('gToken', res.access_token);
return this.request(url, options, true);
}
);
}
// headers appended to every request
if(!url.headers.get('Content-Type')) {
url.headers.append('Content-Type', 'application/json');
}
}
this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS = this.autoAppendHeadersDefault;
return super.request(url, options).catch((error: Response) => {
if (error.status === 401 /* || error.status === 403 */ ) {
if(disableTryFix) {
this.removeAllTokens();
this.navigateOnAuthFail();
return Observable.throw({error: "Can't reauth: 02"});
}
return this.tryFixAuth().flatMap(
(res: any) => {
res = res.json();
if('undefined' !== typeof res.refresh_token)
{
// got aToken & refresh_token
this.storageService.set('aToken', res.access_token);
this.storageService.set('refresh_token', res.refresh_token);
}
else if('undefined' !== typeof res.access_token)
{
// got only gToken
this.storageService.set('gToken', res.access_token);
}
else
{
console.log('tryFix: nothing useful returned')
// got no aToken, no gToken, no refresh_token
}
// retry request
return this.request(url, options, true);
}
);
}
// handle invalid refresh_token
if(disableTryFix && error.status === 400) {
console.log('Wrong refresh token (400)');
this.storageService.remove('refresh_token');
this.storageService.remove('aToken');
this.navigateOnAuthFail();
// handle invalid refresh token
}
return Observable.throw(error);
});
}
private tryFixAuth(): Observable<Response> {
console.log('Trying to fix auth');
if(this.storageService.get('refresh_token'))
{
return this.refreshToken();
}
else if(this.storageService.get('aToken'))
{
// no refresh_token, but aToken
// since aToken is dead it's not useful
this.storageService.remove('aToken');
}
else
{
// no aToken, no refresh_token
// possibly there's a gToken
// since the request is trying to fix itself (is failed) the gToken is most likely not valid
return this.guestToken();
}
}
// sends request with refresh_token to get new aToken
// the request returns only aToken and refresh_token, no gToken
private refreshToken(): Observable<Response> {
// is called only when refresh_token is set
let refreshToken = this.storageService.get('refresh_token');
// check refresh_token in case it's not checked before
if('undefined' === typeof refreshToken || !refreshToken || refreshToken == 'undefined') {
this.storageService.remove('refresh_token');
// there's no refresh_token saved
return Observable.throw({error: "Refresh token is not set"});
}
// form refresh_token request
const headers = new Headers();
headers.append('Authorization', `Bearer ${this.storageService.get('gToken')}`);
headers.append('Content-Type', 'application/json');
const url = `${this.appState.config.WEBSITE_ENDPOINT}/oauth/v2/token`;
const localData = JSON.stringify({
"client_id": this.appState.config.CLIENT_ID,
"client_secret": this.appState.config.CLIENT_SECRET,
"grant_type": 'refresh_token',
"refresh_token": refreshToken
});
this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS = false;
// refresh_token request
return this.request(
new Request({
method: RequestMethod.Post,
url: url,
headers: headers,
body: localData
}),
null, true);
}
// sends request to get new gToken
private guestToken(): Observable<Response> {
const url = `${
this.appState.config.WEBSITE_ENDPOINT}/oauth/v2/token?client_id=${
this.appState.config.CLIENT_ID}&client_secret=${
this.appState.config.CLIENT_SECRET}&grant_type=client_credentials`;
this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS = false;
return super.get(url);
}
// Aux methods
private navigateOnAuthFail() {
console.warn('Page is going to be refreshed');
// redirect to auth is performed after reload by authGuard
// it's possible to add some warning before reload
window.location.reload();
}
private removeAllTokens() {
this.storageService.remove('aToken');
this.storageService.remove('gToken');
this.storageService.remove('refresh_token');
}
}