請問各位先進,之前開發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]這樣的驗證方法(因為實作不出來...><)
通常還是建議您使用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
擴充完後,就可以將它掛載到需要使用的地方,可能是Controller
、Action
甚至是註冊到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());
}
}
托了好久,最近要來解決這個事情了,所以照先進指點的實作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,無法重新導向至登入頁面。
方便將您的Error Message貼出來讓我看看嗎~
這樣應該比較好找問題點~
目前猜測問題可能會在
if (httpContext.Session["MemberId"] != null ||
!string.IsNullOrWhiteSpace(httpContext.Session["MemberId"].ToString()))
在Session失效的情況下,instane並不存在,也沒辦法使用ToString()的方法,會遇到NullReferenceException
請試著改為以下內容
if (httpContext.Session["MemberId"] != null)
我有用截圖貼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裡報錯?
是的,這通常就是表示您的GetChairmanConferenceList
Action並沒有套用到您的Filter,請檢查Action
或Action所屬的Controller
有沒有掛載您的Filter
報告:我將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);
}
leo226,Controller
或Action
層級只需要擇一
即可~
跟您確認一下,我看您Error Message中的所顯示的Action和您上方貼給我的Action並不是同一個,可能要請您確認一下~
Error Action: GetChairmanConferenceList
您好
1.我知道擇一即可,我第一次只加Controller層級,不行成功,大大說檢查Action層級,於是我又多加了Action層級,所以就會變成兩個層級都有加。我的理解理論上兩個都加應該不會產生問題是吧.
2.確實有如大大所見有GetChairmanConferenceList和GetEditorTrackList的不同,剛好抓範例不同,不好意思,範例沒有抓一致性造成誤會,但兩個Controller的設定是都一樣的~
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");
忘記提一個點,就是我沒有掛上去IIS上測試,都還只是用visual studio的環境去跑測試,不知道這樣對Session的測試是不是有影響?
因為我在剛登入有Session的狀態下,Filter是有照著程式碼的判斷去執行成功與失敗的導向,但是當閒置後,沒有Session的狀態下,Filter就無法正常運作,跑出如上Error。
我第一次只加Controller層級,不行成功,大大說檢查Action層級,於是我又多加了Action層級,所以就會變成兩個層級都有加。我的理解理論上兩個都加應該不會產生問題是吧
是的
我沒有掛上去IIS上測試,都還只是用visual studio的環境去跑測試,不知道這樣對Session的測試是不是有影響?
不會有影響
沒有Session的情況下
是否有經過Filter登入驗證
是整個網站通用的,您可以直接考慮將其註冊至Global層級,然後將Login的Action標註為AllowAnonymous
,如此不用擔心會有任何Controller或Action有遺漏報告一下以下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");
}
}
YoChen
這一篇的實作說明跟大大的很像,只差在回傳的是字串,不是bool
https://www.uuu.com.tw/Public/content/article/16/161003tips.htm
然後它在引用時有加入字串的判斷
[CustomAuth("YES")]
另外想請問我的Filter命名為LoginAuthAttribute
我在引用時是要用[LoginAuthAttribute]還是[LoginAuth]呢?
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版本會不會影響到~
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,
其中的內容包含AuthorizeCore
和HandleUnauthorizedRequest
。
簡單來說,每個Request都會先進入OnAuthorization,執行一些必要的流程(e.g. 判斷HttpContext的內容是否為null、是否要省略驗證AllowAnonymous等等...)
最後才執行AuthorizeCore
,且當AuthorizeCore
回傳值為false時,執行HandleUnauthorizedRequest
。
所以,當您override OnAuthorization時,就會導致原本的流程直接被蓋掉,自然也不會執行AuthorizeCore
和HandleUnauthorizedRequest
了~
抱歉讓您花了不少時間,希望這樣有解答您的問題~
我後續會再將回答中有關的OnAuthorization錯誤的描述調整一下,以免之後誤人子弟~XDDD
前輩太強了,如前輩所指點的修改,測試己可正常判斷運作~感激不盡~
前輩太客氣了,前輩願意花自己的時間來幫忙解惑解答己經感激不盡了,遇到錯誤還能有如此寬宏大量的回覆,真的是IT邦的貴人~
這邊再順便請教個問題,關於SessionTimeOut的時間,我在web.config己設定如下為1分鐘,但實測好像Session沒有如預期的在1分鐘後消息,不知前輩是否有了解這樣的狀況?
<system.web>
<sessionState mode="StateServer" stateConnectionString="tcpip=localhost:42424" cookieless="false" timeout="1" />
</system.web>
非常感謝了~謝謝~
又多學了好多知識,感謝YoChen大師指教,謝謝~
簡單的話
宣告 Session['userId'] = id
清除單一Session Session['userId'] = null
清除全部Session Session.Clear()
判斷Session Session['userId'] != null
其他的部分你可以Google看看.
另外我之前寫過鐵人賽文章也可以參考
ASP.NET MVC網頁程式介紹
不採用[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
}