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

发布于 2024-10-18 14:54:34 字数 5176 浏览 2 评论 0原文

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

我们有一个现有的 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();
    }

服务器端成功登录返回的 headers:

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:
请求:

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

作为测试,我们编写了一些看起来可行的 hack,但太不安全而无法实现:

  • 在 jsp 上嵌入一个表单,它将登录凭据直接发布到远程站点的j_spring_security_check。
  • 编写了一个 servlet 方法来从保管库检索凭证。
  • 将客户端的凭据填充到隐藏的表单字段中,并通过 JavaScript 提交表单。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

债姬 2024-10-25 14:54:34

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

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

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

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

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

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

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

  • IIRC,您可以将一个额外参数添加到 j_spring_security_check 请求中,告诉应用程序成功登录后返回何处。我不记得细节了...


我认为将 RA 的 setCookie 响应标头转发到门户对浏览器的响应中就可以将 cookie/会话 ID 传输到用户的新浏览器窗口。这不正确吗?

这将导致浏览器为门户上下文设置 RA 的 cookie。除非 RA 和门户网站位于 cookie 的“范围”内(需要一个更好的词),否则这不会起作用。

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

确实需要修改标记。但具体需要什么按摩尚不完全清楚。我认为您需要映射相关链接,以便当用户的浏览器看到它们时它们指向门户。然后,安排门户使用适当的 cookie 将请求转发给 RA。

您可以用来处理相对链接的一种工具是 HTML < code>元素。事实上,如果您通过门户映射所有内容,这可能比绝对链接更容易处理。

但要注意,在此过程中,有各种各样的事情可能会导致悲伤。例如,您必须注意“相同来源”限制,以及使用带有 RA 嵌入 URL 的 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.

白衬杉格子梦 2024-10-25 14:54:34

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

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

  1. 代理 - 通过门户隧道到
    远程应用程序,使用门户
    作为代理。这个选项最多
    逻辑上很简单,但是
    有上述并发症
    (即你必须修改每个
    请求和每个响应 - 添加
    cookie 和必要的标记)。
    这个方法结果很痛苦
    对我们来说,与我们不无关系
    使用 IBM WebSphere Portal 7。
  2. 第三方 SSO 解决方案 - 使用 CAS 或 Tivoli 或其他一些企业解决方案。这是我们的
    理想的最终解决方案,但它是
    仍在研究以确定
    与我们的环境的兼容性。
  3. Cookie Monster - 我们的临时解决方案,为了
    让 IBM 门户网站成为
    中间人,是部署一个小
    新的远程应用程序
    服务器作为我们的目标应用程序,只需
    接受 JSON 格式的 cookie 并
    将其吐回浏览器
    302 重定向响应。

Cookie Monster 解决方案的工作原理如下:当
用户点击中的链接
门户,我们的 portlet 将在内部
查找用户的凭据,
向远程进行身份验证
申请,并返回
身份验证 cookie/令牌。我们
转换它(以及
目标 URL)转换为 JSON 并返回
它到浏览器。然后浏览器
将此 JSON 发布到远程 cookie
在新窗口中申请。这
cookie被重构并放置
在响应中连同 302
和目标位置。瞧,
页面重定向到应用程序
主页并且用户已登录。耶!

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

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

我确信还有其他更优雅的解决方案,但在我们启动并运行 CAS/Tivoli 之前,这对我们来说一直有效。

In case anyone is interested, here's how everything turned out.

Once we realized the issue with setting foreign cookies, we decided we had a few options:

  1. Proxy - Tunnel through the portal to the
    remote application, using the portal
    as a proxy. This option is the most
    straightforward logically, but it
    has complications as mentioned above
    (i.e. you have to modify each
    request and each response - adding
    cookies and markup as necessary).
    This method turned out to be a pain
    point for us, not unrelated to our
    use of IBM WebSphere Portal 7.
  2. 3rd party SSO solution - Use CAS or Tivoli or some other enterprise solution. This is our
    ideal final solution, but it is
    still being researched to determine
    compatibility with our environment.
  3. Cookie Monster - Our interim solution, in order to
    get IBM portal out of the way as the
    middle man, was to deploy a small
    new remote application on the same
    server as our target app that simply
    accepts a cookie in JSON format and
    spits it back to the browser in a
    302 redirect response.

The cookie monster solution works as follows: when the
user clicks on the link in the
portal, our portlet will internally
lookup the user's credentials,
authenticate to the remote
application, and return the
authentication cookie/token. We
convert that (as well as the
destination URL) to JSON and return
it to the browser. The browser then
posts this JSON to the remote cookie
application in a new window. The
cookie is reconstituted and placed
in the response along with the 302
and the target location. Voila, the
page redirects to the application
homepage and the user is logged in. Yay!

Some notes for anyone using IBM WebSphere Portal:

  • We handled the authentication via
    resource-serving portlet.
  • Make sure the response from the resource-serving portlet is not cached (we made the cache expire immediately as we could not return no-cache)
  • Make sure you ping the portal before making the ajax call as the session may be expired.

I'm sure there are other, more elegant solutions, but this is working for us until we get CAS/Tivoli up and running.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文