如何连接到需要Spring Security Forms身份验证(Java)的远程URL?

时间:2011-02-23 22:58:11

标签: java apache forms-authentication spring-security

我进行了搜索和搜索,但似乎无法找到看似简单的身份验证方案的答案。

我们有一个现有的Java Web应用程序,它使用Spring提供的基于表单的授权。我们正试图通过我们的门户网站访问此应用程序,而不会要求用户输入其凭据(SSO)。

门户网站有一个凭证保险库,我们可以成功访问服务器端远程Web应用程序的机密。我们正在使用Apache的HTTP组件实用程序将登录请求发布到j_spring_security_check并成功进行身份验证。对此帖子的响应会将302重定向发送回应用程序主页,并设置一个具有会话ID的cookie。

现在我们必须以某种方式将这个经过身份验证的会话发送回浏览器,这就是我们遇到麻烦的地方。简单地将浏览器重定向到主页不起作用 - 它将我们重定向到登录页面。完全按照服务器端收到的方式将所有响应标头转发回浏览器也不起作用 - 仍返回登录页面。

那么,我们如何对服务器端进行身份验证,并且仍然可以加载目标页面客户端?

我对此比较陌生,所以如果这是一个愚蠢的问题我会道歉。任何有关替代方法的帮助或建议都表示赞赏。

注意:


HttpComponent客户端代码:

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();
    }

从服务器端成功登录返回的标头:

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

来自客户端请求和响应的标题:
请求:

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  

响应(重定向到登录):

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

作为测试,我们写了一些似乎有效的黑客攻击,但是太不安全了,不可行:

  • 在jsp上嵌入一个表单,它将登录凭据直接发布到远程站点的j_spring_security_check。
  • 编写了一个servlet方法,以从Vault中检索凭据。
  • 将客户端的凭据填写到隐藏的表单字段中,然后通过javascript提交表单。

2 个答案:

答案 0 :(得分:1)

有点难以理解您的应用程序正在尝试做什么,但我最好的猜测是您的“门户”位于用户的浏览器和应用程序之间,并且您正在尝试使用应用程序的一些存储凭据代表用户进行身份验证。

您需要注意/处理两件事。

来自应用程序的响应将包含某种SetCookie标头。需要谨慎处理cookie。根据您使用的安全模型:

  • 它们可以保存在门户中,并用于将来对应用程序的请求。
  • 他们可以转发到用户的浏览器。门户网站还需要在将来对应用程序的请求中传递cookie。 (需要谨慎处理此方法以处理会话令牌泄漏的可能问题。)

另外,请注意,当登录成功时,SpringSecurity 会更改会话cookie。如果您没有捕获新会话cookie并在对应用程序的后续请求中使用它们,则不会对这些请求进行身份验证。

应用程序的登录机制显然是在登录后尝试将您(门户网站)重定向到“默认”位置,这是不合适的。有两个简单的解决方法:

  • 让门户检测到最终的重定向并将其视为您已成功登录的指示。然后让门户网站使用新cookie重复请求您最初从应用程序请求的页面(请参阅上文)。

  • IIRC,您可以添加一个额外的参数给j_spring_security_check请求,该请求告诉应用程序成功登录后返回的位置。我记不起细节......


  

我认为将来自RA的setCookie响应标头转发到门户网站对浏览器的响应将是将cookie /会话ID传输到用户的新浏览器窗口所需的全部内容。这不正确吗?

这将导致浏览器为门户网站上下文设置RA的cookie。除非RA和门户网站在cookie的“范围”中(因为想要更好的词),否则这将无效。

  

问题是,如何在门户网站上/通过门户网站显示?我是否只需复制所有内容并相应地映射所有相关链接?并且,正如您所述,继续通过门户代理对应用程序的所有请求,每次都传递cookie?有没有办法避免复制/修改标记?

需要按摩标记。但究竟需要按摩的方法并不完全清楚。我认为您需要映射相对链接,以便在用户的浏览器看到它们时指向门户。然后,安排门户网站使用适当的cookie将请求转发给RA。

可用于处理相对链接的一个工具是HTML <base> element。事实上,这可能比绝对链接更容易处理......如果你通过门户网站映射一切。

但要注意,在这个过程中会有各种各样的事情引起悲伤。例如,你必须要注意“相同的来源”限制,以及带有RA嵌入式URL的javascript。

答案 1 :(得分:1)

如果有人感兴趣,这就是一切都结果。

一旦我们意识到设置外国cookie的问题,我们决定我们有几个选择:

  1. 代理 - 通过门户网站隧道到 远程应用程序,使用门户网站 作为代理人。这个选项最多 逻辑上直截了当,但它 有如上所述的并发症 (即你必须修改每个 请求和每个响应 - 添加 必要的cookie和标记)。 结果证明这种方法很痛苦 对我们来说,与我们无关 使用IBM WebSphere Portal 7。
  2. 第三方SSO解决方案 - 使用CAS或Tivoli或其他一些企业解决方案。这是我们的 理想的最终解决方案,但确实如此 仍在研究确定 与我们的环境兼容。
  3. Cookie Monster - 我们的临时解决方案,以便 让IBM门户网站脱颖而出 中间人,当时要小部署 同样的新远程应用程序 服务器作为我们的目标应用程序 接受JSON格式的cookie 将它吐回到浏览器中 302重定向响应。
  4. cookie怪物解决方案的工作原理如下:     用户点击中的链接     门户网站,我们的portlet将在内部     查找用户的凭据,     对远程进行身份验证     申请,并返回     身份验证cookie /令牌。我们     转换(以及     目标网址)到JSON并返回     它到浏览器。浏览器然后     将此JSON发布到远程cookie     应用程序在新窗口中。该     cookie被重组并放置     在响应中与302一起     和目标位置。瞧,瞧     页面重定向到应用程序     主页和用户已登录.Yay!

    使用IBM WebSphere Portal的任何人的一些注意事项:

    • 我们通过处理身份验证 资源服务portlet。
    • 确保资源服务portlet的响应没有被缓存(我们使缓存立即过期,因为我们无法返回no-cache)
    • 确保在进行ajax调用之前ping门户网站,因为会话可能已过期。

    我确信还有其他更优雅的解决方案,但这对我们有用,直到我们启动并运行CAS / Tivoli。