一. 用户签到的难点
为什么这么简单的东西也要单独拿出来讲一讲?主要还是跨域的问题。
如果不考虑跨域的话,只要给click绑定以下事件就行了:
onSignin() {
this.memberSerice.signIn().subscribe(
res => console.log(res),
error => console.log(error)
);
}
但是大家都知道,http是无状态的,如果人家没给你一个标识符,人家就无法认出你。这就有了cookie,他会主动向你发一个cookie,然后你携带这这个cookie去访问服务器,诶,他就能认出你来了。
然而我们本次项目开启的服务是http://localhost:3000, 而angular项目跑在http://localhost:4200。这就会引发跨域问题,默认情况下你是无法携带cookie访问我们的服务的。
当然,我们也能在不修改以上代码的情况下,通过添加http-interceptor来发送跨域的cookie。
二. 从同源策略讲起
什么才能叫作同源?一定要同时满足以下三点:
- 策略相同:不能是https和http
- 域名相同:不能是baidu.com和google.com
- 端口相同:不能是80到8080
如果有一个条件不满足,那就发生了跨域现象。
三. CORS
CORS就是跨域资源共享。按理来讲,如果要跨域访问资源,就会有很多限制,比如无法访问localStorage, 无法收到ajax的response等等。但是只要正确设置了CORS头,就能跨域访问资源了。
以最简单的GET请求为例(之所以称它为简单,是因为他无法触发浏览器的preflight请求),如果服务器的response设置了Access-Control-Allow-Origin: *。那么客户端就能跨域访问资源了。
四. 浏览器的preflight请求
对于一些复杂的请求(比如有一些side effect的请求),会触发浏览器的preflight请求。
说得这么绕,到底是咋回事呢?简单点就是说,浏览器先发一个request帮你问问服务器能不能跨域,得到“能”的答复后就让你发送,否则就阻止你的发送。
什么才叫复杂的请求呢?请见:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
五. 携带跨域cookie访问服务器
cookie这个东西和localStoarge不一样,你还是能够设置CORS请求头来访问服务器的。这里我们可以通过设置withCredentials为true来允许跨域。
同时,服务端也要设置好各种响应头才能让我们的浏览器相应跨域行为:
// CORS & Preflight request
app.use((req, res, next) => {
if(req.path !== '/' && !req.path.includes('.')){
res.set({
'Access-Control-Allow-Credentials': true,
// 对于携带cookie的请求来说,origin是不能为*的
'Access-Control-Allow-Origin': req.headers.origin || '*',
'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS',
'Content-Type': 'application/json; charset=utf-8'
})
}
req.method === 'OPTIONS' ? res.status(204).end() : next()
})
六. http-intercetor
在angular中我们可以通过http-interceptor来拦截我们的http请求,如果要给请求添加withCredential属性,可以这么做:
@Injectable()
export class CommonInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// 允许跨域的cookie
return next.handle(req.clone({
withCredentials: true
})).pipe(catchError(this.handleError));
}
}
然后我们创建一个index.ts导出一下:
export const httpInterceptorProvides = [
{ provide: HTTP_INTERCEPTORS, useClass: CommonInterceptor, multi: true }
];
就可以带着cookie访问服务器了。