我正在使用PEAN堆栈(即PostgreSQL - ExpressJS - Angular - NodeJS)构建一个身份验证应用程序。
我检查用户登录状态,如下所示:
1.在后端,检查会话cookie以查看req.session
对象中是否存在user
属性。
server.js
/* ... */
app.post('/api/get-signin-status', async (req, res) => {
try {
if (req.session.user) {
return res.status(200).json({ message: 'User logged in' });
} else {
return res.status(400).json({ message: 'User logged out' });
}
} catch {
return res.status(500).json({ message: 'Internal server error' });
}
});
/* ... */
字符串
1.向api/get-signin-status
端点发送HTTP POST请求,其中包含可选数据,并在请求中包含Cookie。
auth.service.ts
/* ... */
getSignInStatus(data?: any) {
return this.http.post(this.authUrl + 'api/get-signin-status', data, {
withCredentials: true,
});
}
/* ... */
型
1.拦截任何HTTP请求并提供可观察的interceptorResponse$
用于订阅拦截请求的响应。
interceptor.service.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { AuthService } from 'src/app/auth/services/auth.service';
@Injectable({
providedIn: 'root',
})
export class InterceptorService implements HttpInterceptor {
private interceptorResponse$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const signInStatusObserver = {
next: (x: any) => {
this.interceptorResponse$.next({ success: true, response: x });
},
error: (err: any) => {
this.interceptorResponse$.next({ success: false, response: err });
},
};
this.authService.getSignInStatus().subscribe(signInStatusObserver);
return next.handle(httpRequest);
}
getInterceptorResponse(): Observable<any> {
return this.interceptorResponse$.asObservable();
}
constructor(private authService: AuthService) {}
}
型
1.在前端,订阅来自InterceptorService
的interceptorResponse
observable,并将响应记录到控制台。
header.component.ts
import { Component, OnInit } from '@angular/core';
import { InterceptorService } from '../auth/services/interceptor.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
interceptorResponse: any;
constructor(
private interceptorService: InterceptorService
) {
this.interceptorService.getInterceptorResponse().subscribe((response: any) => {
console.log(response);
this.interceptorResponse = response;
if (response) {
console.log('Interceptor response success:', response.response);
} else {
console.log('Interceptor response is null');
}
});
}
ngOnInit(): void {}
}
型
问题
根据StackOverflow answer,我应该使用BehaviorSubject
。问题是,在控制台中,我总是得到以下内容:
x1c 0d1x的数据
但是如果我控制台记录next
和error
如下:
interceptor.service.ts
/* ... */
const signInStatusObserver = {
next: (x: any) => {
console.log(x);
this.interceptorResponse$.next({ success: true, response: x });
},
error: (err: any) => {
console.log(err.error.message);
this.interceptorResponse$.next({ success: false, response: err });
},
};
/* ... */
型
我在控制台中看到了预期的{message: 'User logged in'}
,如下面的屏幕截图所示。这意味着后端正确地将登录状态传递给前端。
的
提问
为什么Angular拦截器总是使用BehaviorSubject
返回null
(即初始值),而不是更新后的值?
编辑1
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HeaderComponent } from './header/header.component';
import { AppComponent } from './app.component';
import { FooterComponent } from './footer/footer.component';
import { AppRoutingModule } from './app-routing.module';
import { RoutingComponents } from './app-routing.module';
import { SharedModule } from './shared/shared.module';
import { HttpClientModule } from '@angular/common/http';
import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';
import { CodeInputModule } from 'angular-code-input';
import { IfSignedOut } from './auth/guards/if-signed-out.guard';
import { IfSignedIn } from './auth/guards/if-signed-in.guard';
import { InterceptorService } from './auth/services/interceptor.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@NgModule({
declarations: [HeaderComponent, AppComponent, FooterComponent, RoutingComponents],
imports: [BrowserModule, BrowserAnimationsModule, AppRoutingModule, SharedModule, HttpClientModule, MatMenuModule, MatSidenavModule, CodeInputModule],
providers: [IfSignedOut, IfSignedIn, { provide: HTTP_INTERCEPTORS, useClass: InterceptorService, multi: true }],
bootstrap: [AppComponent],
})
export class AppModule {}
型
编辑2
在@VonC的帮助下,我设法让整个事情按预期进行。我是这么做的
1.我删除了我在server.js
中的初始代码,因为拦截器现在将依赖于api/get-user
端点,而不是像以前那样依赖于api/get-signin-status
。因此,我不再需要app.post('/api/get-signin-status', () => {})
。我现在使用api/get-user
端点的原因是因为两者都做了同样的事情(即检查会话cookie以查看req.session
对象中是否存在user
属性),这意味着只有一个端点足以用于我的auth应用程序。不需要两次检查会话cookie。
server.js
/* ... */
/* Removed */
/*
app.post('/api/get-signin-status', async (req, res) => {
try {
if (req.session.user) {
return res.status(200).json({ message: 'User logged in' });
} else {
return res.status(400).json({ message: 'User logged out' });
}
} catch {
return res.status(500).json({ message: 'Internal server error' });
}
});
*/
/* ... */
型
1.我删除了我在auth.service.ts
中的初始代码,并按照@VonC的建议添加了代码。
auth.service.ts
/* ... */
/* Removed */
/*
getSignInStatus(data?: any) {
return this.http.post(this.authUrl + 'api/get-signin-status', data, {
withCredentials: true,
});
}
*/
/* Added */
private signInStatus$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
getSignInStatusObserver(): Observable<any> {
return this.signInStatus$.asObservable();
}
setSignInStatus(status: any): void {
this.signInStatus$.next(status);
}
/* ... */
型
1.我删除了我在interceptor.service.ts
中的初始代码,并按照@VonC的建议添加了代码。* 注意:我将端点从api/get-signin-status
更改为api/get-user
。*
interceptor.service.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { AuthService } from 'src/app/auth/services/auth.service';
@Injectable({
providedIn: 'root',
})
export class InterceptorService implements HttpInterceptor {
intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(httpRequest).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse && httpRequest.url.endsWith('api/get-user')) {
this.authService.setSignInStatus({ success: true, response: event.body });
}
}),
catchError((err: any) => {
if (httpRequest.url.endsWith('api/get-user')) {
this.authService.setSignInStatus({ success: false, response: err });
}
return throwError(err);
})
);
}
constructor(private authService: AuthService) {}
}
型
1.我删除了我在header.component.ts
中的初始代码,并按照@VonC的建议添加了代码。
header.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from 'src/app/auth/services/auth.service';
import { Router } from '@angular/router';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
signInStatus: any;
constructor(private authService: AuthService, public publicAuthService: AuthService, private signOutRouter: Router, private snackBar: MatSnackBar) {
this.authService.getSignInStatusObserver().subscribe((response: any) => {
this.signInStatus = response;
if (response) {
console.log('Sign in status success:', response.response);
} else {
console.log('Sign in status is null');
}
});
}
ngOnInit(): void {}
}
型
现在我终于可以根据来自后端的登录状态显示header.component.html
中的元素,如下所示:
header.component.html
<div *ngIf="signInStatus">Show this element if the user is signed in</div>
<div *ngIf="!signInStatus">Show this element if the user is signed out</div>
型
2条答案
按热度按时间2uluyalo1#
“
interceptor response is null
“可能是预期的:您在拦截器中使用RxJSBehaviorSubject
的方式可能会导致此问题。拦截器的目的是拦截HTTP请求,并对这些请求或响应执行某些操作。
(See例如“Intro to Angular Http Interceptors”(来自**Cory Rylan**)
但是在您的拦截器中,您是通过调用
this.authService.getSignInStatus().subscribe(signInStatusObserver);
而不是拦截现有的HTTP请求来创建一个 * 新的 * HTTP请求。这意味着,当您的组件订阅getInterceptorResponse()
时,对此请求的响应可能不会立即可用。......因此可能是空拦截器响应。
例如,您可以使用拦截器来检查使用者的验证状态:
字符串
在此更新版本中,我们订阅了
next
的handle
方法,该方法返回HTTP响应的Observable
。它使用tap
操作符处理成功的响应,并使用catchError
操作符处理错误。我假设
setSignInStatus
是您的AuthService
中的一个方法,用于更新存储在BehaviorSubject
中的登录状态。我还在检查请求URL是否以“
/api/get-signin-status
”结尾,以确保我们仅为该特定请求设置登录状态。这意味着您可以使用以下项实现此用户的身份验证状态:
型
您需要更新
header.component.ts
以使用authService
而不是interceptorService
:型
最后,在您的组件中,您将订阅
getSignInStatusObserver
而不是getInterceptorResponse
。这确保了每次从“
/api/get-signin-status
”收到HTTP响应时都更新登录状态,并且组件可以订阅状态更新。组件中的订阅将如下所示:
型
在
HeaderComponent
的构造函数中,我们注入的是AuthService
,而不是InterceptorService
。我们订阅的是getSignInStatusObserver()
,它是AuthService
中的一个方法,用于返回Observable
的登录状态。当登录状态更新时,将使用新状态作为参数调用订阅的函数。我们将登录状态存储在
signInStatus
属性中,并将其记录到控制台。如果响应存在,我们还将记录一条成功消息,否则将记录一条指示登录状态为null的消息。它反映了原始代码的行为。stszievb2#
我猜问题是Angular创建了你的interceptorService的多个示例。一个实际上用作拦截器,另一个被注入到您的组件/服务中。
我隐约记得有同样的问题。当时我是这样解决的:
字符串
组件和拦截器都使用另一个服务来共享数据。
编辑
快速搜索实际上证实了这一点。参见Interceptor create two instances of singleton service