5

How do I connect to a remote URL that requires Spring Security (Java) forms auth...

 2 years ago
source link: https://www.codesd.com/item/how-do-i-connect-to-a-remote-url-that-requires-spring-security-java-forms-authentication.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

How do I connect to a remote URL that requires Spring Security (Java) forms authentication?

advertisements

I've searched and searched but can't seem to find the answer to what seems like a straightforward authentication scenario.

We have an existing Java web application that uses form-based authorization provided by Spring. We are attempting to access this application via our portal site without challenging the user to enter their credentials (SSO).

The portal has a credential vault and we can successfully access the secrets for the remote web application on the server side. We are using Apache's HTTP Components utility to post the login request to the j_spring_security_check and are successfully authenticating. The response to this post sends back a 302 redirect to the application home page and sets a cookie with a session id.

Now we have to somehow send this authenticated session back to the browser and this is where we are having trouble. Simply redirecting the browser to the home page doesn't work - it redirects us to the login page. Forwarding all of the response headers back to the browser exactly as received on the server-side doesn't work either - still returned to the login page.

So, how do we authenticate server-side and still be able to load the target page client-side?

I am relatively new to this so I apologize if this is a silly question. Any help or advice regarding an alternative approach is appreciated.

Notes:


HttpComponent Client code:

DefaultHttpClient httpclient = new DefaultHttpClient();
    try {
        // try to get the home page
        HttpGet httpget = new HttpGet("http://<host>/<root>/home.action");
        HttpResponse httpClientResponse = httpclient.execute(httpget);
        HttpEntity entity = httpClientResponse.getEntity();

        // check status and close entity stream
        System.out.println("Login form get: " + httpClientResponse.getStatusLine());
        EntityUtils.consume(entity);

        // check cookies
        System.out.println("Initial set of cookies:");
        List<Cookie> cookies = httpclient.getCookieStore().getCookies();
        printCookies(cookies);

        /***  Login ***/
        HttpPost httppost = new HttpPost("http://<host>/<root>/j_spring_security_check");

        // Prepare post parameters
        List <NameValuePair> nvps = new ArrayList <NameValuePair>();
        nvps.add(new BasicNameValuePair("j_username", getUserFromVault()));
        nvps.add(new BasicNameValuePair("j_password", getPasswordFromVault()));
        httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

        httpClientResponse = httpclient.execute(httppost);

        // copy response headers and determine redirect location
        Header[] allHeaders = httpClientResponse.getAllHeaders();
        System.out.println("Headers: ");
        String location = "";
        for (Header header : allHeaders) {
            System.out.println(header);
            if("location".equalsIgnoreCase(header.getName())) location = header.getValue();
            response.addHeader(header.getName(), header.getValue());
        }

        // check response body
        entity = httpClientResponse.getEntity();
        System.out.println("Response content: " + httpClientResponse.getStatusLine());
        System.out.println(EntityUtils.toString(entity)); // always empty
        EntityUtils.consume(entity);

        // check cookies
        System.out.println("Post logon cookies:");
        cookies = httpclient.getCookieStore().getCookies();
        printCookies(cookies);

        // populate redirect information in response
        System.out.println("Redirecting to: " + locationHeaderValue);
        response.setStatus(httpClientResponse.getStatusLine().getStatusCode()); // 302

        // test if server-side get works for home page at this point (it does)
        httpget = new HttpGet(location);
        httpClientResponse = httpclient.execute(httpget);
        entity = httpClientResponse.getEntity();

        // print response body (all home content is loaded)
        System.out.println("home get: " + httpClientResponse.getStatusLine());
        System.out.println("Response content: " + httpClientResponse.getStatusLine());
        System.out.println(EntityUtils.toString(entity));
        EntityUtils.consume(entity);

    } finally {
        httpclient.getConnectionManager().shutdown();
    }


Headers returned from the successful login on the server side:

HTTP/1.1 302 Found
Date: Wed, 23 Feb 2011 22:09:03 GMT
Server: Apache/2.2.3 (CentOS)
Set-Cookie: JSESSIONID=6F98B0B9A65BA6AFA0472714A4C816E5; Path=<root>
Location: http://<host>/<root>/home.action
Content-Type: text/plain; charset=UTF-8
Content-Length: 0
Via: 1.1 PPWebFilter.<host>:80 (IronPort-WSA/7.0.0-825)
Connection: keep-alive


Headers from the client side request and response:
Request:

GET /<root>/home.action HTTP/1.1
Host: <host>
Connection: keep-alive
Referer: http://localhost:10039/SCMViewer/TestLoginServlet?launchScm=Launch+SCM+servlet
Accept:application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.98 Safari/534.13
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: JSESSIONID=FC8E823AB1A1545BE8518DB4D097E665

Response (redirect to login):

HTTP/1.1 302 Found
Date: Wed, 23 Feb 2011 22:09:03 GMT
Server: Apache/2.2.3 (CentOS)
Location: http://<host>/<root>/security/login.action
Content-Type: text/plain; charset=UTF-8
Content-Length: 0
Via: 1.1 PPWebFilter.<host>:80 (IronPort-WSA/7.0.0-825)
Connection: keep-alive


As a test, we wrote a bit of a hack that seems to work, but is too insecure to be viable:

  • Embedded a form on the jsp which will post the login credentials directly to the remote site's j_spring_security_check.
  • Wrote a servlet method to retrieve the credentials from the vault.
  • Filled the credentials on the client side into hidden form fields and submitted the form via javascript.

It is a bit hard to understand what your application is trying to do, but my best guess is that your 'portal' sits between the user's browser and the application, and you are trying to use the some stored credentials for the application to authenticate on behalf of the users.

There are two things you need to watch for / deal with.

The responses from the application will contain SetCookie headers of some sort. The cookies need to be handled carefully. Depending on the security model you are using:

  • They could be saved in the portal and used for future requests to the application.
  • They could be relayed to the user's browser. The portal would also need to pass the cookies through in future requests to the application. (This approach needs to be handled carefully to deal with possible issues with session token leakage.)

Also, be aware that SpringSecurity changes the session cookie when login succeeds. If you don't capture the new session cookie and use them in follow on requests to the application, those requests won't be authenticated.

The application's login mechanism is clearly trying to redirect you (the portal) to the "default" place after logging in, and this is inappropriate. There are two simple fixes for this:

  • Have the portal detect the final redirect and treat it as an indication that you've successfully logged in. Then have the portal repeat the request for the page you were originally requesting from the application using the new cookie (see above).

  • IIRC, there's an extra parameter you can add to a j_spring_security_check request that tells the application where to return on successful login. I can't recall the details ...


I thought that forwarding the setCookie response header from the RA into the portal's response to the browser would be all that is needed to transfer the cookie/session id to the user's new browser window. Is that not correct?

That will cause the browser to set the RA's cookie for the portal context. That won't work unless the RA and portal are in the cookie's "scope" (for the want of a better word).

Question is, how do I display this on/through the portal? Do I just have to copy all the content over and map all the relative links accordingly? And, as you state, continue to proxy all requests to the app through the portal, passing the cookie each time? Is there any way to avoid copying/modifying the markup?

You do need to massage the markup. But exactly what massaging is required is not entirely clear. I think you'll need to map the relative links so that when the user's browser sees them they point to the portal. Then, arrange that the portal relays requests to the RA with the appropriate cookies.

One tool that you can use to deal with relative links is the HTML <base> element. In fact, this potentially easier to deal with than absolute links ... if you map everything via the portal.

But beware that there are all sorts of things that can cause grief in this process. For example, you've got to beware of the "same source" restriction, and with javascript with embedded URLs for the RA.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK