1. 問題背景
在開發(fā)一個請假銷假管理系統(tǒng)時满钟,我采用前后端分離的技術(shù)方案廓奕,具體如下圖所示:
其中,后臺登錄的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()
是用來設(shè)置請求頭的方法。
2. 問題描述
在開發(fā)環(huán)境下運行angular 2工程時并沒有出現(xiàn)任何錯誤添谊。但是部署在生產(chǎn)環(huán)境下時,經(jīng)常出現(xiàn)首次登錄成功后察迟,過段時間token過期時重新登錄斩狱,會發(fā)現(xiàn)http在CORS跨域請求
時首先發(fā)送OPTIONS
探針請求返回http狀態(tài)碼200,此時后續(xù)的post登錄請求
卻無法正常進行扎瓶,查看chrome的Console后發(fā)現(xiàn)異常錯誤如下:
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在發(fā)送OPTIONS
探針請求時會自動將Access-Control-Request-Headers: x-xsrf-token
Http Header添加到OPTIONS請求中概荷,而java后臺的HTTP CORS過濾器中尚未把 X-XSRF-TOKEN
添加到 Access-Control-Allow-Headers
中秕岛,因此后續(xù)的POST登錄請求被攔截而無法被處理。
4. 解決方法
(1) Angular 2.0 Client
在api.service.ts中的類ApiService中误证,將
Access-Control-Request-Headers: x-xsrf-token
添加到Headers中继薛,即修改setHeader()
函數(shù)為:
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