如何从另一个来源访问 url?即使存在 Access-Control-Allow-* 标头,预检请求也会返回 401

Posted

技术标签:

【中文标题】如何从另一个来源访问 url?即使存在 Access-Control-Allow-* 标头,预检请求也会返回 401【英文标题】:How to access url from another origin? Preflight request returns 401 even if Access-Control-Allow-* headers is present 【发布时间】:2017-01-13 12:48:08 【问题描述】:

我在具有基本身份验证的嵌入式码头中运行简单的 Jersey REST 端点,并尝试通过来自其他 Web 应用程序的 ajax 调用来访问它。但是即使存在 Access-Control-Allow-* 标头,我也会在预检请求中得到 401 Unauthorized

服务器代码:

public final class RESTServerStarter 
private static final int WEB_SERVER_PORT = 8888;

public static void main(String[] args) throws Exception 
    final ServletHolder sh = new ServletHolder(ServletContainer.class);
    sh.setInitParameter(ServletContainer.RESOURCE_CONFIG_CLASS, PackagesResourceConfig.class.getCanonicalName());
    sh.setInitParameter(PackagesResourceConfig.PROPERTY_PACKAGES, HelloRESTService.class.getPackage().getName());

    final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
    context.setSecurityHandler(basicAuth("admin", "adminpwd", "Private!"));
    context.setContextPath("/rest");
    context.addServlet(sh, "/*");
    context.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));

    final Server server = new Server();

    final ServerConnector connector = new ServerConnector(server);
    connector.setHost("172.18.133.20");
    connector.setPort(WEB_SERVER_PORT);
    server.addConnector(connector);

    server.setHandler(context);

    try 
        server.start();
        server.join();
     finally 
        server.destroy();
    



private static final SecurityHandler basicAuth(String username, String password, String realm) 

    HashLoginService hashLoginService = new HashLoginService();
    hashLoginService.putUser(username, Credential.getCredential(password), new String[]"user");
    hashLoginService.setName(realm);

    Constraint constraint = new Constraint();
    constraint.setName(Constraint.__BASIC_AUTH);
    constraint.setRoles(new String[]"user");
    constraint.setAuthenticate(true);

    ConstraintMapping cm = new ConstraintMapping();
    cm.setConstraint(constraint);
    cm.setPathSpec("/*");

    ConstraintSecurityHandler csh = new ConstraintSecurityHandler();
    csh.setAuthenticator(new BasicAuthenticator());
    csh.setRealmName("myrealm");
    csh.addConstraintMapping(cm);
    csh.setLoginService(hashLoginService);

    return csh;



public static final class MyFilter implements Filter 
    public void init(FilterConfig filterConfig) 
        //nothing to init
    

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
        final HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.addHeader("Access-Control-Allow-Origin", "http://172.18.133.20:" + RESTClientStarter.WEB_SERVER_PORT);
        httpResponse.addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, DELETE, PUT");
        httpResponse.addHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.addHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
        chain.doFilter(request, response);
    

    public void destroy() 
        //nothing to destroy
    


@Path("/test")
public static class HelloRESTService 

    @GET
    @Path("hello")
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() 
        return "hello";
    

客户端代码:

$(function()
    $.ajax(
        url : "http://172.18.133.20:8888/rest/test/hello",
        type : "GET",
        timeout : 120000,
        async : true,
        xhrFields: 
            withCredentials: true
        ,
        crossDomain: true,
        headers: 
            "Authorization": "Basic " + btoa("admin:adminpwd")
        ,
        error: function(xhr, status, error) 
            $("#container").text(error);
        ,
        success: function(xhr) 
            $("#container").text(xhr);
        
    );
);

客户端代码在http://172.18.133.20:9999上运行

这里是完整的来源:

https://bitbucket.org/dmitry_apanasevich/cors/src

你能告诉我有什么问题吗?

感谢您的提前!

【问题讨论】:

【参考方案1】:

我已经解决了从 SecurityMapping 中排除 OPTIONS 请求的问题。只需添加一行:

...
ConstraintMapping cm = new ConstraintMapping();
cm.setMethod("GET"); //new line
cm.setConstraint(constraint);
cm.setPathSpec("/*");
...

【讨论】:

【参考方案2】:

我认为你使用 httpResponse.addHeader("Access-Control-Allow-Origin", "http://172.18.133.20:" + RESTClientStarter.WEB_SERVER_PORT); 并且您的其余应用程序的 WEB_SERVER_PORT 是 9999,因此 httpResponse 允许 http://172.18.133.20:9999。 但是你在 8888 端口调用它是不允许的。

【讨论】:

没有。 REST 应用在 8888 端口上运行,客户端应用在 9999 端口上运行 所以,你应该尝试 httpResponse.addHeader("Access-Control-Allow-Origin", "172.18.133.20"); 而不是 httpResponse.addHeader("Access-Control-Allow-Origin", " 172.18.133.20:" + RESTClientStarter.WEB_SERVER_PORT); 为什么? ajax reuest 的来源是172.18.133.20:9999。我已经尝试过你的决定,但它不起作用。 因为如果你写 172.18.133.20,你的服务器是允许来自这个 ip 的所有请求,或者你可以写 * 来允许来自任何 ip/域的所有请求。您应该将 X-PINGOTHER 添加到 Access-Control-Allow-Headers 中,因为服务器应该允许它可以运行的所有方法。

以上是关于如何从另一个来源访问 url?即使存在 Access-Control-Allow-* 标头,预检请求也会返回 401的主要内容,如果未能解决你的问题,请参考以下文章

如何修复错误“加载资源失败:访问控制允许来源不能包含多个来源”

如何在 Nuxt 中从另一个访问一个 Vuex 状态?

如何从另一个类访问函数?

Laravel 从另一个来源获取值

通过受限访问 URL 从另一个站点访问一个站点的问题

从另一个线程访问用户会话对象,如何实现?