启用从 AngularJS 到 Jersey 的 CORS 发布请求

Posted

技术标签:

【中文标题】启用从 AngularJS 到 Jersey 的 CORS 发布请求【英文标题】:Enable CORS Post Request from AngularJS to Jersey 【发布时间】:2014-11-09 17:08:27 【问题描述】:

我正在尝试将 JSON 文档从 AngularJS 应用程序发布到 Jersey REST 服务。请求失败,通知我:

XMLHttpRequest cannot load http://localhost:8080/my.rest.service/api/order/addOrder. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

Jersey REST 发布功能

我已启用(我认为是)相应的标头:Access-Control-Allow-OriginAccess-Control-Allow-Methods 在响应中,如下面的方法所示:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/addOrder")
public Response addOrder(DBObject dbobject) 
    DB db = mongo.getDB("staffing");
    DBCollection col = db.getCollection("orders");
    col.insert(dbobject);
    ObjectId id = (ObjectId)dbobject.get("_id");
    return Response.ok()
            .entity(id)
            .header("Access-Control-Allow-Origin","*")
            .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
            .allow("OPTIONS")
            .build();

Angular JS 控制器

我已经声明了应用程序并使用类似 Stack Overflow 问题中建议的所有设置配置了 $httpProvider:

var staffingApp = angular.module('myApp', ['ngRoute', 'ui.bootstrap']);
myApp.config(['$httpProvider', function ($httpProvider) 
    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
    $httpProvider.defaults.headers.common["Accept"] = "application/json";
    $httpProvider.defaults.headers.common["Content-Type"] = "application/json";
 ]);

我还创建了这个控制器来打开一个模式并处理表单:

    var modalCtrl = function($scope, $modal, $log, $http, $location)           
    $scope.order = 
        activityTitle : null,
        anticipatedAwardDate : null,
        component : null,
        activityGroup : null,
        activityCategory : null,
        activityDescription : null
    ;
    $scope.open = function () 
        var modalInstance = $modal.open(
            templateUrl: 'addOrder.html',
            windowClass: 'modal',
            controller: modalInstanceCtrl,
            resolve: 
                order : function () 
                    return $scope.order;
                    
                
            );
        modalInstance.result.then(function (oid) 
            $log.info("Form Submitted, headed to page...");
            $location.path("/orders/" + oid);
        , function()  
            $log.info("Form Cancelled")
        );
    ;
;

var modalInstanceCtrl = function ($scope, $modalInstance, $log, $http, order) 
    $scope.order = order,
    $scope.ok = function () 
        $log.log('Submitting user info');
        $log.log(order);
        $log.log('And now in JSON....');
        $log.log(JSON.stringify(order));
        $http.post('http://localhost:8080/my.rest.service/api/order/addOrder', JSON.stringify(order)).success(function(data)
            $log.log("here's the data:\n");
            $log.log(data);
            $modalInstance.close(data._id.$oid)
        );
    ;
    $scope.cancel = function () 
        $modalInstance.dismiss('cancel');
    ;      
;
myApp.controller('modalCtrl', modalCtrl);

没用,我试过了:

从响应标头中删除 .allow("OPTIONS")。 从应用程序中删除 $httpProvider 配置 将 $httpProvider 配置更改为调用 myApp.config(function ($httpProvider) ...),传递函数本身而不是数组。

获取请求使用相同的配置:

@GET
@Path("/listall/")
@Produces(MediaType.APPLICATION_JSON)
public Response listAll()
    DB db = mongo.getDB("staffing");
    DBCollection col = db.getCollection("orders");
    List<DBObject> res = col.find().limit(200).toArray();
    return Response.ok()
            .entity(res.toString())
            .header("Access-Control-Allow-Origin","*")
            .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
            .allow("OPTIONS")
            .build();       

这个控制器可以正常工作:

myApp.controller('orderListCtrl', function ($scope, $http)
    $http.get('http://localhost:8080/my.rest.service/api/order/listall').success(function(data) 
        for (var i = 0; i < data.length; i++) 
            if (data[i].description.length > 200) 
                data[i].shortDesc = data[i].description.substring(0,196) + "...";
             else 
                data[i].shortDesc = data[i].description;
            
        ;
        $scope.orders = data;
    );
);

更新 #1:

我已经在同一个来源的基础上尝试了相同的请求,本质上是为 Angular 应用程序提供服务以及来自 locahost:8080 的 REST 服务。此配置有效,但需要在我上面编辑的代码中进行轻微更改和一些常规清理。

作为 CORS 请求,Post 仍然失败,但是我仍在寻找此配置中缺少的部分。

更新 #2:

我调查了工作请求的标头,因为它们被传递到浏览器,并将它们与非工作请求进行了比较。

有效的 get 请求会返回以下标头及其响应:

不工作的 post 请求返回标头及其响应,但缺少 Access-Control-Allow-Origin 标头:

我认为这现在已经成为一个问题,即在将响应返回给客户端之前将标头从响应中剥离,这将导致浏览器无法请求。

更新#3:

从 Chrome 的 REST 控制台扩展向同一 URL 提交测试 POST 请求会返回相应的响应标头,如下面的屏幕截图所示。

此时,我无法确定是什么删除了 Jersey 和我的 Angular 客户端之间的标头,但我相当有信心这是罪魁祸首。

【问题讨论】:

【参考方案1】:

我在从 angularjs 调用我的 Restful 服务(在 java - Jersey 中实现)时遇到了类似的 CORS 错误。为了解决这个问题,我在响应标头中添加了 Access-Control-Allow-Origin: *。我在下面添加:

response.addHeader("Access-Control-Allow-Origin", "*"); 

欲了解更多信息,您可以查看 - http://enable-cors.org/server.html

CORS 错误通常发生在您的 angularjs 代码(Web 项目)和 webserivce 代码(服务器端项目)位于不同的 IP 和端口号上时。

您的网络服务实现看起来正确。所以只是为了检查,尝试在同一端口(例如 8080)上的 localhost 上运行它们。如果所有代码都正确,它应该在那里工作。

为了单独运行它们,请尝试在 web 服务实现中添加 Access-Control-Allow-Origin: *,如上所示。

希望这会有所帮助。

【讨论】:

您在上面的代码中建议响应对象是什么?这与在响应构建器中包含相同的响应标头在功能上有何不同? 当我在其余 Web 服务响应中仅添加“Access-Control-Allow-Origin”、“*”时,它对我有用。我可以看到您已经将其设置为响应。请尝试在相同的 IP 和端口号上运行您的代码,这样您就可以确定角度代码没有问题。【参考方案2】:

问题原来是在 POST 请求之前使用正确的跨源标头处理在飞行前发送的 OPTIONS 请求的不充分。

我能够通过下载并实施此页面上的 CORS 过滤器来解决此问题:http://software.dzhuvinov.com/cors-filter-installation.html。

如果您遇到类似问题,请按照说明进行测试,以查看您的 OPTIONS 请求不再失败,并且紧随其后的是您的成功请求。

【讨论】:

【参考方案3】:

最好的方法是添加 Jersey 响应过滤器,它将为所有方法添加 CORS 标头。您不必更改您的网络服务实现。

我会解释 Jersey 2.x

1) 首先添加一个ResponseFilter如下图

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;

public class CorsResponseFilter implements ContainerResponseFilter 

@Override
public void filter(ContainerRequestContext requestContext,   ContainerResponseContext responseContext)
    throws IOException 
        responseContext.getHeaders().add("Access-Control-Allow-Origin","*");
        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");

  

2) 然后在 web.xml 中,在 jersey servlet 声明中添加以下内容

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>YOUR PACKAGE.CorsResponseFilter</param-value>
    </init-param>

【讨论】:

【参考方案4】:

实际上,您还有其他不需要过滤器的解决方案。将Access-Control-Allow-* 标头添加到GET 请求中是不够的,您必须创建一个OPTIONS 端点以允许浏览器执行飞行前请求,即:

@OPTIONS
public Response corsMyResource(@HeaderParam("Access-Control-Request-Headers") String requestH) 
    ResponseBuilder rb = Response.ok();

    return buildResponse(rb, requestH);

参考https://kdecherf.com/blog/2011/06/19/java-jersey-a-cors-compliant-rest-api/。

【讨论】:

以上是关于启用从 AngularJS 到 Jersey 的 CORS 发布请求的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring Security、Jersey 和 AngularJS 验证表单

AngularJS + Jersey RESTful 后端:身份验证和授权

AngularJS $http、CORS 和 Jersey 的基本身份验证

前端Angularjs +后端Jersey REST Services +需要设置开发环境

AngularJS 和位于不同域的 Jersey Webservice 之间的通信。无法访问正确的会话

Jax-rs -jersey 跨域请求启用