成員資格、受權 – ASP.NET MVC 4 系列

       ASP.NET MVC 不像 ASP.NET WEB FORMS 那樣提供了不少自動保護機制來保護頁面不受惡意用戶的攻擊,更明確的說,後者是致力於使應用程序免受攻擊:程序員

  1. 服務器組件對顯示的值和特性進行 HTML 編碼,以幫助阻止 XSS 攻擊。
  2. 加密和驗證試圖狀態,從而幫助阻止篡改提交的表單。
  3. 請求驗證(%@page validaterequest="true"%)截獲看起來是惡意的數據,並給出警告(也是 ASP.NET MVC 框架默認開啓的保護)。
  4. 事件驗證幫助組織注入攻擊和提交無效值。

      ASP.NET MVC 對標記和程序的運行提供了更多控制,這意味着程序員要承擔更多的責任。web

       之因此應用程序存在安全隱患,主要是由於開發人員缺少足夠的信息或理解。另外,人無完人,不免有疏忽的時候,鑑於此,下面是本章的關鍵總結:數據庫

  1. 永遠不要相信用戶提供的數據。
  2. 每當渲染做爲用戶輸入而引入的數據時,要進行 HTML 編碼;若是數據做爲特性值顯示,要進行 HTML 特性編碼(HTML-attribute-encode)。
  3. 考慮網站哪些部分容許匿名訪問,哪些部分要求認證訪問。
  4. 在不須要經過客戶端腳本(大部分狀況下)訪問 cookie 時,使用 HTTP-only cookie。
  5. 記住,外部輸入不是顯式的表單域,由於它包括 URL 查詢字符串、隱藏表單域、Ajax 請求以及咱們使用的外部 Web 服務結果等。
  6. 強烈建議使用 AntiXSS 庫(www.codeplex.com/AntiXSS)。

       黑客、解密高手、垃圾郵件發送者、病毒、惡意軟件,它們都想進入計算機並查看或破壞裏面的數據!瀏覽器

使用 Authorize 特性登陸

       保護應用程序的第一步,也是最簡單的一步,就是要求登陸系統的用戶訪問那些由應用程序指定的 URL。咱們能夠經過控制器上或控制器內部特定操做上的 Authorize 操做過濾器來實現。安全

       Authorize 特性是 ASP.NET MVC 自帶的默認受權過濾器,可限制用戶對操做方法的訪問,若該特性運用於控制器,則會應用於控制器內部全部操做方法。服務器

       有時會對用戶身份驗證和用戶受權之間的區別感到困惑,這兩個詞也比較類似!cookie

  • 身份驗證:指經過使用登陸機制的一些表單(包含用戶名 / 密碼、OpenID 等)覈實用戶的身份,即知道他是誰。
  • 受權驗證:指用來覈實該用戶是否在他的權限內進行操做,即他能作什麼,不能作什麼。

       受權特性不帶任何參數,只要求用戶以某種角色身份登陸網站,換句話說,禁止匿名訪問!app

保護控制器操做

       如今根據一個很是簡單的購物應用需求,建立音樂商店的應用程序。程序中的 StoreController 控制器僅包含 2 個操做,Index 和 Buy:框架

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;
 
namespace MvcMusicStore.Controllers
{
    public class StoreManagerController : Controller
    {
        public ActionResult Index()
        {
            var albums = GetAlbums();
            return View(albums);
        }
 
        [Authorize]
        public ActionResult Buy(int id)
        {
            var album = GetAlbums().Single(e => e.AlbumId == id);
            return View(album);
        }
 
        private static List<Album> GetAlbums()
        {
            var albums = new List<Album> { 
                new Album {AlbumId = 1, Title = "The Fall of Math", Price = 8.99M},
                new Album {AlbumId = 2, Title = "The Blue Notebooks", Price = 8.99M},
                new Album {AlbumId = 3, Title = "Lost in Translation", Price = 9.99M},
                new Album {AlbumId = 4, Title = "Permutation", Price = 10.99M}
            };
            return albums;
        }
    }
}

       若是如今訪問 Store 控制器的 Buy 操做時,就會要求登陸。ide

       下面內容很是重要,請慢讀、理解、記憶:

  • 使用 Web Forms 保護應用程序安全的一個廣泛方法是使用 URL 受權。若是系統有管理模塊而且限制只有 Admins 角色才能訪問該模塊,假設把全部管理頁面都放在了 Admin 文件夾下,那麼除了那些 Admins 角色外,其他用戶一律禁止訪問 Admin 子文件夾,這能夠在網站的 web.config 文件中鎖定這個目錄,以保護其不被非法訪問。
    <location path="Admin" allowOverride="false" />
    <system.web>
      <authorization>
        <allow roles="Administrator"/>
        <deny users="?"/>
      </authorization>
    </system.web>

       這種方式在 MVC 框架中沒法正常工做,緣由有兩個。首先,請求再也不映射到屋裏目錄;其次,可能存在多種查找同一控制器的方式。

  • 理論上,MVC 能夠擁有一個封裝了程序管理功能的 AdminController,而後在根目錄 web.config 設置 URL 受權來阻止全部以 /Admin 開頭的訪問請求。但這未必是安全的,假設從此要切換 {controller} 和 {action} 的順序,那先前的設置將不能阻止對這個 URL 的訪問。

       實現安全性的最好方法是,安全性檢查儘量的接近要保護的對象。可能有高於堆棧的檢查,但最終都要確保實際資源的安全。這樣不管用戶如何得到資源,該方式都會對其進行安全性檢查。因而,也就沒必要依賴路由和 URL 受權來確保控制器安全了。

       Authorize 特性就起這個做用:

  • 若是不指定調用方法的角色和用戶,就必須另外簡單的驗證當前用戶。
  • 用戶訪問這個特性的操做方法,在受權檢查失敗的狀況下,過濾器會引起服務器返回一個「401 未受權」的 HTTP 狀態碼。
  • 若是在 web.config 中啓用了表單身份驗證並指定了登陸 URL 地址,那麼 ASP.NET 會處理這個響應碼,並將用戶重定向。

Ahthorize 特性在表單身份驗證和 AccountController 控制器中的用法

       上面例子在後臺是如何操做的呢?原來,在 ASP.NET MVC 的 InternetApplication 模板包含一個基本的 AccountController,它支持 ASP.NET Membership 和 OAuth 驗證的帳戶管理。

       Authorize 特性是一個過濾器,它能先於控制器操做執行。首先會執行它在 OnAuthorization 方法中的主要操做,這是一個在接口 IauthorizationFilter 中定義的標準方法,查看源碼就會發現,基本的安全機制正在覈實 ASP.NET 上下文中存儲的基自己份驗證信息:

return HttpContext.User.Identity.IsAuthenticated;

       若是用戶驗證失敗,就會返回一個 HttpUnauthorizedResult 操做結果,產生一個 HTTP 401(未受權)的狀態碼。這個狀態碼被 FormsAuthenticationModule 的 Onleave 方法截獲,並轉而重定向到配置中的登陸頁面。

<authentication mode="Forms">
  <forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>

       MVC 框架 InternetApplication 模板提供的這種方式,在簡單的應用場合中能夠輕鬆添加受權,而不須要編寫任何額外代碼及配置。

整個控制器的安全

       有時可能會但願受權級別是控制器,而不是在內部每個操做上添加 Authorize 特性。此時,能夠添加 Authorize 特性至 Controller 上。

使用全局受權過濾器保護整個應用程序安全

       大部分網站,基本上整個應用程序都是須要受權的。這種情形下,默認受權要求和匿名訪問少數網頁就變得極其簡單。把 AuthorizeAttribute 配置爲全局過濾器,而使用 AllowAnonymous 特性標註容許匿名訪問的控制器或方法。

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new AuthorizeAttribute());
        filters.Add(new HandleErrorAttribute());
    }
}

       這樣就限制了對整個應用程序全部操做的訪問,但也別忘了標註容許匿名訪問的控制器或操做(若是有的話)。

角色成員使用 Authorize 特性

[Authorize(Roles = "Administrator")]
public class StoreManagerController : Controller

       這樣就限定了受權訪問的用戶角色只能是管理員角色。顧名思義(Roles 是複數),傳遞的角色列表能夠是逗號間隔的字符串;也能夠受權給一組用戶 Users = "xx,xxx";也能夠二者同時使用。

       固然,應當使用角色而非用戶組。另外,當建立角色組時,可考慮使用基於特權的角色分組,像 CanEditAlbums 這樣的角色組遠比 SuperAdmin、CEOOffice更爲精細,更爲便於管理。

擴展角色和成員

       ASP.NET MVC 的好處之一就是它運行在成熟且功能齊全的 ASP.NET 核心之上。而 ASP.NET MVC 中的身份驗證和受權創建在 System.Web.Security 命名空間中的 Role 類和 Membership 類之上。這樣作是有好處的:

  1. 可以使用基於 ASP.NET Membership 系統的現有代碼和技術。
  2. 經過使用 ASP.NET Membership 和 Roles 的 API,能夠擴展來處理安全性問題的 ASP.NET MVC 組件(如受權和默認的控制器 AccountController)。
  3. 可利用提供器系統建立本身的 Membership、Role 和 Profile 提供器來配合 ASP.NET MVC 工做。

經過 OAuth 和 OpenID 的外部登陸

       運用本地數據庫維護用戶信息也有一些嚴重的負面影響:

  1. 維護包含用戶信息和加密口令的本地數據庫是一項重大的責任!重大安全漏洞、用戶信息泄漏在當今已司空見慣,甚至一些用戶各網站密碼是相同的。
  2. 網站註冊很是麻煩。用戶已經厭倦了填寫各類表格,適用各類不一樣的密碼規則、記憶密碼以及擔憂網站是否能確保他們的信息安全。

       OAuth 和 OpenID 是開放的受權標準。這些協議容許用戶使用他們已有的帳戶登陸咱們的網站,這些帳戶來自於他們信任的網站(提供器)。過去,配置網站以支持 OAuth 和 OpenID 是很是難以實現的。緣由有如下兩點:首先是協議複雜,而後是頂級提供器對這兩種協議的實現方式不同。

       MVC 4 經過在 Internet 模板中內置支持 OAuth 和 OpenID 極大化的簡化了這一點。這種支持包括了一個更新的 AccountController、便於註冊和帳戶管理的視圖以及構建在流行庫 DotNetOpenAuth 之上的工具類!

       新的登陸頁面會出現兩個登陸的選項,以下圖:

       image

註冊外部登陸提供器

       須要顯式的啓用外部網站,以便利用它們的帳戶登陸咱們的網站。可喜的是,這個操做很是簡單,能夠在 AuthConfig.cs 中配置受權提供程序。默認文件中的全部驗證提供器都會註釋掉,以下:

public static void RegisterAuth()
{
 // 若要容許此站點的用戶使用他們在其餘站點(例如 Microsoft、Facebook 和 Twitter)上擁有的賬戶登陸,
 // 必須更新此站點。有關詳細信息,請訪問 http://go.microsoft.com/fwlink/?LinkID=252166
 
 //OAuthWebSecurity.RegisterMicrosoftClient(
 // clientId: "",
 // clientSecret: "");
 
 //OAuthWebSecurity.RegisterTwitterClient(
 // consumerKey: "",
 // consumerSecret: "");
 
 //OAuthWebSecurity.RegisterFacebookClient(
 // appId: "",
 // appSecret: "");
 
 //OAuthWebSecurity.RegisterGoogleClient();
}

       使用OAuth提供器的網站(如Facebook、Twitter等)要求咱們把網站註冊爲一個應用程序,這樣它們就會提供咱們一個客戶端 id 和一個口令,咱們利用 OAuth 提供器就能夠進行驗證。利用 OpenID(如 Google 和 Yahoo)的網站不須要註冊應用程序,咱們也不須要客戶端 id 和口令。

配置 OpenID 提供器

       因爲不用註冊,不用填寫參數,所以配置 OpenID 提供器是很是簡單的,Google、Yahoo 等都有現成的實現,而 myOpenID 須要建立註冊一個自定義的客戶端(經過 using 語句引入一些必要的命名空間,位於 DotNetOpenAuth 下):

using Microsoft.Web.WebPages.OAuth;
using DotNetOpenAuth.AspNet.Clients;
using DotNetOpenAuth.OpenId.RelyingParty;
 
namespace OAuthMVC
{
    public static class AuthConfig
    {
        public static void RegisterAuth()
        {
 // 配置 Google 提供器
            OAuthWebSecurity.RegisterGoogleClient();
 
 // 配置 Yahoo 提供器
            OAuthWebSecurity.RegisterYahooClient();
 
 // 配置 myOpenID 提供器,先建立 OpenIdClient,再進行註冊
            var MyOpenIdClient = new OpenIdClient("myopenid", WellKnownProviders.MyOpenId);
            OAuthWebSecurity.RegisterClient(MyOpenIdClient, "MyOpenID", null);
        }
    }
}

       運行程序,測試登陸的效果,如圖:

image

       Google 被和諧,點擊 Yahoo 能夠導航到登陸界面:

       image

       輸入帳戶密碼以後,Yahoo 詢問是否贊成登陸咱們的網站,選擇 Agree:

       image

       驗證成功,並受權後,瀏覽器返回咱們的站點,此時能夠完成一些咱們站點的註冊步驟,並在單擊註冊後,會被做爲一個已認定的用戶重定向到主頁:

       image

       註冊成功:

       image

      點擊用戶名能夠管理本身的帳戶,添加一個本地帳戶和密碼或者綁定額外的外部登陸提供器:

       image

配置 OAuth 提供器

       須要在第三方網站將本身的站點註冊爲一個 App,以後使用得到的 AppID 和密鑰就能夠註冊了,例以下面的 Facebook 站點:

public static void RegisterAuth()
{
    OAuthWebSecurity.RegisterFacebookClient(appId: "123456789", appSecret: "abcdefg");
}

外部登陸的安全性

       儘管 OAuth 和 OpenID 簡化了安全性編碼,但也給應用程序引入了其餘潛在的攻擊媒介,若是一個提供器網站被破壞,或者網站之間的安全通訊遭到破壞,攻擊者可能會破壞咱們的網站登陸、或者捕獲用戶信息。所以要作到如下幾點:

       1. 可信的外部登陸提供器,使用知名的提供器,只支持咱們信任的站點

       2. 要求 SSL 登陸,外部提供器到網站的回調中包含擁有用戶信息的安全令牌,當令牌在互聯網傳遞時,使用 HTTPS 傳輸是很重要的,這樣能夠防止信息被攔截。

[RequireHttps]
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
    ViewBag.ReturnUrl = returnUrl;
    return View();
}
相關文章
相關標籤/搜索