1. 问题背景
在开发一个请假销假管理系统时,我采用前后端分离的技术方案,具体如下图所示:
其中,后台登录的Rest API为http://localhost:9090/token/user/login.
前端angular 2中利用Http请求Rest API的get/post/put/delete封装在api.service.ts中,具体代码如下:
export class ApiService {
private expiredTime: number = 30 * 60 * 1000; // token时间30分钟
constructor(private http: Http,
private jwtService: JwtService) {
}
private setHeader(): Headers {
let headersConfig = {
'Content-Type': 'application/json',
'Accept': 'application/json'
};
if (this.jwtService.getToken()) {
headersConfig['Authorization'] = `Token ${this.jwtService.getToken()}`;
headersConfig['token'] = this.jwtService.getToken();
headersConfig['X-Auth-Token'] = this.jwtService.getToken();
}
return new Headers(headersConfig);
}
private formatErrors(error: any) {
return Observable.throw(error.json());
}
get(path: string, params: URLSearchParams = new URLSearchParams()): Observable<any> {
return this.http.get(`${environment.api_url}${path}`, {headers: this.setHeader(), search: params})
.catch(this.formatErrors)
.map((res: Response) => res.json());
}
put(path: string, body: Object = {}): Observable<any> {
return this.http.put(`${environment.api_url}${path}`,
JSON.stringify(body),
{headers: this.setHeader()})
.catch(this.formatErrors)
.map((res: Response) => res.json());
}
post(path: string, body: Object = {}): Observable<any> {
return this.http.post(
`${environment.api_url}${path}`,
JSON.stringify(body),
{headers: this.setHeader()})
.catch(this.formatErrors)
.map((res: Response) => res.json());
}
delete(path: string): Observable<any> {
return this.http.delete(
`${environment.api_url}${path}`,
{headers: this.setHeader()}
)
.catch(this.formatErrors)
.map((res: Response) => res.json());
}
}
其中setHeader()
是用来设置请求头的方法。
2. 问题描述
在开发环境下运行angular 2工程时并没有出现任何错误。但是部署在生产环境下时,经常出现首次登录成功后,过段时间token过期时重新登录,会发现http在CORS跨域请求
时首先发送OPTIONS
探针请求返回http状态码200,此时后续的post登录请求
却无法正常进行,查看chrome的Console后发现异常错误如下:
Failed to load http://localhost:9090/token/user/login:
Request header field X-XSRF-TOKEN is not allowed by Access-Control-Allow-Headers in preflight response.
3. 原因分析
在Chrome浏览器中,大部分情况下默认Chrome Cookie保存在X-XSRF-TOKEN
字段中,Chrome在发送OPTIONS
探针请求时会自动将Access-Control-Request-Headers: x-xsrf-token
Http Header添加到OPTIONS请求中,而java后台的HTTP CORS过滤器中尚未把 X-XSRF-TOKEN
添加到 Access-Control-Allow-Headers
中,因此后续的POST登录请求被拦截而无法被处理。
4. 解决方法
(1) Angular 2.0 Client
在api.service.ts中的类ApiService中,将
Access-Control-Request-Headers: x-xsrf-token
添加到Headers中,即修改setHeader()
函数为:
private setHeader(): Headers {
let headersConfig = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Headers': 'Content-Type, X-XSRF-TOKEN'
};
if (this.jwtService.getToken()) {
headersConfig['Authorization'] = `Token ${this.jwtService.getToken()}`;
headersConfig['token'] = this.jwtService.getToken();
headersConfig['X-Auth-Token'] = this.jwtService.getToken();
}
return new Headers(headersConfig);
}
(2) Java Server:
在Access-Control-Allow-Headers
中添加HeaderX-XSRF-TOKEN
。
在spring-servlet.xml配置文件中添加以下代码:
<mvc:cors>
<mvc:mapping path="/**"
allow-credentials="true"
allowed-headers="Content-Type, Accept, token, Authorization, X-Auth-Token, X-XSRF-TOKEN, X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Access-Control-Allow-Headers"
allowed-methods="POST, GET, PUT, OPTIONS, DELETE"/>
</mvc:cors>
或者, 修改CORS拦截器CORSIntercepter【在spring-servlet.xml中配置拦截器】的代码为:
public class CORSIntercepter extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with," +
"Content-Type,X-Amz-Date,Authorization,X-Api-Key," +
"X-Amz-Security-Token,X-XSRF-TOKEN,Access-Control-Allow-Headers");
return false;
}
}
又或者, 修改CORS过滤器CORSFilter【在web.xml中配置过滤器】的代码为:
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by bob on 2017/2/5.
*/
public class CORSFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
System.out.println("Filtering on...........................................................");
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, token, Authorization, " +
"X-Auth-Token,X-XSRF-TOKEN,Access-Control-Allow-Headers");
chain.doFilter(req, res);
}
@Override
public void destroy() {
}
}
Bob
20171010