我正在使用PEAN堆栈(即PostgreSQL-ExpressJS-Angel-NodeJS)构建一个身份验证应用程序.对于身份验证,我在后端使用express-session
.
我按如下方式判断用户登录状态:
- 在后台,判断会话cookie,查看
user
%属性是否存在于req.session
x对象中.
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' });
}
});
/* ... */
- 向
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,
});
}
/* ... */
- 截取任何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) {}
}
- 在前端,订阅
InterceptorService
中的interceptorResponse
个观察点,并将响应记录到控制台.
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
.问题是,在控制台中,我总是得到以下信息:
但如果我像这样控制日志(log)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'}
,如下面的屏幕截图所示.这意味着后端正确地将登录状态传递到前端.
问题
一百零二
EDIT 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 {}
EDIT 2个
在@Vonc的帮助下,我设法让整个事情像预期的那样工作.我是这么做的.
- 我在
server.js
中删除了我的初始代码,因为拦截器现在将依赖于api/get-user
端点,而不是像以前那样依赖于api/get-signin-status
.因此,我不再需要app.post('/api/get-signin-status', () => {})
.我现在使用api/get-user
端点的原因是因为两者都做了相同的事情(即,判断会话cookie以查看req.session
对象中是否存在user
属性),这意味着对于我的身份验证应用程序,只有一个就足够了.不需要判断会话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' });
}
});
*/
/* ... */
- 我删除了我在
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);
}
/* ... */
- 我删除了我在
interceptor.service.ts
中的初始代码,并按照@Vonc的建议添加了代码.Note: I changed the endpoint from 101 to 102.
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) {}
}
- 我删除了我在
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>