CORSFilter 不适用于 JAX-RS 应用程序

day*_*mer 3 java rest jax-rs wildfly

我在用resteasy-jaxrs - 3.0.9.FINAL。我有两个独立javax.ws.rs.core.Application

@ApplicationPath("oauth")
public class OAuthApplication extends Application {
    final Set<Class<?>> classes = new HashSet<>();

    @Nonnull
    @Override
    public Set<Class<?>> getClasses() {
        classes.add(RegisterResource.class);
        classes.add(TokenResource.class);
        classes.add(HelloResource.class);
        return Collections.unmodifiableSet(classes);
    }
}
Run Code Online (Sandbox Code Playgroud)

对于/oauth基于端点和

@ApplicationPath("rest")
public class RestApplication extends Application {
    final Set<Class<?>> classes = new HashSet<>();

    @Nonnull
    @Override
    public Set<Class<?>> getClasses() {
        classes.add(CategoryResource.class);
        classes.add(CategoryGroupResource.class);
        classes.add(TransactionResource.class);
        classes.add(MonthlySummaryResource.class);
        classes.add(MemberResource.class);
        return Collections.unmodifiableSet(classes);
    }
}
Run Code Online (Sandbox Code Playgroud)

我的过滤器看起来像

@Provider
public class CorsFeature implements Feature {

  @Override
  public boolean configure(FeatureContext featureContext) {
    CorsFilter corsFilter = new CorsFilter();
    corsFilter.getAllowedOrigins().add("*");
    corsFilter.setAllowedMethods("OPTIONS, GET, POST, DELETE, PUT, PATCH");
    featureContext.register(corsFilter);
    return true;
  }
}
Run Code Online (Sandbox Code Playgroud)

我还在我的/rest/*资源上应用了过滤器,看起来像

@WebFilter("/rest/*")
public class AuthTokenValidatorFilter implements Filter {
    private static final String BEARER_HEADER = "BEARER";
    private static final String COLON = ":";
    private static final Pattern PATTERN = Pattern.compile(COLON);

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        final HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;

        if (httpRequest.getHeader(BEARER_HEADER) == null || !isValidAuthToken(httpRequest.getHeader(BEARER_HEADER))) {
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    private static boolean isValidAuthToken(@Nonnull final String header) {
        final String[] tokenParts = PATTERN.split(header);
        if (tokenParts.length != 3) {
            // hash, uuid, timestamp
            return false;
        }

        final int nanoTimeStamp;
        try {
            nanoTimeStamp = Integer.parseInt(tokenParts[2]);
        } catch (final NumberFormatException e) {
            return false;
        }

        final String hashedAuthToken = new UniqueIdGenerator().getHashedAuthToken(tokenParts[1], nanoTimeStamp);
        return hashedAuthToken.equals(tokenParts[0]);
    }

    @Override
    public void destroy() {
    }
}
Run Code Online (Sandbox Code Playgroud)

当我启动应用程序并到达端点时,我看到

 ~ curl -v http://localhost:9090/application/oauth/hello
*   Trying ::1...
* Connected to localhost (::1) port 9090 (#0)
> GET /application/oauth/hello HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow 1
< Server: Wildfly 8
< Content-Type: application/octet-stream
< Content-Length: 5
< Date: Fri, 26 May 2017 19:05:15 GMT
<
* Connection #0 to host localhost left intact
hello%
Run Code Online (Sandbox Code Playgroud)

我没有看到发回的 CORS 标头。我在这里缺少什么?

Pau*_*tha 6

我猜你从这篇文章中得到了你的代码。这个问答的要点是OP希望能够继续使用类路径扫描,即只保留

@ApplicationPath("/api")
public class RestApplication extends Application {
}
Run Code Online (Sandbox Code Playgroud)

当您有这样的空类时,它会触发类路径扫描以和Application注释的类。所有这些类都会自动注册。但是,一旦您在类中重写或,并在其中任何一个中返回非空集,类路径扫描就会自动禁用。@Path@ProvidergetClasses()getSingletons()Application

因此,OP 试图找出如何在CorsFilter不禁用类路径扫描的情况下注册。解决方案是使用Feature带有注释的@Provider. 允许自动@Provider发现Feature和注册。

就您而言,自从禁用它以来,您就没有类路径扫描。因此,您只需要注册Feature或 因为您没有使用类路径扫描,您可以忘记,然后直接在类中Feature注册CorsFilterApplication

@Override
public Set<Object> getSingletons() {
    Set<Object> providers = new HashSet<>();
    CorsFilter corsFilter = new CorsFilter();
    corsFilter.getAllowedOrigins().add("*");
    corsFilter.setAllowedMethods("OPTIONS, GET, POST, DELETE, PUT, PATCH");
    providers.add(corsFilter);
    return providers;
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,即使您正确注册了过滤器,您的 cURL 请求也不会显示 CORS 标头。Origin它期望在请求中看到标头。

更新

另一方面,您需要考虑 servlet 过滤器和 JAX-RS 应用程序的调用顺序。Servlet 过滤器在 JAX-RS 之前调用。当涉及 JAX-RS 级别的 CORS 支持时,这会产生影响。

因此,当客户端发出预检 (CORS) 请求时,将调用 servlet 过滤器。请注意,这只是一个预检请求,因此不会发送任何标头,包括令牌标头。预检只是检查服务器是否允许该请求。此预检发生在真正的请求之前。这就是 CORS 协议的工作原理。

因此,在预检时,响应应包含 CORS 响应标头,但使用 servlet 过滤器时,它不会发送此标头。您只是发送未经授权的回复。所以 CORS 永远不会起作用。

几个解决方案是:

  1. 在 servlet 过滤器级别设置 CORS 支持。您将无法使用 RESTEasy CorsFilter。您只需要自己实现即可。

  2. 您可以使用JAX-RS ContainerRequestFilter来代替在 servlet 过滤器中进行身份验证。这样,身份验证和 CORS 支持就处于同一级别。

如果你选择第二个选项,你基本上会做类似的事情

if (notAuthenticated()) {
    requestContext.abortWith(Response.status(401).build()));
    return;
}
Run Code Online (Sandbox Code Playgroud)