Server端采用的是JWT
登录机制,即:登录校验成功后,Server端生成并返回一个token
,客户端需要缓存此token
,在后续的API访问中携带此token
以进行鉴权。因此,在Server端,session
是无状态的,也就是并没有”缓存“此token
,也无法和当前访问用户相关联,以获取登录状态和用户信息。
因此,问题便出在此,因为SSR方式下的HTTP请求是同步方式,在请求时,尚无法访问Storage
(localStorage、sessionStorage),也就无法附带token
鉴权信息。
但既然要显示登录状态、用户信息,且需要限制登录阅读,那必然需要能获取到session
状态。
如何才能解决SSR的session
状态持久化问题呢?
直觉自然是在Server端缓存token
,而首要选择便是Redis
,然而……状态、数据的维护过于繁琐,且容易出问题。有没有更优雅的方式呢?
为避免人为维护token
状态,首先想到的便是将token
和session
关联,将其缓存至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,却能正常获取session
、cookie
、token
。
因此,原因也明了了:
同步请求(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
了……┓( ´∀` )┏