【angular 2踩过的坑】Angular2 : X-XSRF-TOKEN is not allowed by Access-Control-Allow-Headers

1. 问题背景


在开发一个请假销假管理系统时,我采用前后端分离的技术方案,具体如下图所示:

【angular 2踩过的坑】Angular2 : X-XSRF-TOKEN is not allowed by Access-Control-Allow-Headers_第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 {
    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 {
    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 {
    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 {
    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配置文件中添加以下代码:

 
        
    

或者, 修改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

你可能感兴趣的:(【angular 2踩过的坑】Angular2 : X-XSRF-TOKEN is not allowed by Access-Control-Allow-Headers)