Add CORS headers to response of j_security_check
Asked Answered
A

4

5

I'm building a REST API with jax-rs and WildFly 10. Some of the endpoints are secured. I'm using FORM based authentication.

In my javascript code, I check the response of the AJAX request, and if it is set to 401 Unauthorized, I then present a login form to the user. When he fills it in, I POST the details to j_security_check.

Running on localhost this all works fine, but when the webserver and the REST server are on different machines, the browser denies the AJAX request due to cross-origin issues.

I understand CORS, so I added a CORS filter to my REST server that sets CORS headers for the GUI server. It all works fine, except for one small, but important detail: after the login has succeeded, the CORS filter does not fire for the j_security_check response. No CORS headers are added and the browser can not read the response.

Apart from this one detail I have the whole setup working exactly like I want it.... But I have been struggling with this detail all night and I just can't get it to work.

I understand there are issues with trying to filter j_security_check, but I know of no other ways to add CORS headers... So my question is:

How do I add CORS headers to the response for j_security_check?

Aeroneurosis answered 5/2, 2016 at 6:49 Comment(2)
Hi! I’ve faced exactly the same problem while implementing ContainerResponseFilter. I’m able to add all the needed Access-Control-Allow-XXX headers to any response on the server side. But response to the j_security_check request is an exception. Have you managed to find the solution? Thank in advance:).Gree
Yeah... sort of. Not really happy with it but it sort of works. I'll post an answer.Aeroneurosis
S
4

Configuring undertow subsystem in standalone.xml/domain.xml file solved this problem for me. Filters configured there handle all the requests including j_security_check one.

<subsystem xmlns="urn:jboss:domain:undertow:3.0">
            <buffer-cache name="default"/>
            <server name="default-server">
                <http-listener name="default" redirect-socket="https" socket-binding="http"/>
                <host name="default-host" alias="localhost">
                    <location name="/" handler="welcome-content"/>
                    <filter-ref name="server-header"/>
                    <filter-ref name="x-powered-by-header"/>
                    <!--CORS headers -->
                    <filter-ref name="Access-Control-Allow-Origin"/>
                    <filter-ref name="Access-Control-Allow-Methods"/>
                    <filter-ref name="Access-Control-Allow-Headers"/>
                    <filter-ref name="Access-Control-Allow-Credentials"/>
                    <filter-ref name="Access-Control-Max-Age"/>
                </host>
            </server>
            <servlet-container name="default">
                <jsp-config/>
                <websockets/>
            </servlet-container>
            <handlers>
                <file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
            </handlers>
            <filters>
                <response-header name="server-header" header-value="WildFly/10" header-name="Server"/>
                <response-header name="x-powered-by-header" header-value="Undertow/1" header-name="X-Powered-By"/>
                <!-- CORS headers -->
                <response-header name="Access-Control-Allow-Origin" header-name="Access-Control-Allow-Origin" header-value="*"/>
                <response-header name="Access-Control-Allow-Methods" header-name="Access-Control-Allow-Methods" header-value="OPTIONS, GET, POST, PUT, DELETE"/>
                <response-header name="Access-Control-Allow-Headers" header-name="Access-Control-Allow-Headers" header-value="accept, authorization, content-type, x-requested-with"/>
                <response-header name="Access-Control-Allow-Credentials" header-name="Access-Control-Allow-Credentials" header-value="true"/>
                <response-header name="Access-Control-Max-Age" header-name="Access-Control-Max-Age" header-value="60"/>
            </filters>
        </subsystem>

Of course you'd better replace "*" wildcard by your GUI server's url in the Access-Control-Allow-Origin header's value attribute.

Sandalwood answered 28/8, 2016 at 8:56 Comment(5)
What a great solution:)Gree
Thanks for your help. Unfortunately, multiple domains are not allowed as cors origin value. Neither is *. And you can't have multiple cors origin headers in one response... This effectively means you cannot use static config like this if you have multiple domains (as most sites have that support both naked domain urls and ones starting with www.)Aeroneurosis
@Sandalwood Well it's one and a half year later and in that time I did a lot of research on this subject and it seems you are 100% right. The way you describe here with an Undertow filter seems to be the only way to filter these requests. Servlet filters and JAX-RS filters do not get called for these requests.Aeroneurosis
To solve the problem with the value for Access-Control-Allow-Origin having to be dynamic, I created an Undertow filter specifically for adding CORS headers. I will add an answer about it.Aeroneurosis
@nfedorov, I still get "Reason: CORS preflight channel did not succeed". What do I need to modify to make it work?Homogony
A
2

Handle the login yourself.

Instead of posting to j_security_check, post to /auth/login or something like that and handle the login. Something like this:

@POST
@Path("login")
@PermitAll
public Response postLogin() {
    String user = request.getParameter("j_username");
    String password = request.getParameter("j_password");
    StringBuffer buf = request.getRequestURL();
    URI redir = null; 
    try {redir = new URI(buf.substring(0, buf.lastIndexOf("/login")) + "/session");} 
    catch (URISyntaxException e) {}
    try {
        request.login(user, password);
        return Response.seeOther(redir).build();
    } catch (ServletException e) {
        if (e.getMessage() != null && e.getMessage().equals("UT010030: User already logged in")) {
            Response.seeOther(redir).build();
        }
        return Response.status(Status.FORBIDDEN).build();
    }
}
Aeroneurosis answered 27/8, 2016 at 23:0 Comment(2)
I'm not accepting this answer, because it is actually a workaround... then again it may be the only way. Who knows? Still hoping for more input from the SO community.Aeroneurosis
undertow-cors-filter is probably the best way to handle this atm.Aeroneurosis
A
1

Based on nfedorov's answer, I came to the conclusion that he is right and that an Undertow filter is the only type of filter that can operate on container responses. So if we want to add CORS headers to these responses, we need an Undertow filter. So I wrote one:

undertow-cors-filter

Download the .zip file and unzip it in your Wildfly root folder. Then add a filter configuration to standalone.xml:

<filters>
  <filter name="undertow-cors-filter" class-name="com.stijndewitt.undertow.cors.Filter" module="com.stijndewitt.undertow.cors">

  </filter>
</filters>

Add a filter-ref to the host element (still in standalone.xml):

<host name="default-host" alias="localhost">
  <filter-ref name="undertow-cors-filter" />
</host>

This will add CORS headers allowing all origins access to all responses this server emits.

Add params to the filter definition to configure the behavior. For example, if you want the filter to apply only to responses for requests to URLS starting with /api and you only want to allow example.com and example.org access, you could use a configuration similar to this:

<filters>
  <filter name="undertow-cors-filter" class-name="com.stijndewitt.undertow.cors.Filter" module="com.stijndewitt.undertow.cors">
    <param name="urlPattern" value="^http(s)?://([^/]+)(:([^/]+))?(/([^/])+)?/api(/.*)?$" />
    <param name="policyClass" value="com.stijndewitt.undertow.cors.AllowMatching" />
    <param name="policyParam" value="^http(s)?://(www\.)?example\.(com|org)$" />   
  </filter>
</filters>

There are 3 policy classes available, AllowAll, AllowMatching and Whitelist and you can write your own custom policies if needed.

Aeroneurosis answered 3/2, 2018 at 11:42 Comment(0)
L
0

As mentioned in the earlier answer by nfedorov, configuring undertow subsystem in standalone.xml/domain.xml file would solve this problem. But that suggested solution would only allow either one domain or '*' to be specified in the Access-Control-Allow-Origin header's value attribute. Slightly better solution would be to whitelist one or more domains for accessing the REST APIs, if the APIs could potentially be accessed from more than one domain. We could achieve the whitelisting and matching the requesting domain against the whitelist in Undertow without using any 3rd party Undertow Filter. Undertow Predicates & Exchange Attributes would help achieve this. A regex can be used to define the list of domains to be whitelisted. And the CORS related response header Access-Control-Allow-Origin could be written to the response conditionally by checking the incoming Origin header and matching it against the whitelist using regex matching. All these could be configured in the standalone.xml of Wildfly without any Java coding. The following article explains the same.

https://medium.com/amritatech/a-mechanism-to-enable-cors-filter-with-no-coding-57ef2906e023

Lew answered 17/11, 2021 at 2:47 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Unbelievable

© 2022 - 2024 — McMap. All rights reserved.