iT邦幫忙

0

ASP.NET MVC Session登入驗證

請問各位先進,之前開發WebForm使用Session記錄做驗證,然後再寫一支aspx檔Include進需要做登入驗證的每一個頁面裡,如下。

<!--#INCLUDE FILE="~/defense.aspx"-->
<!--#INCLUDE FILE="~/checkLock.aspx"-->

defense.aspx

<div>
<%
System.Web.HttpContext context = System.Web.HttpContext.Current;
String host = Request.Url.Segments[1].Replace("/","");
if((Session["mId"] == null) || (context.Session["mId"] == null))
{
    Response.Redirect("~/reject.aspx");
    /*"存取錯誤!! 您目前的權限無法觀看此網頁" + Request.Url.AbsoluteUri + ",請登入後再試";*/
}
%>
</div>

請問現在轉換至MVC專案,一樣想要採用Session記錄做登入驗證,請問該如何在每一個需要驗證的頁面載入判斷的程式呢?
不採用[Authorize]這樣的驗證方法(因為實作不出來...><)

0
YoChen
iT邦研究生 5 級 ‧ 2019-11-05 11:28:04
最佳解答

通常還是建議您使用Authorize Filter,不但容易維護,程式碼也會直覺很多,以下提供一個簡單的實作供您參考

首先,先建立一個Class,假設它叫做SessionAuthorizeAttribute,並繼承FilterAttribute中專門負責做授權的AuthorizeAttribute

public class SessionAuthorizeAttribute : AuthorizeAttribute
{
    // AuthorizeAttribute包含三個關鍵方法供您override
    
    // 核心Function
    // 可以自定義module邏輯,若沒有要自己處理整個module架構,基本上不覆寫
    // Source Code: (https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Mvc/AuthorizeAttribute.cs) 
    public override void OnAuthorization(AuthorizationContext filterContext) { }
    
    // 驗證邏輯,成功回傳True,失敗回傳False
    protected override bool AuthorizeCore(HttpContextBase httpContext) { }
    
    // 驗證失敗要做什麼
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { }
}

接著我們搭配Session做個簡單的實作

public class SessionAuthorizeAttribute : AuthorizeAttribute
{
    // AuthorizeAttribute包含三個關鍵方法供您override
    
    // 驗證邏輯,成功回傳True,失敗回傳False
    protected override bool AuthorizeCore(HttpContextBase httpContext) 
    {
        bool isPass = false;

        // 透過httpContext取得Session
        if (httpContext.Session["userId"] == MyLogic())
        {
            isPass = true;
        }
        
        return isPass;
    }
    
    // 驗證失敗要做什麼
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) 
    {
        // 設定要執行的Result
        filterContext.Result = new RedirectResult("/");
    }
}

AuthorizeAttribute擴充完後,就可以將它掛載到需要使用的地方,可能是ControllerAction甚至是註冊到Global讓整個專案使用都行~

MyController.cs

[SessionAuthorizeAttribute] // 掛在這邊,整個Controller都會經過此Filter
public class MyController : Controller
{
    [SessionAuthorizeAttribute] // 掛在這邊,Action會經過此Filter
    public ActionResult Index() 
    {
        // ...
    }
}

如果要註冊到Global,先找到專案中App_Start資料夾裡的FilterConfig.cs,然後加入我們新的Filter

FilterConfig.cs

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new SessionAuthorizeAttribute());
    }
}
看更多先前的回應...收起先前的回應...
leo226 iT邦新手 5 級 ‧ 2020-01-09 10:22:09 檢舉

托了好久,最近要來解決這個事情了,所以照先進指點的實作filter,並套用在Controller階層上,目前的寫法好像也無法解決Session失效的問題,不知道是那個環結寫錯或沒有寫好,不知各位大大是否方便再進一步指點一下。

public class LoginAuthAttribute : AuthorizeAttribute
{
    // AuthorizeAttribute包含三個關鍵方法供您override

    // 驗證邏輯,成功回傳True,失敗回傳False
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        bool isPass = false;

        // 透過httpContext取得Session
        if (httpContext.Session["MemberId"] != null || !string.IsNullOrWhiteSpace(httpContext.Session["MemberId"].ToString()))
        {
            isPass = true;
        }
        return isPass;
    }

    // 驗證成功要做什麼
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        // 設定要執行的Result
        //filterContext.Result = new RedirectResult("/Users/Conference/Index");
    }

    // 驗證失敗要做什麼
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        // 設定要執行的Result
        filterContext.Result = new RedirectResult("/Member/Login");
    }
}
    [LoginAuthAttribute]
    public class ReviewerController : Controller
    {

註:
我有在filter的OnAuthorization和HandleUnauthorizedRequest寫入,當成功時導向首頁頁面,當失敗時重新導向登入頁面,這樣的功能是可以正常執行的。
但測試當登入後產生Session,然後閒置等Session失效,再執行有加filter的連結,還是依然直接Error,無法重新導向至登入頁面。

YoChen iT邦研究生 5 級 ‧ 2020-01-09 13:28:41 檢舉

方便將您的Error Message貼出來讓我看看嗎~
這樣應該比較好找問題點~

YoChen iT邦研究生 5 級 ‧ 2020-01-09 13:46:00 檢舉

目前猜測問題可能會在

if (httpContext.Session["MemberId"] != null || 
!string.IsNullOrWhiteSpace(httpContext.Session["MemberId"].ToString()))

在Session失效的情況下,instane並不存在,也沒辦法使用ToString()的方法,會遇到NullReferenceException

請試著改為以下內容

if (httpContext.Session["MemberId"] != null)
leo226 iT邦新手 5 級 ‧ 2020-01-09 13:59:45 檢舉

我有用截圖貼Error,沒想到沒有秀出來,剛才重新編輯了一下,好像截圖有秀出來了,文字error如下,感謝前輩指點,我再試一下,晚點回報狀況~

並未將物件參考設定為物件的執行個體。
描述: 在執行目前 Web 要求的過程中發生未處理的例外狀況。請檢閱堆疊追蹤以取得錯誤的詳細資訊,以及在程式碼中產生的位置。

例外狀況詳細資訊: System.NullReferenceException: 並未將物件參考設定為物件的執行個體。

原始程式錯誤:


行 28:         public ActionResult GetChairmanConferenceList()
行 29:         {
行 30:             int MemberId = Convert.ToInt32(Session["MemberId"].ToString());
行 31:             int Status = 5;
行 32: 

報錯的內容反而不在filter裡面,而是在Controller的Action裡的int MemberId = Convert.ToInt32(Session["MemberId"].ToString());
這樣是正常的嗎?如果Session有錯是不是應該會在filter裡報錯?

YoChen iT邦研究生 5 級 ‧ 2020-01-09 14:10:20 檢舉

是的,這通常就是表示您的GetChairmanConferenceList Action並沒有套用到您的Filter,請檢查ActionAction所屬的Controller有沒有掛載您的Filter

leo226 iT邦新手 5 級 ‧ 2020-01-09 15:54:26 檢舉

報告:我將Filter在Controller層級和Action層級都有套用,程式碼如下,測試結果一樣會在Action程式碼讀取Session時報錯,Filter過濾Session後並沒有將頁面導向至登入頁面~不知道是那個環結沒有處理好~

[LoginAuthAttribute]
public class EditorController : Controller
{
    // GET: Users/Editor
    public ActionResult Index()
    {
        return View();
    }
    #region 取得主編負責之會議主題投稿清單
    [LoginAuthAttribute]
    public ActionResult GetEditorTrackList()
    {
        int MemberId = Convert.ToInt32(Session["MemberId"].ToString());
        List<MemberToConferenceTrackViewModel> mttVML = new List<MemberToConferenceTrackViewModel>();
        mttVML = paperSrv.GetMemberToConferenceTrackList(MemberId, "Editor");
        return View(mttVML);
    }
YoChen iT邦研究生 5 級 ‧ 2020-01-09 16:26:42 檢舉

leo226ControllerAction層級只需要擇一即可~

跟您確認一下,我看您Error Message中的所顯示的Action和您上方貼給我的Action並不是同一個,可能要請您確認一下~

Error Action: GetChairmanConferenceList

leo226 iT邦新手 5 級 ‧ 2020-01-09 18:06:28 檢舉

您好
1.我知道擇一即可,我第一次只加Controller層級,不行成功,大大說檢查Action層級,於是我又多加了Action層級,所以就會變成兩個層級都有加。我的理解理論上兩個都加應該不會產生問題是吧.
2.確實有如大大所見有GetChairmanConferenceList和GetEditorTrackList的不同,剛好抓範例不同,不好意思,範例沒有抓一致性造成誤會,但兩個Controller的設定是都一樣的~

leo226 iT邦新手 5 級 ‧ 2020-01-09 18:18:30 檢舉

Error Message如下:

並未將物件參考設定為物件的執行個體。
描述: 在執行目前 Web 要求的過程中發生未處理的例外狀況。請檢閱堆疊追蹤以取得錯誤的詳細資訊,以及在程式碼中產生的位置。

例外狀況詳細資訊: System.NullReferenceException: 並未將物件參考設定為物件的執行個體。

原始程式錯誤:


行 31:         public ActionResult GetEditorTrackList()
行 32:         {
行 33:             int MemberId = Convert.ToInt32(Session["MemberId"].ToString());
行 34:             List<MemberToConferenceTrackViewModel> mttVML = new List<MemberToConferenceTrackViewModel>();
行 35:             mttVML = paperSrv.GetMemberToConferenceTrackList(MemberId, "Editor");
leo226 iT邦新手 5 級 ‧ 2020-01-09 20:26:35 檢舉

忘記提一個點,就是我沒有掛上去IIS上測試,都還只是用visual studio的環境去跑測試,不知道這樣對Session的測試是不是有影響?
因為我在剛登入有Session的狀態下,Filter是有照著程式碼的判斷去執行成功與失敗的導向,但是當閒置後,沒有Session的狀態下,Filter就無法正常運作,跑出如上Error。

YoChen iT邦研究生 5 級 ‧ 2020-01-09 23:09:34 檢舉

我第一次只加Controller層級,不行成功,大大說檢查Action層級,於是我又多加了Action層級,所以就會變成兩個層級都有加。我的理解理論上兩個都加應該不會產生問題是吧

是的

我沒有掛上去IIS上測試,都還只是用visual studio的環境去跑測試,不知道這樣對Session的測試是不是有影響?

不會有影響


  • 請您試著在Filter內下中斷點,觀察看看在沒有Session的情況下是否有經過Filter
  • 另外建議您,如果您的登入驗證是整個網站通用的,您可以直接考慮將其註冊至Global層級,然後將Login的Action標註為AllowAnonymous,如此不用擔心會有任何Controller或Action有遺漏
leo226 iT邦新手 5 級 ‧ 2020-01-10 17:26:51 檢舉

報告一下以下Filter設定中斷點的測試結果:
1.中斷點設定在AuthorizeCore,不論Session是否存活,都沒有跑進去中斷點程式裡
2.中斷點設定在OnAuthorization,在剛登入Session還存活時,有跑進去中斷點程式裡
3.中斷點設定在HandleUnauthorizedRequest,不論Session是否存活,都沒有跑進去中斷點程式裡

問題:
1.請問以下Filter的程式流程,是會先進AuthorizeCore判斷true或false,若為true則進OnAuthorization,若為false則進HandleUnauthorizedRequest,是嗎?
2.目前測試結果只有OnAuthorization部份可以運作而己,其它部份設定中斷點都沒有運作,所以想要判斷Session的功能實作不出來。
3.我想要把Session的判斷式寫在OnAuthorization,但不知道該如何寫呼叫出Session的語法。

public class LoginAuthAttribute : AuthorizeAttribute
{
    // AuthorizeAttribute包含三個關鍵方法供您override

    // 驗證邏輯,成功回傳True,失敗回傳False
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        bool isPass = false;

        // 透過httpContext取得Session
        if (httpContext.Session["MemberId"] != null)
        {
            isPass = true;
        }
        return isPass;
    }

    // 驗證成功要做什麼
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        // 設定要執行的Result
        //filterContext.Result = new RedirectResult("/Users/Conference/Index");
    }

    // 驗證失敗要做什麼
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        // 設定要執行的Result
        filterContext.Result = new RedirectResult("/Member/Login");
    }
}
leo226 iT邦新手 5 級 ‧ 2020-01-13 15:02:56 檢舉

YoChen
這一篇的實作說明跟大大的很像,只差在回傳的是字串,不是bool
https://www.uuu.com.tw/Public/content/article/16/161003tips.htm
然後它在引用時有加入字串的判斷

[CustomAuth("YES")]

另外想請問我的Filter命名為LoginAuthAttribute
我在引用時是要用[LoginAuthAttribute]還是[LoginAuth]呢?

YoChen iT邦研究生 5 級 ‧ 2020-01-13 15:55:42 檢舉

leo226,回答您的問題

請問以下Filter的程式流程,是會先進AuthorizeCore判斷true或false,若為true則進OnAuthorization,若為false則進HandleUnauthorizedRequest,是嗎?

是的

目前測試結果只有OnAuthorization部份可以運作而己,其它部份設定中斷點都沒有運作,所以想要判斷Session的功能實作不出來

這個部份真的滿奇怪的,看起來很像您的Request根本都沒有通過Filter,建議您還是先將Filter設成Global試試看,如果還是不會經過,再試著找看看是不是哪邊有設定將Filter省略了

我想要把Session的判斷式寫在OnAuthorization,但不知道該如何寫呼叫出Session的語法

filterContext.HttpContext.Session

這一篇的實作說明跟大大的很像,只差在回傳的是字串,不是bool

他回傳的還是Bool並非String

return _authstr == "YES";
// 同
bool isPass = _authstr == "YES";
return isPass;

然後它在引用時有加入字串的判斷

您也可以透過類別建構式達到一樣的效果,不過該作者只是為了方便表示驗證成功或失敗,沒有需要的話,可以不用加~

另外想請問我的Filter命名為LoginAuthAttribute
我在引用時是要用[LoginAuthAttribute]還是[LoginAuth]呢?

建議還是用全名LoginAuthAttribute會比較好,Attribute可以省略沒錯,但我不確定Framework版本會不會影響到~

YoChen iT邦研究生 5 級 ‧ 2020-01-13 16:57:58 檢舉

leo226,找到問題所在了,是我的觀念錯誤,請您照以下方式修改便可

public class LoginAuthAttribute : AuthorizeAttribute
{
    // AuthorizeAttribute包含三個關鍵方法供您override

    // 驗證邏輯,成功回傳True,失敗回傳False
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        // 略...
    }
    
    // 請直接將這個Function直接註解掉
    // public override void OnAuthorization(AuthorizationContext filterContext)
    // {
        // 略...
    // }

    // 驗證失敗要做什麼
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        // 略...
    }
}

我剛剛自己試了一下您提供的範例程式碼,發現也會跟您有一樣的問題,於是我跑去看了一下Source Code: System.Web.Mvc.AuthorizeAttribute.cs

由原始碼我立刻就發現問題所在了XDDD,
OnAuthorization並非成功時要做什麼
而是AuthorizeAttribute的核心Function,
其中的內容包含AuthorizeCoreHandleUnauthorizedRequest

簡單來說,每個Request都會先進入OnAuthorization,執行一些必要的流程(e.g. 判斷HttpContext的內容是否為null、是否要省略驗證AllowAnonymous等等...)
最後才執行AuthorizeCore,且當AuthorizeCore回傳值為false時,執行HandleUnauthorizedRequest

所以,當您override OnAuthorization時,就會導致原本的流程直接被蓋掉,自然也不會執行AuthorizeCoreHandleUnauthorizedRequest了~

抱歉讓您花了不少時間,希望這樣有解答您的問題~
我後續會再將回答中有關的OnAuthorization錯誤的描述調整一下,以免之後誤人子弟~XDDD

leo226 iT邦新手 5 級 ‧ 2020-01-14 10:38:29 檢舉

前輩太強了,如前輩所指點的修改,測試己可正常判斷運作~感激不盡~
前輩太客氣了,前輩願意花自己的時間來幫忙解惑解答己經感激不盡了,遇到錯誤還能有如此寬宏大量的回覆,真的是IT邦的貴人~
這邊再順便請教個問題,關於SessionTimeOut的時間,我在web.config己設定如下為1分鐘,但實測好像Session沒有如預期的在1分鐘後消息,不知前輩是否有了解這樣的狀況?

<system.web>
    <sessionState mode="StateServer" stateConnectionString="tcpip=localhost:42424" cookieless="false" timeout="1" />
</system.web>

非常感謝了~謝謝~

YoChen iT邦研究生 5 級 ‧ 2020-01-14 14:41:14 檢舉

leo226
由於您是使用StateServer Mode,因此您可能要到services.msc(您可以在cmd裡面直接輸入services.msc開啟服務列表)檢查一下ASP.NET State Service有沒有啟動,詳細內容您可以參考保哥的文章

另外,若您要查看Session的timeout有沒有吃到config的值,您可以在程式碼中有包含Session的部分下中斷點,檢查看看timeout屬性的值是否正確

leo226 iT邦新手 5 級 ‧ 2020-01-14 19:05:17 檢舉

又多學了好多知識,感謝YoChen大師指教,謝謝~

1
小魚
iT邦大師 1 級 ‧ 2019-11-04 17:13:13

簡單的話

宣告 Session['userId'] = id
清除單一Session Session['userId'] = null
清除全部Session Session.Clear()

判斷Session Session['userId'] != null

其他的部分你可以Google看看.

另外我之前寫過鐵人賽文章也可以參考
ASP.NET MVC網頁程式介紹

0
allenlwh
iT邦研究生 2 級 ‧ 2019-11-04 21:00:29

不採用[Authorize]這樣的驗證方法(因為實作不出來...><)

我剛好有一小段Sample可以參考看看

using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

public class AuthActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (actionContext.Request.Headers.Authorization == null)              
        {
            setErrorResponse(actionContext, "Error!!");
        }        
        base.OnActionExecuting(actionContext);
    }
}

[AuthActionFilter]
public object updatecase()
{
    xxxxx
}
leo226 iT邦新手 5 級 ‧ 2020-01-09 10:39:34 檢舉

大大使用的是ActionFilterAttribute
請問大大這一段是直接寫在Controller裡嗎?
我試寫了一段,報了很多error,using無法用,不知是少參考了什麼東西需要另外安裝的?

我要發表回答

立即登入回答