javascript - Angular2: oauth2 med token-headere
Jeg er ny på Angular2. I 1.* var alt fint med interceptorer, bare legg dem til: og du har overalt dine headere, og du kan håndtere forespørslene dine når tokenen blir ugyldig...
I angular2 bruker jeg RxJs. Så jeg får tokenet mitt:
getToken(login: string, pwd: string): Observable<boolean> {
let bodyParams = {
grant_type: 'password',
client_id: 'admin',
scope: AppConst.CLIENT_SCOPE,
username: login,
password: pwd
};
let params = new URLSearchParams();
for (let key in bodyParams) {
params.set(key, bodyParams[key])
}
let headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});
let options = new RequestOptions({headers: headers});
return this.http.post(AppConst.IDENTITY_BASE_URI + '/connect/token', params.toString(), options)
.map((response: Response) => {
let data = response.json();
if (data) {
this.data = data;
localStorage.setItem('auth', JSON.stringify({
access_token: data.access_token,
refresh_token: data.refresh_token
}));
return true;
} else {
return false;
}
});
}
og hvordan kan jeg bruke denne tokenen i hver forespørsel? Jeg vil ikke sette .header i hver forespørsel. Det er en dårlig praksis.
Og så: for eksempel når jeg gjør en forespørsel, og får 401-feil, hvordan kan jeg avbryte, og få en ny token, og deretter gjenoppta alle forespørsler, som det var i angular 1?
jeg prøvde å bruke JWT herfra jwt, men det oppfyller ikke kravene mine, forresten i første Angular brukte jeg Restangular - og alt var fint der (også med manualen for tokens:https://github.com/mgonto/restangular#seterrorinterceptor)
Svar #1
Du kan enten utvide standard http-tjenesten og bruke den utvidede versjonen, eller du kan lage en metode som tar noen parametere (om nødvendig) og returnerer et RequestOptions-objekt for å sende til standard http-tjeneste.
Alternativ 1
Du kan opprette en tjeneste:
@Injectable()
export class HttpUtils {
constructor(private _cookieService: CookieService) { }
public optionsWithAuth(method: RequestMethod, searchParams?: URLSearchParams): RequestOptionsArgs {
let headers = new Headers();
let token = 'fancyToken';
if (token) {
headers.append('Auth', token);
}
return this.options(method, searchParams, headers);
}
public options(method: RequestMethod, searchParams?: URLSearchParams, header?: Headers): RequestOptionsArgs {
let headers = header || new Headers();
if (!headers.has('Content-Type')) {
headers.append('Content-Type', 'application/json');
}
let options = new RequestOptions({headers: headers});
if (method === RequestMethod.Get || method === RequestMethod.Delete) {
options.body = '';
}
if (searchParams) {
options.params = searchParams;
}
return options;
}
public handleError(error: Response) {
return (res: Response) => {
if (res.status === 401) {
// do something
}
return Observable.throw(res);
};
}
}
Brukseksempel:
this._http
.get('/api/customers', this._httpUtils.optionsWithAuth(RequestMethod.Get))
.map(res => <Customer[]>res.json())
.catch(err => this._httpUtils.handleError(err));
Dette eksemplet bruker informasjonskapsler for å lagre og få tilgang til tokenet. Du kan også bruke en parameter.
Alternativ 2
Det andre alternativet er å utvide HTTP-tjenesten, for eksempel slik:
import { Injectable } from '@angular/core';
import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
@Injectable()
export class MyHttp extends Http {
constructor (backend: XHRBackend, options: RequestOptions) {
let token = 'fancyToken';
options.headers.set('Auth', token);
super(backend, options);
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let token = 'fancyToken';
if (typeof url === 'string') {
if (!options) {
options = {headers: new Headers()};
}
options.headers.append('Auth', token);
} else {
url.headers.append('Auth', token);
}
return super.request(url, options).catch(this.handleError(this));
}
private handleError (self: MyHttp) {
return (res: Response) => {
if (res.status === 401) {
// do something
}
return Observable.throw(res);
};
}
}
Og i din @NgModule:
@NgModule({
// other stuff ...
providers: [
{
provide: MyHttp,
useFactory: (backend: XHRBackend, options: RequestOptions) => {
return new MyHttp(backend, options);
},
deps: [XHRBackend, RequestOptions]
}
]
// a little bit more other stuff ...
})
Bruk:
@Injectable()
class CustomerService {
constructor(private _http: MyHttp) {
}
query(): Observable<Customer[]> {
return this._http
.get('/api/customers')
.map(res => <Customer[]>res.json())
.catch(err => console.log('error', err));
}
}
Ekstra:
Hvis du vil bruke oppfriskingstoken for å skaffe et nytt token, kan du gjøre noe som dette:
private handleError (self: MyHttp, url?: string|Request, options?: RequestOptionsArgs) {
return (res: Response) => {
if (res.status === 401 || res.status === 403) {
let refreshToken:string = 'fancyRefreshToken';
let body:any = JSON.stringify({refreshToken: refreshToken});
return super.post('/api/token/refresh', body)
.map(res => {
// set new token
})
.catch(err => Observable.throw(err))
.subscribe(res => this.request(url, options), err => Observable.throw(err));
}
return Observable.throw(res);
};
}
For å være ærlig har jeg ikke testet dette, men det kan gi deg i det minste et utgangspunkt.
Svar #2
Vi løste problemet med utvidelsen av AuthHttp. Vi la til en metode på AuthHttp for å sette en ny header dynamisk på denne måten (X-RoleId er en tilpasset header)
declare module 'angular2-jwt' {
interface AuthHttp {
setRoleId(config: {});
}
}
AuthHttp.prototype.setRoleId = function (roleId) {
let jsThis = <any>(this);
jsThis.config.globalHeaders = [
{'Content-Type': 'application/json'},
{'X-RoleId': roleId}
];
};
