几个web应用程序身份验证协议(如WS-Federation和SAML协议,即所谓的"被动"协议,显然还有ASP.NET Forms身份验证,请参阅此StackOverflow问题,AppEngine,请参阅该GWT错误注释)丢失了原始的"URL片段",即#符号之后的部分。
大致如下:在一个干净的浏览器中(因此没有缓存的信息/cookie/登录信息),我打开URL(1)http://example.com/myapp/somepage?some=parameter#somewhere.这使得浏览器请求(2)http://example.com/myapp/somepage?some=parameter,服务器会将我重定向到我的身份提供者(包括身份验证请求中的URL(2)),最终我会重定向回我的来源,即URL(1):这是服务器唯一知道的URL。但我想转到URL(1),而URL片段('nchor')在这一过程中已经丢失了,实际上已经是第一步了。
这似乎是这些协议的一个基本限制,因为服务器根本看不到URL片段。
我知道,根据规范,当我导航到(1)时,浏览器从服务器请求(2),导致这个片段失去了对SAML协议、WS-Federation等的限制。我的问题是:我如何绕过这个限制?
显而易见的解决方法是避免URL片段,正如这个答案中所建议的那样。然而,对于我们的特定web应用程序来说,这并不好,因为我们在单页GWT应用程序中使用了可添加书签的URL片段,以确保应用程序中的导航不会导致页面重新加载。
我的问题是:对于这种情况,还有什么其他的解决方法或标准模式?
(我对GWT+SAML协议解决方案特别感兴趣。)
您基本上有两个选项:
-
避免使用
location.hash
(至少在支持它的浏览器上使用HTML5的pushState
;和/或提出一种在应用程序中生成永久链接的方法——谷歌群组会这样做) -
使用JavaScript执行重定向。也就是说,不从服务器发送重定向,而是发送一个空的HTML页面,其中包含一些脚本,该脚本采用完整的URL(带有哈希),并使用
location.assign()
或location.replace()
进行重定向。如果运气好的话(取决于服务器),您将在身份验证后重定向到完整的URL。
当然,你可以同时做这两件事:如果链接是应用程序的深度链接,那么进行重定向(即假设没有哈希),否则发送一个带有JS的页面,以确保你不会丢失hash中存在的任何状态。
最后,显而易见的第三种解决方案远非理想:什么都不做,并尝试教育用户,当他们需要(重新)验证时,他们应该重新粘贴URL、重新单击链接或重新单击书签。
根据RFC 1738,当请求资源时,客户端不会向服务器发送锚标记。
锚标记用于标识资源中的位置,而不是服务器上的其他资源。为了识别资源中的位置,客户端需要从服务器获取完整的资源,并且这个过程不需要涉及关于片段的信息传输(因为它对服务器没有任何意义)。
如果您确实希望将片段字符(#)发送到服务器,则需要将其编码在查询字符串中,否则客户端(浏览器)在向服务器发送请求时会忽略URL的该部分。
编辑:
我不知道任何真正的解决方案,但要解决这个问题,你需要在客户端的某个地方保存你的完整返回URL(带有锚标签),因为服务器对锚一无所知。为此,您可以使用SessionStorage(http://www.w3schools.com/html/html5_webstorage.asp)到临时存储ReturnUrl,直到登录过程完成。请注意,旧版浏览器(如<=IE7)不支持它。
在这种情况下,变通方法看起来像这样:
<script>
if(typeof(sessionStorage) == 'undefined')
{
sessionStorage = {
getItem: function(){},
setItem: function(){}
};
}
window.onload = function ()
{
var key = 'ReturnUrl';
//try to get last returnUrl with anchors
var returnUrl = sessionStorage.getItem(key);
//if we got something, do the navigation
if(returnUrl !== undefined && returnUrl !== document.URL)
{
//clean it up
sessionStorage.setItem(key, null);
//navigate to last URL
window.location = returnUrl;
}
else
{
//store url
sessionStorage.setItem(key, document.URL);
}
}
</script>
PS。如果有一些语法错误,请耐心等待,因为我是从头开始写的,但没有尝试。