文章出處

被這個問題困擾多年,今天終于找到了更簡單的解決方法,分享一下。

問題場景

假設我們在i.cnblogs.com站點的web.config中對FormsAuthentication進行了如下的設置:

<authentication mode="Forms">
  <forms name=".cnblogs" loginUrl="https://passport.cnblogs.com/login.aspx" protection="All" path="/"/>
</authentication>

當我們訪問一個需要登錄后才能訪問的URL時,比如:http://i.cnblogs.com/post/list,請求會被重定向至如下的地址:

https://passport.cnblogs.com/login.aspx?ReturnUrl=%2fpost%2flist

瞧!通過ReturnUrl查詢參數傳遞給登錄頁面的是相對路徑——這就是問題所在。由于訪問的頁面與登錄頁面不在同一個二級域名下,使用這個相對路徑是Return不回來的。

問題的根源

用ILSPY看一下System.Web.Security.FormsAuthentication的代碼,立馬就能知道問題原因所在:

internal static string GetLoginPage(string extraQueryString, bool reuseReturnUrl)
{
    //...
    if (text2 == null)
    {
        text2 = HttpUtility.UrlEncode(current.Request.RawUrl, current.Request.ContentEncoding);
    }
    text = text + FormsAuthentication.ReturnUrlVar + "=" + text2;
    if (!string.IsNullOrEmpty(extraQueryString))
    {
        text = text + "&" + extraQueryString;
    }
    return text;
}

由碼可見,微軟根本就無視了登錄頁面不在同一個二級域名的基本應用場景,而且一直無視到現在。

以前的解決方法

在當前站點添加一個中轉頁面,由中轉頁面重定向至登錄頁面。

于是,web.config的設置變成了如下的樣子,先重定向至當前站點的登錄中轉頁面。

<authentication mode="Forms">
  <forms name=".cnblogs" loginUrl="~/account/login" protection="All" path="/"/>
</authentication>

然后,在中轉頁面使用絕對路徑作為ReturnUrl的值,再重定向至真正的登錄頁面。

中轉頁面的示例代碼如下:

public class AccountController : Controller
{ 
    public ActionResult Login(string ReturnUrl)
    {
        return Redirect("https://passport.cnblogs.com/login.aspx?ReturnUrl=" +
            HttpUtility.UrlEncode("http://" + Request.Url.Host) + ReturnUrl);
    }
}

雖然解決了問題,但是對于這樣的解決方法,我覺得有些啰嗦,總覺得有更好的解決方法,可是一直沒找到。

今天再次面對這個問題時,狠了一下心,竟然有了意外的收獲!

更簡單的解決方法

Forms驗證中,工作在第一線、最苦最累的是System.Web.Security.FormsAuthenticationModule。

它在OnEnter(object source, EventArgs eventArgs)中調用了OnAuthenticate方法:

// System.Web.Security.FormsAuthenticationModule
private void OnEnter(object source, EventArgs eventArgs)
{
    //...
    this.OnAuthenticate(new FormsAuthenticationEventArgs(context));
    //...
}

而在OnAuthenticate()方法中有如下的事件處理:

private void OnAuthenticate(FormsAuthenticationEventArgs e)
{
    HttpCookie httpCookie = null;
    if (this._eventHandler != null)
    {
        this._eventHandler(this, e);
    }    
    //...  

再找到有關這個事件的代碼:

// System.Web.Security.FormsAuthenticationModule
public event FormsAuthenticationEventHandler Authenticate
{
    add
    {
        this._eventHandler = (FormsAuthenticationEventHandler)Delegate.Combine(this._eventHandler, value);
    }
    remove
    {
        this._eventHandler = (FormsAuthenticationEventHandler)Delegate.Remove(this._eventHandler, value);
    }
}

從這個地方下手,更簡單的解決方法就浮出了水面——

在Global.asax.cs中添加如下的代碼:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        //...
    }

    protected void FormsAuthentication_OnAuthenticate(Object sender, 
        FormsAuthenticationEventArgs e)
    {
        if (Request.Cookies[FormsAuthentication.FormsCookieName] == null)
        {
            Response.Redirect(FormsAuthentication.LoginUrl + "?ReturnUrl=" + 
                HttpUtility.UrlEncode(e.Context.Request.Url.AbsoluteUri));
        }
    }
}

web.config中使用原先的設置:

<authentication mode="Forms">
  <forms name=".cnblogs" loginUrl="https://passport.cnblogs.com/login.aspx" protection="All" path="/"/>
</authentication>

訪問http://i.cnblogs.com/post/list時,會進行如下的重定向:

https://passport.cnblogs.com/login.aspx?ReturnUrl=http%3a%2f%2fi.cnblogs.com/post/list

如果微軟繼續無視這個問題,我想這就是最簡單的解決方法。

【更新】

感謝彭偉在評論中指出上面的代碼沒有考慮cookie過期的的情況,下面的代碼是針對MVC的一個解決方法:

public class BlogAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (!httpContext.User.Identity.IsAuthenticated)
        {
            httpContext.Response.Redirect(FormsAuthentication.LoginUrl + "?ReturnUrl=" +
                HttpUtility.UrlEncode(httpContext.Request.Url.AbsoluteUri));
        }
        return base.AuthorizeCore(httpContext);
    }
}

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()