Fluent Security - 在MVC集中管理頁面權限的套件

Fluent Security - 在MVC集中管理頁面權限的套件

前言

當我們開發Asp.net MVC的時候,通常會使用[Authorize]來限制未登入或者不在正確Roles的人進入某些頁面。因為要在所有Controller套上[Authorize]比較麻煩也不好管理,因此通常會有一個BaseController套上[Authorize]然後真正的Controller繼承哪一個BaseController達到所有Controller有受到[Authorize]的控制。

最近在做一個專案的時候,我發現不是使用上述的方式來做驗證和授權,反而用上了一個套件叫做Fluent Security。一開始我不知道他的運作模式,害我一直在修改[Authorize]的標籤然後發現都沒有生效,因為Fluent Security的優先度比較高,不過後來看了官網,我發現這種方式我個人覺得比套上[Authorize]方式更簡單,也更容易維護。

Fluent Security的優勢

基本上來說,Fluent Security的原理和[Authroize]一樣,不過它把設定頁面的部份拉出來,使得我們可以再一個地方設定好所有頁面的權限設定。他有以下幾個優點:

  • 所有的頁面權限可以再一個地方管理,而不需要像以前一樣需要變動某些頁面的權限,需要找不同使用Attribute的位置。
  • 自己可以很簡單的增加自定義的驗證邏輯,和如果驗證沒有過所需要顯示的內容
  • 頁面的權限設定資訊可以取出來,表示可以和其他套件如MvcSitemapProvider整合,使的權限設定只需要寫一個地方,而很多地方都可以使用到。

安裝Fluent Security

使用Nuget搜索FluentSecurity就可以了,在寫這一篇的時候2.0還在Pre release,而這篇是以2.0為主,因此安裝的時候請看一下版本,如果是1.xx記得開啟Pre relase版本。

Fluent Security的一些名詞

在開始介紹如何使用之前,先介紹一些之後會使用的名詞:

Policy
其實就代表驗證邏輯。例如,我今天要說HomeController是所有人都可以看得,我就可以把這一Controller加上Ignore這個預設設定好的Policy。
Policy Violation Handler
當某一個頁面不通過某一個Policy的時候,因該要怎麼辦就是定義在Policy Violation Handler

Fluent Security的流程

基本上流程如下:

  1. 設定如何取得是否登入和Roles的方式
  2. 設定好每一頁的權限
  3. 註冊Fluent Security的Attribute到Global Filter裡面

設定頁面權限

可以使用SecurityConfigurator.Configure這個Static method來做設定。通常來說,會用一個Helper Class把設定整個包起來,然後在Global.asxs執行。包起來的Method如下:

?
1
2
3
4
5
6
7
8
9
10
11
public static ISecurityConfiguration SetFluentSecurity()
{
    SecurityConfigurator.Configure(configue =>
        {
            //設定頁面的權限
            //.....
        });
 
 
    return SecurityConfiguration.Current;
}

1. 設定登入和Roles取得的方式

在開始任何頁面權限設定之前,需要先設定Fluent Security可以使用什麽方式來知道目前使用者的兩種狀態:

  1. 是否登入
  2. 擁有的Roles

如果使用的是Forms 驗證機制,那麼可以參考以下設定:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
//設定去那裡取得關於驗證訊息
configue.GetAuthenticationStatusFrom(IsUserAuthenticated);
configue.GetRolesFrom(UserRoles);
....
 
public static bool IsUserAuthenticated()
{
    return HttpContext.Current.User.Identity.IsAuthenticated;
}
 
public static IEnumerable <object> UserRoles()
{
    var currentUser = HttpContext.Current.User;
    return string.IsNullOrEmpty(currentUser.Identity.Name) ? null
    : System.Web.Security.Roles.GetRolesForUser(currentUser.Identity.Name);
 
}

2. 設定頁面的流程

基本上要設定某一個頁面的Policy很簡單:

  1. 先選擇要被設定的Controller或者Action
  2. 加上對應的Policy就可以

例如下面就是把所有的Controller允許任何人瀏覽:

?
1
2
3
4
    .....
//所有頁面都可以瀏覽
configue.ForAllControllers().Ignore();
    .....

選取Controller或Action

在選取Controller或是Action的時候,通常都是使用For(Action).Policy的方式去設定。目前來說有以下幾種常用的:

For(Action).Policy 或者 ForAllControllers()
第一個表示某一個Controller,如果有給Action則表示那一個Action。第二個則是對所有的Controllers。預設需要所有頁面都有設定權限,要不然會出錯,因此可以 使用ForAllControllers()現初步設定然後在為特定頁面加上不同Policy。
 
ForAllControllersInNamespaceContainingType()
可以選擇某一個Namespace裡面所有的Controller。
 

上面兩種是常用的,其他還有像某一個Inhertance裡面的Controller,可以做Predicate來選定符合條件的Controller等,可以參考官網連接

Policy

就是設定被選取的Controller 或Action的驗證動作。預設有以下幾種Policy:

Ignore
什麽限制都沒有,表示任何人都可以進入。可以用在如登入頁面
 
RequireAnyRole
必須在某一個Role才可以進入
 
DenyAuthenticated
只有未登入才可以進入
 
DenyAnonymous
只有登入才可以進入
 

自訂Policy

預設的應該夠基本使用,不過如果今天你有自己的驗證邏輯,那麼可以自定義Policy。

自定義的Policy需要實作ISecurityPolicy然後使用Static method PolicyResult來創建一個Success或者Failure。

下面是一個例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//如果使用者是Admin Role就通過,不然就失敗。
public class AdminPolicy : ISecurityPolicy
{
    public PolicyResult Enforce(FluentSecurity.ISecurityContext context)
    {
 
        PolicyResult result = PolicyResult.CreateFailureResult(this, "Access denied!");
 
        if (context.CurrentUserRoles() != null)
        {
            if (context.CurrentUserRoles().Contains("Admin"))
            {
                result = PolicyResult.CreateSuccessResult(this);
            }
        }
 
        return result;
    }
}

傳進來的ISecurityContext其實帶了很多資訊,包含使用者的登入狀況,出發Policy的頁面Route資訊,甚至可以在設定configu的時候傳入一些自訂欄位。

Policy Violation Handler

當某一個頁面的Policy傳回FailureResult(使用者不符合可以看著頁的條件),就會觸發Policy Violation。而Policy Violation Handler就是用來處理當使用者不能看到某一頁的時候,該怎麼辦?

這裡需要注意一個地方,在2.0之前只能使用IoC的方式來設定Handler,在2.0以後才可以不用。

預設有兩種Handler:

  1. ExceptionPolicyViolationHandler 預設的,只是丟出一個Exception
  2. HttpUnauthorizedPolicyViolationHandler 丟出使用者沒有權限,而依照webconfig的是定會轉到特定頁面(通常是登入畫面)

Fluent Security如何選擇使用那個Handler

預設它會做兩個判斷,一個靠名字(例如Policy叫做AdminPolicy,那麼就會找對應的AdminPolicyViolationHandler),一個使用全域的DefaultHanlder。

當然,我們可以自己指定某一個Policy要使用哪一個Handler。

越在後面設定的的在選擇的優先度越高。

設定Default Handler

因為預設使用的那一個只是丟出Exception而已,因此,會想換到HttpUnauthorizedPolicyViolationHandler這一個。設定Default Handler如下:

?
1
2
3
4
5
6
//其他設定
....
 //如果不符合Policy,傳回UnAuthorized。換句話說,重新導向登入頁面
//如果沒有加,預設是直接丟出exception
configue.DefaultPolicyViolationHandlerIs(() => new HttpUnauthorizedPolicyViolationHandler());
....

修改某一個Policy的Handler

如果特定Policy想指定某一個Handler的話(例如上面自訂的Admin Policy轉向的頁面會是後臺的login url),可以在configue.Advanced.Violations裡面設定,基本上syntax是: Vilotations.Of<{PolicyType}>().IsHandledBy(() => {Handler})

?
1
2
3
4
5
configue.Advanced.Violations(violation =>
{
violation.Of<adminpolicy>().IsHandledBy(() => new AdminPolicyViolationHandler());
});
</adminpolicy>

上面的例子把之前自定義的AdminPolicy使用另一個自定義的Handler作為處理。如果想使用IoC的方式來new Handler,可以參考官網連接

ISecurityContext

在自定義Policy的時候,會注意到傳進來的是一個ISecurityContext這個interface。在Fluent Security有實作這個介面叫做SecurityContext,而在這個裡面其實儲存的就是和這個使用者有關的內容。例如目前的登入狀況、觸發這個Policy的Route資訊等。如果有需要,我們甚至可以再做頁面config的時候塞一些我們會用到的自定義資料在裡面。

3. 註冊到Globa Filter裡面

當設定檔案都設定好了以後,需要添加一個HandleSecurityAttribute到Global Filter裡面,同時記得它的優先度要條最高(0)。範例如下:

?
1
2
3
4
5
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//加入Fluent Security的Attribute,第二個參數為0表示最優先。
filters.Add(new HandleSecurityAttribute(), 0);
}

最後一步,把一切串起來

記得一定要在Global.asax把HandleSecurityAttribute加入Global Filter以前設定好config,換句話說呼叫的順序應該如下

  • 呼叫設定config的Helper
  • 在註冊Global Filter
?
1
2
3
4
5
6
7
8
9
protected void Application_Start()
{
//設定Fluent security
Security.SetFluentSecurity();
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
//設定global filter
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}

其他注意事項

在Fluent Security預設需要把所有頁面都設定好權限,要不然會爆出Exception。如果不喜歡這種模式可以在config加上:

結語

這篇大概的介紹了有關於Fluent Security這個套件的基本應用,其實設定好頁面瀏覽權限的資訊以後,這些資訊是可以在拿出來的。所以可以和其他套件相互使用,例如MvcSitemap裡面的Visibility判斷就可以使用Fluent Security。同時,如果想把頁面權限設定也加入Unit testing,Fluent Security也可以做到。這部份下一次在介紹。

Sample 程式碼

放在了github,有興趣的可以下載來看看
https://github.com/alantsai/FluentSecurityDemo

其他參考資料


Google+

創用 CC 授權條款
Alan Tsai 的隨手筆記Alan Tsai製作,以創用CC 姓名標示 4.0 國際 授權條款釋出。