Мне нужно установить некоторые заголовки авторизации после входа пользователя в систему для каждого последующего запроса.
Информация:
Чтобы настроить заголовки для конкретного запроса,
import {Headers} from 'angular2/http';
var headers = new Headers();
headers.append(headerName, value);
// HTTP POST using these headers
this.http.post(url, data, {
headers: headers
})
// do something with the response
Но было бы нецелесообразно вручную устанавливать заголовки запросов для каждого запроса таким образом.
Как установить настройки заголовков после входа пользователя в систему, а также удалить эти заголовки при выходе из системы?
Чтобы ответить, вы сомневаетесь, что можете предоставить сервис, который обертывает исходный объект Http
из Angular2. Что-то вроде описанного ниже.
import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
@Injectable()
export class HttpClient {
constructor(private http: Http) {}
createAuthorizationHeader(headers: Headers) {
headers.append('Authorization', 'Basic ' +
btoa('username:password'));
}
get(url) {
let headers = new Headers();
this.createAuthorizationHeader(headers);
return this.http.get(url, {
headers: headers
});
}
post(url, data) {
let headers = new Headers();
this.createAuthorizationHeader(headers);
return this.http.post(url, data, {
headers: headers
});
}
}
Вместо того, чтобы вставлять объект Http
, вы можете вставить это (HttpClient
).
import { HttpClient } from './http-client';
export class MyComponent {
// Notice we inject "our" HttpClient here, naming it Http so it easier
constructor(http: HttpClient) {
this.http = httpClient;
}
handleSomething() {
this.http.post(url, data).subscribe(result => {
// console.log( result );
});
}
}
Я также думаю, что что-то можно сделать с помощью нескольких поставщиков для класса Http
, предоставив свой собственный класс, расширяющий Http
один... См. эту ссылку: http://blog.thoughtram.io/angular2/2015/11/23/multi-providers-in-angular-2.html.
Bearer ${token}
, / \ "/ g, '')]);
Расширение BaseRequestOptions
может оказать большую помощь в этом сценарии. Проверьте следующий код:
import {provide} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_PROVIDERS, Headers, Http, BaseRequestOptions} from 'angular2/http';
import {AppCmp} from './components/app/app';
class MyRequestOptions extends BaseRequestOptions {
constructor () {
super();
this.headers.append('My-Custom-Header','MyCustomHeaderValue');
}
}
bootstrap(AppCmp, [
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(RequestOptions, { useClass: MyRequestOptions })
]);
В каждом вызове должно быть указано "My-Custom-Header".
Update:
Чтобы иметь возможность изменять заголовок в любое время, а не выше кода, вы также можете использовать следующий код для добавления нового заголовка:
this.http._defaultOptions.headers.append('Authorization', 'token');
для удаления вы можете сделать
this.http._defaultOptions.headers.delete('Authorization');
Также есть другая функция, которую вы можете использовать для установки значения:
this.http._defaultOptions.headers.set('Authorization', 'token');
Выше решение по-прежнему не полностью применимо в контексте typescript. _defaultHeaders защищен и не должен использоваться таким образом. Я бы порекомендовал это решение для быстрого исправления, но в долгосрочной перспективе лучше написать собственную оболочку вокруг http-звонков, которая также обрабатывает auth. Возьмите следующий пример из auth0, который лучше и чист.
https://github.com/auth0/angular2-jwt/blob/master/angular2-jwt.ts
HTTP-перехватчики теперь доступны через новый HttpClient
от @angular/common/http
, от Angular версий 4.3.x и выше.
Довольно просто добавить заголовок для каждого запроса:
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
} from '@angular/common/http';
export class AddHeaderInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Clone the request to add the new header
const clonedRequest = req.clone({ headers: req.headers.set('Authorization', 'Bearer 123') });
// Pass the cloned request instead of the original request to the next handle
return next.handle(clonedRequest);
}
}
Там принцип неизменности, что причина, по которой запрос нужно клонировать, прежде чем устанавливать что-то новое на нем.
Поскольку заголовки редактирования являются очень общей задачей, на самом деле есть ярлык для него (при клонировании запроса):
const clonedRequest = req.clone({ setHeaders: { Authorization: 'Bearer 123' } });
После создания перехватчика вы должны зарегистрировать его с помощью HTTP_INTERCEPTORS
.
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@NgModule({
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: AddHeaderInterceptor,
multi: true,
}],
})
export class AppModule {}
Хотя я отвечаю на него очень поздно, но это может помочь кому-то другому. Чтобы вводить заголовки ко всем запросам, когда используется @NgModule
, можно сделать следующее:
(я тестировал это в Angular 2.0.1)
/**
* Extending BaseRequestOptions to inject common headers to all requests.
*/
class CustomRequestOptions extends BaseRequestOptions {
constructor() {
super();
this.headers.append('Authorization', 'my-token');
this.headers.append('foo', 'bar');
}
}
Теперь в @NgModule
выполните следующие действия:
@NgModule({
declarations: [FooComponent],
imports : [
// Angular modules
BrowserModule,
HttpModule, // This is required
/* other modules */
],
providers : [
{provide: LocationStrategy, useClass: HashLocationStrategy},
// This is the main part. We are telling Angular to provide an instance of
// CustomRequestOptions whenever someone injects RequestOptions
{provide: RequestOptions, useClass: CustomRequestOptions}
],
bootstrap : [AppComponent]
})
В Angular 2.1.2
я подошел к этому, расширив angular Http:
import {Injectable} from "@angular/core";
import {Http, Headers, RequestOptionsArgs, Request, Response, ConnectionBackend, RequestOptions} from "@angular/http";
import {Observable} from 'rxjs/Observable';
@Injectable()
export class HttpClient extends Http {
constructor(protected _backend: ConnectionBackend, protected _defaultOptions: RequestOptions) {
super(_backend, _defaultOptions);
}
_setCustomHeaders(options?: RequestOptionsArgs):RequestOptionsArgs{
if(!options) {
options = new RequestOptions({});
}
if(localStorage.getItem("id_token")) {
if (!options.headers) {
options.headers = new Headers();
}
options.headers.set("Authorization", localStorage.getItem("id_token"))
}
return options;
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
options = this._setCustomHeaders(options);
return super.request(url, options)
}
}
то в моих поставщиках приложений я смог использовать пользовательский Factory для предоставления "Http"
import { RequestOptions, Http, XHRBackend} from '@angular/http';
import {HttpClient} from './httpClient';
import { RequestOptions, Http, XHRBackend} from '@angular/http';
import {HttpClient} from './httpClient';//above snippet
function httpClientFactory(xhrBackend: XHRBackend, requestOptions: RequestOptions): Http {
return new HttpClient(xhrBackend, requestOptions);
}
@NgModule({
imports:[
FormsModule,
BrowserModule,
],
declarations: APP_DECLARATIONS,
bootstrap:[AppComponent],
providers:[
{ provide: Http, useFactory: httpClientFactory, deps: [XHRBackend, RequestOptions]}
],
})
export class AppModule {
constructor(){
}
}
теперь мне не нужно объявлять каждый метод Http и использовать http
как обычно во всем приложении.
Создайте собственный класс Http путем расширения поставщика Angular 2 Http
и просто переопределите метод constructor
и request
в пользовательском классе Http. В приведенном ниже примере добавляется заголовок Authorization
в каждом HTTP-запросе.
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 HttpService extends Http {
constructor (backend: XHRBackend, options: RequestOptions) {
let token = localStorage.getItem('auth_token'); // your custom token getter function here
options.headers.set('Authorization', `Bearer ${token}`);
super(backend, options);
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let token = localStorage.getItem('auth_token');
if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
if (!options) {
// let make option object
options = {headers: new Headers()};
}
options.headers.set('Authorization', `Bearer ${token}`);
} else {
// we have to add the token to the url object
url.headers.set('Authorization', `Bearer ${token}`);
}
return super.request(url, options).catch(this.catchAuthError(this));
}
private catchAuthError (self: HttpService) {
// we have to pass HttpService own instance here as `self`
return (res: Response) => {
console.log(res);
if (res.status === 401 || res.status === 403) {
// if not authenticated
console.log(res);
}
return Observable.throw(res);
};
}
}
Затем настройте основной app.module.ts
, чтобы предоставить XHRBackend
в качестве поставщика ConnectionBackend
и RequestOptions
для вашего настраиваемого класса Http:
import { HttpModule, RequestOptions, XHRBackend } from '@angular/http';
import { HttpService } from './services/http.service';
...
@NgModule({
imports: [..],
providers: [
{
provide: HttpService,
useFactory: (backend: XHRBackend, options: RequestOptions) => {
return new HttpService(backend, options);
},
deps: [XHRBackend, RequestOptions]
}
],
bootstrap: [ AppComponent ]
})
После этого теперь вы можете использовать свой собственный http-провайдер в своих сервисах. Например:
import { Injectable } from '@angular/core';
import {HttpService} from './http.service';
@Injectable()
class UserService {
constructor (private http: HttpService) {}
// token will added automatically to get request header
getUser (id: number) {
return this.http.get(`/users/${id}`).map((res) => {
return res.json();
} );
}
}
Здесь содержится исчерпывающий справочник - http://adonespitogo.com/articles/angular-2-extending-http-provider/
Лучше поздно, чем никогда... =)
Вы можете взять концепцию расширенного BaseRequestOptions
(отсюда https://angular.io/docs/ts/latest/guide/server-communication.html#!#override-default-request-options) и обновить заголовки "на лету" (не только в конструкторе). Вы можете использовать свойство getter/setter "headers" переопределить следующим образом:
import { Injectable } from '@angular/core';
import { BaseRequestOptions, RequestOptions, Headers } from '@angular/http';
@Injectable()
export class DefaultRequestOptions extends BaseRequestOptions {
private superHeaders: Headers;
get headers() {
// Set the default 'Content-Type' header
this.superHeaders.set('Content-Type', 'application/json');
const token = localStorage.getItem('authToken');
if(token) {
this.superHeaders.set('Authorization', `Bearer ${token}`);
} else {
this.superHeaders.delete('Authorization');
}
return this.superHeaders;
}
set headers(headers: Headers) {
this.superHeaders = headers;
}
constructor() {
super();
}
}
export const requestOptionsProvider = { provide: RequestOptions, useClass: DefaultRequestOptions };
Вот улучшенная версия принятого ответа, обновленная для Angular2 final:
import {Injectable} from "@angular/core";
import {Http, Headers, Response, Request, BaseRequestOptions, RequestMethod} from "@angular/http";
import {I18nService} from "../lang-picker/i18n.service";
import {Observable} from "rxjs";
@Injectable()
export class HttpClient {
constructor(private http: Http, private i18n: I18nService ) {}
get(url:string):Observable<Response> {
return this.request(url, RequestMethod.Get);
}
post(url:string, body:any) {
return this.request(url, RequestMethod.Post, body);
}
private request(url:string, method:RequestMethod, body?:any):Observable<Response>{
let headers = new Headers();
this.createAcceptLanguageHeader(headers);
let options = new BaseRequestOptions();
options.headers = headers;
options.url = url;
options.method = method;
options.body = body;
options.withCredentials = true;
let request = new Request(options);
return this.http.request(request);
}
// set the accept-language header using the value from i18n service that holds the language currently selected by the user
private createAcceptLanguageHeader(headers:Headers) {
headers.append('Accept-Language', this.i18n.getCurrentLang());
}
}
Конечно, он должен быть расширен для таких методов, как delete
и put
, если это необходимо (они мне еще не нужны в этой точке моего проекта).
Преимущество состоит в том, что в методах get
/post
/... меньше дублируется код.
Обратите внимание, что в моем случае я использую файлы cookie для проверки подлинности. Мне нужен заголовок для i18n (заголовок Accept-Language
), потому что многие значения, возвращаемые нашим API, переведены на язык пользователя. В моем приложении служба i18n содержит язык, выбранный пользователем.
Хотя я отвечаю на это очень поздно, но если кто-то ищет более легкое решение.
Мы можем использовать angular2 -jwt. angular2 -jwt полезно автоматически подключать JSON Web Token (JWT) в качестве заголовка авторизации при выполнении HTTP-запросов из приложения Angular 2.
Мы можем установить глобальные заголовки с расширенной конфигурацией
export function authHttpServiceFactory(http: Http, options: RequestOptions) {
return new AuthHttp(new AuthConfig({
tokenName: 'token',
tokenGetter: (() => sessionStorage.getItem('token')),
globalHeaders: [{'Content-Type':'application/json'}],
}), http, options);
}
И отправка на токен запроса, например
getThing() {
let myHeader = new Headers();
myHeader.append('Content-Type', 'application/json');
this.authHttp.get('http://example.com/api/thing', { headers: myHeader })
.subscribe(
data => this.thing = data,
err => console.log(error),
() => console.log('Request Complete')
);
// Pass it after the body in a POST request
this.authHttp.post('http://example.com/api/thing', 'post body', { headers: myHeader })
.subscribe(
data => this.thing = data,
err => console.log(error),
() => console.log('Request Complete')
);
}
После некоторого расследования я нашел окончательный и самый простой способ расширить BaseRequestOptions
, который я предпочитаю.
Ниже перечислены способы, которые я пытался, и отказаться по какой-то причине:
1. Расширьте BaseRequestOptions
и добавьте динамические заголовки в constructor()
. Он не может работать, если я вхожу в систему. Он будет создан один раз. Так что это не динамично.
2. продолжите Http
. По той же причине, что и выше, я не могу добавить динамические заголовки в constructor()
. И если я переписываю метод request(..)
и устанавливаю заголовки, например:
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let token = localStorage.getItem(AppConstants.tokenName);
if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
if (!options) {
options = new RequestOptions({});
}
options.headers.set('Authorization', 'token_value');
} else {
url.headers.set('Authorization', 'token_value');
}
return super.request(url, options).catch(this.catchAuthError(this));
}
Вам просто нужно перезаписать этот метод, но не каждый метод get/post/put.
3. И мое предпочтительное решение расширяется BaseRequestOptions
и перезаписывает merge()
:
@Injectable()
export class AuthRequestOptions extends BaseRequestOptions {
merge(options?: RequestOptionsArgs): RequestOptions {
var newOptions = super.merge(options);
let token = localStorage.getItem(AppConstants.tokenName);
newOptions.headers.set(AppConstants.authHeaderName, token);
return newOptions;
}
}
эта функция merge()
будет вызываться для каждого запроса.
BaseRequestOptions
решение, основанное на расширении BaseRequestOptions
. Однако, к сожалению, это не сработало для меня. какие-либо возможные причины?
Мне нравится идея переопределить параметры по умолчанию, это похоже на хорошее решение.
Однако, если вы хотите расширить класс Http
. Обязательно прочитайте это!
Некоторые ответы здесь демонстрируют некорректную перегрузку метода request()
, что может привести к ошибкам с жесткими ошибками и странному поведению. Я наткнулся на это сам.
Это решение основано на реализации метода request()
в Angular 4.2.x
, но должно быть совместимо с будущим:
import {Observable} from 'rxjs/Observable';
import {Injectable} from '@angular/core';
import {
ConnectionBackend, Headers,
Http as NgHttp,
Request,
RequestOptions,
RequestOptionsArgs,
Response,
XHRBackend
} from '@angular/http';
import {AuthenticationStateService} from '../authentication/authentication-state.service';
@Injectable()
export class Http extends NgHttp {
constructor (
backend: ConnectionBackend,
defaultOptions: RequestOptions,
private authenticationStateService: AuthenticationStateService
) {
super(backend, defaultOptions);
}
request (url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
if ('string' === typeof url) {
url = this.rewriteUrl(url);
options = (options || new RequestOptions());
options.headers = this.updateHeaders(options.headers);
return super.request(url, options);
} else if (url instanceof Request) {
const request = url;
request.url = this.rewriteUrl(request.url);
request.headers = this.updateHeaders(request.headers);
return super.request(request);
} else {
throw new Error('First argument must be a url string or Request instance');
}
}
private rewriteUrl (url: string) {
return environment.backendBaseUrl + url;
}
private updateHeaders (headers?: Headers) {
headers = headers || new Headers();
// Authenticating the request.
if (this.authenticationStateService.isAuthenticated() && !headers.has('Authorization')) {
headers.append('Authorization', 'Bearer ' + this.authenticationStateService.getToken());
}
return headers;
}
}
Обратите внимание, что я импортирую оригинальный класс таким образом import { Http as NgHttp } from '@angular/http';
, чтобы предотвратить конфликты имен.
Задача, рассмотренная здесь, заключается в том, что метод
request()
имеет две разные сигнатуры вызова. Когда объектRequest
передается вместо URLstring
, аргументoptions
игнорируется Angular. Поэтому оба случая должны быть надлежащим образом обработаны.
И вот пример того, как зарегистрировать этот переопределенный класс с контейнером DI:
export const httpProvider = {
provide: NgHttp,
useFactory: httpFactory,
deps: [XHRBackend, RequestOptions, AuthenticationStateService]
};
export function httpFactory (
xhrBackend: XHRBackend,
requestOptions: RequestOptions,
authenticationStateService: AuthenticationStateService
): Http {
return new Http(
xhrBackend,
requestOptions,
authenticationStateService
);
}
При таком подходе вы можете вводить класс Http
нормально, но вместо этого ваш переопределенный класс будет вводиться магически. Это позволяет легко интегрировать ваше решение без изменения других частей приложения (полиморфизм в действии).
Просто добавьте httpProvider
в свойство providers
ваших метаданных модуля.
Как насчет сохранения отдельной службы, например, следующим образом
import {Injectable} from '@angular/core';
import {Headers, Http, RequestOptions} from '@angular/http';
@Injectable()
export class HttpClientService extends RequestOptions {
constructor(private requestOptionArgs:RequestOptions) {
super();
}
addHeader(headerName: string, headerValue: string ){
(this.requestOptionArgs.headers as Headers).set(headerName, headerValue);
}
}
и когда вы вызываете это из другого места, используйте this.httpClientService.addHeader("Authorization", "Bearer " + this.tok);
и вы увидите добавленный заголовок, например: - Авторизация, как показано ниже
Так я сделал для установки токена с каждым запросом.
import { RequestOptions, BaseRequestOptions, RequestOptionsArgs } from '@angular/http';
export class CustomRequestOptions extends BaseRequestOptions {
constructor() {
super();
this.headers.set('Content-Type', 'application/json');
}
merge(options?: RequestOptionsArgs): RequestOptions {
const token = localStorage.getItem('token');
const newOptions = super.merge(options);
if (token) {
newOptions.headers.set('Authorization', `Bearer ${token}`);
}
return newOptions;
}
}
И зарегистрируйтесь в app.module.ts
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [
{ provide: RequestOptions, useClass: CustomRequestOptions }
],
bootstrap: [AppComponent]
})
export class AppModule { }
Я могу выбрать более простое решение > Добавить новые заголовки в параметры по умолчанию, слияние или загрузку с помощью функции api get (или другого).
get(endpoint: string, params?: any, options?: RequestOptions) {
if (!options) {
options = new RequestOptions();
options.headers = new Headers( { "Accept": "application/json" } ); <<<<
}
// [...]
}
Конечно, вы можете экрнализировать этот заголовок в настройках по умолчанию или что угодно в своем классе. Это в обработанном Ionic API-интерфейсе API-класса API-класса iInjectable() {}
Это очень быстро, и это работает для меня. Мне не нужен формат json/ld.
Были внесены некоторые изменения для angular 2.0.1 и выше:
import {RequestOptions, RequestMethod, Headers} from '@angular/http';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { AppRoutingModule } from './app.routing.module';
import { AppComponent } from './app.component';
//you can move this class to a better place
class GlobalHttpOptions extends RequestOptions {
constructor() {
super({
method: RequestMethod.Get,
headers: new Headers({
'MyHeader': 'MyHeaderValue',
})
});
}
}
@NgModule({
imports: [ BrowserModule, HttpModule, AppRoutingModule ],
declarations: [ AppComponent],
bootstrap: [ AppComponent ],
providers: [ { provide: RequestOptions, useClass: GlobalHttpOptions} ]
})
export class AppModule { }
Вы можете использовать canActive
в своих маршрутах, например:
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { CanActivate } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate() {
// If user is not logged in we'll send them to the homepage
if (!this.auth.loggedIn()) {
this.router.navigate(['']);
return false;
}
return true;
}
}
const appRoutes: Routes = [
{
path: '', redirectTo: '/deals', pathMatch: 'full'
},
{
path: 'special',
component: PrivateDealsComponent,
/* We'll use the canActivate API and pass in our AuthGuard.
Now any time the /special route is hit, the AuthGuard will run
first to make sure the user is logged in before activating and
loading this route. */
canActivate: [AuthGuard]
}
];