Server端采用的是JWT登录机制,即:登录校验成功后,Server端生成并返回一个token,客户端需要缓存此token,在后续的API访问中携带此token以进行鉴权。因此,在Server端,session是无状态的,也就是并没有”缓存“此token,也无法和当前访问用户相关联,以获取登录状态和用户信息。

因此,问题便出在此,因为SSR方式下的HTTP请求是同步方式,在请求时,尚无法访问Storage(localStorage、sessionStorage),也就无法附带token鉴权信息。

但既然要显示登录状态、用户信息,且需要限制登录阅读,那必然需要能获取到session状态。

如何才能解决SSR的session状态持久化问题呢?

直觉自然是在Server端缓存token,而首要选择便是Redis,然而……状态、数据的维护过于繁琐,且容易出问题。有没有更优雅的方式呢?

为避免人为维护token状态,首先想到的便是将tokensession关联,将其缓存至seesion中,并将session cookie失效时间和token失效时间保持一致。

typescriptCopy code
  • 1
  • 2
req.session['token'] = token; req.session.save();
req.session['token'] = token; req.session.save();

然而,本地测试通过后,在生产环境却依然无法获得登录状态。

查看调试日志后,每个API访问的session ID都不一样,且请求头request.headers.cookie为空,而异步AJAX方式访问的API,却能正常获取sessioncookietoken

因此,原因也明了了:

同步请求(server-side)的请求头中缺少Cookie字段,也就无法携带session ID,导致无法获取session并识别访客信息。

因此,解决方案也很清晰了:

Cookie信息attach到server-side端的请求头中。

最先想到的方案自然是通过拦截器方式统一处理:

typescriptCopy code
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
if (this.request && this.request.headers.cookie) { httpRequest = httpRequest.clone({ setHeaders: { Cookie: this.request.headers.cookie } }); }
if (this.request && this.request.headers.cookie) { httpRequest = httpRequest.clone({ setHeaders: { Cookie: this.request.headers.cookie } }); }

其它的各种以options等方式追加cookie信息的方案都难免觉得啰嗦、丑陋。

但问题又来了,访问时,会报如下错误:

Refused to set unsafe header 'Cookie'

何解呢?

只能求助于万能的Google。终于,在某个角落找到以下解决方案:

typescriptCopy code
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
import { XhrFactory } from '@angular/common'; import * as XHR2 from 'xhr2'; export class ServerXhr implements XhrFactory { build(): XMLHttpRequest { XHR2.prototype._restrictedHeaders.cookie = false; return new XHR2.XMLHttpRequest(); } }
import { XhrFactory } from '@angular/common'; import * as XHR2 from 'xhr2'; export class ServerXhr implements XhrFactory { build(): XMLHttpRequest { XHR2.prototype._restrictedHeaders.cookie = false; return new XHR2.XMLHttpRequest(); } }

修改AppServerModule

typescriptCopy code
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
@NgModule({ ... providers: [{ provide: XhrFactory, useClass: ServerXhr }], ... }) export class AppServerModule { }
@NgModule({ ... providers: [{ provide: XhrFactory, useClass: ServerXhr }], ... }) export class AppServerModule { }

以上代码编译时会报类型错误,且并不存在@types/xhr2包,因此,需要取消类型检查:

typescriptCopy code
  • 1
  • 2
// @ts-ignore import * as XHR2 from 'xhr2';
// @ts-ignore import * as XHR2 from 'xhr2';

改完,发布,测试……完美!

但……好事多磨,以上方案依然有问题——不知为何,修改拦截器追加Cookie请求头后,会导致cdn域名下的静态资源访问出现跨域问题。

只能继续折腾Nginx了……┓( ´∀` )┏