我正在使用PEAN堆栈(即PostgreSQL-ExpressJS-Angel-NodeJS)构建一个身份验证应用程序.对于身份验证,我在后端使用express-session.

我按如下方式判断用户登录状态:

  1. 在后台,判断会话cookie,查看user%属性是否存在于req.sessionx对象中.

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个观察点,并将响应记录到控制台.

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.问题是,在控制台中,我总是得到以下信息:

Screenshot

但如果我像这样控制日志(log)nexterror:

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'},如下面的屏幕截图所示.这意味着后端正确地将登录状态传递到前端.

Screenshot

问题

一百零二


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的帮助下,我设法让整个事情像预期的那样工作.我是这么做的.

  1. 我在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' });
  }
});
*/

/* ... */
  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的建议添加了代码.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) {}
}
  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>

推荐答案

"interceptor response is null"可能是意料之中的:您在拦截器中使用RxJSBehaviorSubject的方式可能会导致这个问题.

The purpose of an interceptor is to intercept HTTP requests and do something with those requests or responses.
(See for instance "Intro to Angular Http Interceptors" from Cory Rylan)

But in your interceptor, you are creating a new HTTP request by calling this.authService.getSignInStatus().subscribe(signInStatusObserver); instead of intercepting an existing one. That means the response to this request might not be available immediately when your component subscribes to getInterceptorResponse().
... hence possibly the null interceptor response.


例如,您可以使用拦截器判断用户的身份验证状态:

// interceptor.service.ts

import { tap, catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class InterceptorService implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  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-signin-status')) {
          this.authService.setSignInStatus({ success: true, response: event.body });
        }
      }),
      catchError((err: any) => {
        if (httpRequest.url.endsWith('/api/get-signin-status')) {
          this.authService.setSignInStatus({ success: false, response: err });
        }
        return throwError(err);
      })
    );
  }
}

在这个更新版本中,我们订阅nexthandle方法,该方法返回Observable的HTTP响应.它使用tap运算符来处理成功的响应,并使用catchError运算符来处理错误.

I am assuming setSignInStatus is a method in your AuthService that updates the sign-in status stored in a BehaviorSubject.
I am also checking if the request URL ends with '/api/get-signin-status' to make sure we are only setting the sign-in status for that specific request.

这意味着您可以通过以下方式实现此用户的身份验证状态:

// auth.service.ts

private signInStatus$: BehaviorSubject<any> = new BehaviorSubject<any>(null);

getSignInStatusObserver(): Observable<any> {
  return this.signInStatus$.asObservable();
}

setSignInStatus(status: any): void {
  this.signInStatus$.next(status);
}

你需要更新你的header.component.ts,才能使用authService而不是interceptorService:

// header.component.ts

constructor(private authService: AuthService) {
  this.authService.getSignInStatusObserver().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');
    }
  });
}

最后,在您的组件中,您将订阅getSignInStatusObserver,而不是getInterceptorResponse.

这确保了每次收到来自‘/api/get-signin-status’的HTTP响应时都会更新登录状态,并且组件可以订阅状态更新.

组件中的订阅将如下所示:

// header.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth/services/auth.service';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
  signInStatus: any;

  constructor(private authService: AuthService) {
    this.authService.getSignInStatusObserver().subscribe((response: any) => {
      // That block of code will run every time the signInStatus updates
      console.log(response);

      this.signInStatus = response;
      if (response) {
        console.log('Sign in status success:', response.response);
      } else {
        console.log('Sign in status is null');
      }
    });
  }

  ngOnInit(): void {}
}

HeaderComponent的构造函数中,我们注入的是AuthService而不是InterceptorService.我们订阅getSignInStatusObserver(),这是AuthService中的一个方法,它返回登录状态的Observable.当更新登录状态时,将使用新状态作为参数调用订阅函数.

我们将登录状态存储在signInStatus属性中,并将其记录到控制台.如果响应存在,我们还会记录一条成功消息,否则会记录一条指示登录状态为空的消息.这反映了原始代码的行为.

Node.js相关问答推荐

在我的Next.js应用程序中没有正确设置Process.env.NODE_ENV

MongoDB-$Lookup未获得适当的结果

NPM:无法导入本码模块

如何从基于JSON的HTML/SCSS模板生成PDF?

在对象的嵌套数组中使用$lookup,创建多个记录作为响应,mongodb

Webpack:如何避免导出函数的重命名?

NPM如何管理node_modules传递依赖?

NodeJS `request` 库无法通过 multipart-form-data 将文件发布到 dotnet 服务器

错误 node :错误:绑定消息提供 16 个参数,但准备语句需要 15 个

只要我在后端正确验证所有内容,就可以将这些数据存储在本地存储中吗?

用户通过 oauth2 twitter 授权或通过Passport discord后如何重定向到 React/Vue 路由?

后端位于 Docker 容器中时的 SvelteKit SSR fetch()

如何更新 MongoDB 中对象数组中的键?

aws cdk 2.0 init 应用程序无法构建更漂亮的问题,这来自 jest-snapshot

如何使用 UglifyJS 缩小文件夹中的多个 Javascript 文件?

如何在不全局安装的情况下在 Node REPL 中要求 node 模块?

Node.js `--nolazy` 标志是什么意思?

npm install packagename --save-dev 不更新 package.json

mongoose 填充与对象嵌套

NodeJS:如何调试检测到 EventEmitter 内存泄漏.添加了 11 个侦听器