1. 程式人生 > >使用windows驗證(Using Windows Authentication)【轉】

使用windows驗證(Using Windows Authentication)【轉】

在軟體術語裡面,驗證的意思是測定身份。這個跟授權是完全分開的,授權是讓合適的人做合適的事情,授權通常在驗證之後發生。ASP.NET的驗證功能也緊緊圍繞識別訪問者的身份並且設定決定實際的訪問者能夠做什麼的安全上下文(security context)。最簡單的驗證方式就是把這個任務委託給IIS(這通常適合內部系統)。在配置檔案裡啟用Windows身份驗證,如果我們使用的Intranet應用程式模版,預設會使用這個配置。如下:
<configuration>
    <system.web>
        <authentication mode="Windows" />


    </system.web>
</configuration>
使用windows驗證,ASP.NET依賴於IIS來驗證使用者的請求,並且有如下模式:
①匿名驗證:允許任何使用者訪問,IIS7預設啟用
②基本驗證:需要使用者提供一個經過驗證的使用者名稱和密碼,驗證標識作為文字會從瀏覽器傳送到伺服器,這種模式僅僅應該在SSL連線的基礎上使用
③摘要驗證:需要使用者名稱和密碼,驗證標識會被加密為hash程式碼傳送到伺服器。安全性比基本驗證更高,並且需要伺服器是一個域控制器。
④windows驗證:通過windows域透明地建立使用者的身份,不需要提供其他的認證。在企業內部區域網使用廣泛,不適合面向Internet的應用。
可以在IIS管理器裡面設定驗證模式,如下:


如果我們想讓所有的請求都經過驗證,需要我們禁用匿名驗證功能。如果只是部分限制,那麼可以啟用匿名驗證並對actions和controllers使用驗證過濾器。在區域網部署程式,那麼使用windows驗證是非常有用的。如果使用者是來自Internet,應用程式傾向於依賴表單驗證。

使用表單驗證(Using Forms Authentication)

表單驗證非常適合面向Internet網的程式,當然建立表單驗證也會比windows驗證更加複雜,一旦設定都完畢,那麼比windows驗證將會更加靈活。表單驗證的安全性依賴於一加密的cookie——.ASPXAUTH。這個cookie內容類似於:
9CC50274C662470986ADD690704BF652F4DFFC3035FC19013726A22F794B3558778B12F799852B2E84


D34D79C0A09DA258000762779AF9FCA3AD4B78661800B4119DD72A8A7000935AAF7E309CD81F28
用FormsAuthentication.Decrypt對其進行解密,可以得到一個FormsAuthenticationTicket物件並且具有如下屬性:


其中裡面一個關鍵的屬性就是Name,這個跟使用者請求的相關聯的身份標識。系統的安全性的保證來自於cookie資料的機密和使用伺服器的machine keys的簽名。這些是由IIS自動生成的,並且如果沒有這些keys,包含在cookie裡的驗證資訊是不能被讀取和修改的。

Tip:當我們把使用了表單驗證的程式部署到伺服器叢集時,必須保證請求總是返回生成cookie的伺服器或保證所有的伺服器具有相同的machine keys。這些Keys可以使用IIS管理器裡面的machine keys選項來生成和配置。

建立表單驗證(Setting Up Forms Authentication)

當我們使用網際網路應用程式模版建立一個MVC程式時,預設啟用了表單身份驗證,相關的配置如下:
  <authentication mode="Forms">
    <forms loginUrl="~/Account/LogOn" timeout="2880" />
  </authentication>
這個配置能夠適合大多數程式,當然也可以定義更多的屬性來控制,這些屬性如下:

屬性名 預設值 描述
name .ASPXAUTH cookie名
timeout 30分鐘 超時時間
slidingExpiration true 滑動過去時間
domain None 跨子域共享(www.example.com和a.example.com)
path / 設定驗證cookie傳送到指定的URL,這個讓我們可以在同一個域中寄宿多個應用程式而不會暴露彼此的驗證cookie
loginUrl /login.aspx 如果表單需要使用者登入,重定向到指定的登入頁面
cookieless UserDeviceProfile 啟用無cookie驗證
requireSSL false 設定為true會建議瀏覽器僅僅在使用SSL加密的請求中傳遞cookie





在web.config檔案啟用表單驗證,當沒有經過驗證的使用者訪問到任何標記了[Authorize]的controller和action時會跳轉到登入頁。

使用無cookie的表單驗證(Using Cookieless Forms Authentication)

表單驗證支援無cookie模式,這種情況下驗證的票據存放在URL裡,資料仍然是簽名和加密的,但是會作為URL的一部分發送到伺服器。只要每一個請求包含了驗證資料,那麼使用者接收到同樣應用程式的體驗跟啟用了cookie是一樣的。啟用無cookie驗證的配置如下:
<authentication mode="Forms">
  <forms loginUrl="~/Account/LogOn" timeout="2880" cookieless="UseUri">
  </forms>
</authentication>

當用戶登入時,他們會被重定向到一個像如下的URL:
/(F(nMD9DiT464AxL7nlQITYUTT05ECNIJ1EGwN4CaAKKze-9ZJq1QTOK0vhXTx0fWRjAJdgSYojOYyhDil
HN4SRb4fgGVcn_fnZU0x55I3_Jes1))/Home/ShowPrivateInformation
仔細觀察會發現,URL遵循這樣的模式:/(F(authenticationData))/normalUrl
我們不用特別的步驟和配置,路由系統會仔細轉換這些URL以至於我們的路由配置能夠適應任何改變並且HTML輔助方法會自動生成這種型別的URL。不推薦使用無cookie的驗證,因為這種驗證非常脆弱,如果有一個連結沒有包含驗證資訊,那麼使用者馬上就被登出了。無cookie驗證也是不安全的,任何一個人複製你的URL並共享給其他人,那麼第一個使用者的session將會被劫持。而且,如果我們依賴了從第三方伺服器獲取的內容,那麼我們的驗證資料會通過瀏覽器的引用頭(Referer header)傳送給第三方。最後,這個URL看起非常醜陋,沒有可讀性和吸引力。

使用Membership,Roles和Profiles

在SportsStroe專案裡面,我們在Web.config檔案裡面儲存了使用者的證書資訊,對於小的程式和使用者不會經常改變並且使用者數量很小的情況下可以這樣使用。ASP.NET提供了一套標準的使用者賬戶系統來支援常用的使用者賬戶管理任務,包含註冊,密碼管理,個性設定。具有三個關鍵的功能區域:
Membership:註冊使用者賬戶並訪問賬戶詳情和授權證書
Roles:把使用者放入組裡面,對身份驗證的典型應用
Profiles:儲存每一個使用者的基礎資料
ASP.NET對上面的三個區域提供了標準的實現,但是我們也可以跟自定義的實現混合使用,通過一個providers系統就可以實現。內建的providers能夠以不同的方式儲存資料,包括SQL Server和活動目錄。

建立和使用Membership
ASP.NET裡面已經有了SqlMembershipProvider和ActiveDirectoryMembershipProvider兩個提供者。下面會介紹如何最常規的使用:

建立SqlMembershipProvider

當使用網際網路應用程式模版建立一個MVC程式時,預設配置了SqlMembershipProvider。配置的Web.config如下:

View Code 複製程式碼
<configuration> 
  <connectionStrings> 
    <add name="ApplicationServices" 
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI; 
                          AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" 
                          providerName="System.Data.SqlClient" /> 
  </connectionStrings> 
  ... 
   <system.web> 
    <membership> 
      <providers> 
        <clear/> 
        <add name="AspNetSqlMembershipProvider"  
             type="System.Web.Security.SqlMembershipProvider"  
             connectionStringName="ApplicationServices" 
             enablePasswordRetrieval="false"  
             enablePasswordReset="true"  
             requiresQuestionAndAnswer="false"  
             requiresUniqueEmail="false" 
             maxInvalidPasswordAttempts="5"  
             minRequiredPasswordLength="6"  
             minRequiredNonalphanumericCharacters="0"passwordAttemptWindow="10" 
             applicationName="/" /> 
      </providers> 
    </membership> 
</system.web> 
</configuration>
複製程式碼

SQL Server Express版本的資料庫支援一個user例項的資料庫,這些資料庫是不用在之前配置就可以使用的。付費版的SQL Server不支援使用者例項資料庫,需要在使用之前準備好該資料庫。安裝方法:執行aspnet_regsql.exe。安裝完了可以去SQL Server management studio裡面檢視下。

管理Membership

Membership API包含對註冊使用者的管理方法:新增和移除賬戶,重置密碼等等。對於一些簡單的情形,完全可以使用站點管理工具(VS裡面有對應的按鈕)。如下:


一旦部署了應用程式,我們能夠通過IIS .NET使用者選項來管理應用程式的使用者。使用.NET User選項時,IIS管理工具讀取Web.config檔案並且檢視確保membership提供者是值得信任的。不太走運的是,IIS管理工具是基於.NET2.0並且尚未更新到支援ASP.NET4.當前的IIS版本,微軟對使用.NET4的程式禁用了.NET Users選項。下面看看示例的配置檔案:

View Code 複製程式碼
<?xml version="1.0" encoding="UTF-8"?> 
 
<configuration> 
  <connectionStrings> 
    <add name="ApplicationServices" connectionString="data Source=TITAN\SQLEXPRESS; 
       Initial Catalog=aspnetdb; 
       Persist Security Info=True; 
       User ID=adam;Password=adam" 
         providerName="System.Data.SqlClient" /> 
  </connectionStrings> 
 
  <system.web> 
 
    <authentication mode="Forms"> 
      <forms loginUrl="~/Account/LogOn" timeout="2880" /> 
    </authentication> 
 
    <membership> 
      <providers> 
        <remove name="AspNetSqlMembershipProvider"/> 
        <add name="AspNetSqlMembershipProvider" 
             type="System.Web.Security.SqlMembershipProvider, 
            System.Web, Version=2.0.0.0, Culture=neutral,                    
            PublicKeyToken=b03f5f7f11d50a3a"     
             connectionStringName="ApplicationServices" 
             applicationName="/" 
        /> 
      </providers> 
    </membership> 
  </system.web> 
</configuration> 
複製程式碼

這是一個精簡過的Web.config檔案,只包含兩個配置節:connection string和membership database,使用時編輯適合自己的環境。

建立一個自定義的Membership提供者

通過從抽象類MembershipProvider派生可以建立自定義的membership提供者,如下所示:

View Code 複製程式碼
using System; 
using System.Web.Security; 
using System.Collections.Generic; 
 
namespace MvcApp.Infrastructure { 
 
    public class SiteMember { 
        public string UserName { get; set; } 
        public string Password { get; set; } 
    } 
 
    public class CustomMembershipProvider : MembershipProvider { 
        private static List<SiteMember> Members = new List<SiteMember> { 
            new SiteMember { UserName = "adam", Password = "secret" }, 
            new SiteMember { UserName = "steve", Password = "shhhh" } 
        }; 
 
        public override bool ValidateUser(string username, string password) { 
            return Members.Exists(m => m.UserName == username && m.Password == password); 
        } 
 
        ... ... 
    } 
}
複製程式碼

上面的Provider使用一個靜態的使用者和密碼列表來執行驗證,為了簡單說明原理,這裡忽略了除ValidateUser之外的其他的方法。接著在配置檔案註冊自定義的Provider:

View Code 複製程式碼
<configuration>     
  <system.web> 
    <authentication mode="Forms"> 
      <forms loginUrl="~/Account/LogOn" timeout="2880" /> 
    </authentication> 
 
    <membership defaultProvider="MyMembershipProvider"> 
      <providers> 
        <clear/> 
        <add name="MyMembershipProvider" 
             type="MvcApp.Infrastructure.CustomMembershipProvider"/> 
      </providers> 
    </membership> 
  </system.web> 
</configuration> 
複製程式碼

下面擷取一個在SportsStore裡面使用的例子:

View Code 複製程式碼
using System.Web.Security; 
using SportsStore.WebUI.Infrastructure.Abstract; 
 
namespace SportsStore.WebUI.Infrastructure.Concrete { 
    public class FormsAuthProvider : IAuthProvider { 
 
        public bool Authenticate(string username, string password) { 
 
            bool result = Membership.ValidateUser(username, password); 
            if (result) { 
                FormsAuthentication.SetAuthCookie(username, false); 
            }             return result; 
        } 
    } 
} 
複製程式碼

建立並使用角色

前面介紹了驗證,還有另外一個常見的安全需求就是授權——決定使用者在驗證之後能夠做什麼。ASP.NET使用基於角色的授權機制,這意味著actions是限制在角色裡面的,屬於角色的使用者能夠執行相應的action方法。角色通過唯一的字串值來表示,例如可以定義如下三個角色:ApprovedMember  CommentsModerator  SiteAdministrator。每一個角色都是完全獨立的,沒有層級關係。ASP.NET平臺期望我們通過provider模型來使用角色,提供了常用的API。當然也可以自定義:

建立SqlRoleProvider

SqlRoleProvider類是對SqlMembershipProvider的補足,使用了同樣的資料庫。使用網際網路應用程式模版建立MVC程式,VS會自動新增相應的元素來建立SqlRoleProvider,如下:

View Code
<configuration> 
  <system.web> 
    <roleManager enabled="false"> 
      <providers> 
        <clear/> 
        <add name="AspNetSqlRoleProvider"  
             type="System.Web.Security.SqlRoleProvider"  
             connectionStringName="ApplicationServices"  
             applicationName="/" /> 
        <add name="AspNetWindowsTokenRoleProvider"  
             type="System.Web.Security.WindowsTokenRoleProvider"  
             applicationName="/" /> 
      </providers> 
    </roleManager> 
</configuration> 

兩個Role Provider註冊了,預設都沒有啟用。要建立SqlRoleProvider必須修改roleManager元素
如:<roleManager enabled="true" defaultProvider="AspNetSqlRoleProvider">

管理Roles

可以使用管理members的方法管理roles,可以使用VS裡面的ASP.NET圖形化的配置工具來操作。下面介紹建立自定義的Roles Provider:

View Code 複製程式碼
using System; 
using System.Web.Security; 
 
namespace MvcApp.Infrastructure { 
 
    public class CustomRoleProvider : RoleProvider { 
 
        public override string[] GetRolesForUser(string username) { 
 
            if (username == "adam") { 
                return new string[] { "CommentsModerator", "SiteAdministrator" }; 
            } else if (username == "steve") { 
                return new string[] { "ApprovedUser", "CommentsModerator" }; 
            } else { 
                return new string[] { }; 
            } 
        } 
 
        ...... 
    } 
} 
複製程式碼

通過從RoleProvider派生來定義自己的提供者,我只需要實現GetRolesForUser方法就可以使用了。同樣,建立以後需要註冊:

View Code
<configuration> 
  <system.web> 
 
    <authentication mode="Forms"> 
      <forms loginUrl="~/Account/LogOn" timeout="2880" /> 
    </authentication> 
 
    <membership defaultProvider="MyMembershipProvider"> 
      <providers> 
        <clear/> 
        <add name="MyMembershipProvider" 
             type="MvcApp.Infrastructure.CustomMembershipProvider"/> 
      </providers> 
    </membership> 
 
    <roleManager enabled="true" defaultProvider="MyRoleProvider">       <providers> 
        <clear/> 
        <add name="MyRoleProvider" 
             type="MvcApp.Infrastructure.CustomRoleProvider"/> 
      </providers>         
    </roleManager> 
    
  </system.web> 
</configuration> 

建立並使用Profiles

Membership記錄我們的使用者,roles記錄允許使用者做什麼操作。如果我們想記錄使用者個性化的一些資料,如會員積分等等資訊。那麼通常可以使用Profiles,這對於使用SqlMembershipProvider的小應用程式來說是一個非常有吸引力的功能。

建立SqlProfileProvider

使用網際網路應用程式模版建立一個新的MVC程式,包含在Web.config裡面的元素建立SqlProfileProvider。如下:

View Code
<configuration> 
  <system.web> 
    <profile> 
      <providers> 
        <clear/> 
        <add name="AspNetSqlProfileProvider"  
             type="System.Web.Profile.SqlProfileProvider"  
             connectionStringName="ApplicationServices"  
             applicationName="/" /> 
      </providers> 
    </profile> 
  </system.web> 
</configuration> 

配置/讀/寫Profile資料

在使用Profile之前,我們必須定義Profile的資料結構,可以在Web.config的profile節裡面新增屬性元素如下:

View Code
<profile> 
  <providers> 
    <clear/> 
    <add name="AspNetSqlProfileProvider"  
         type="System.Web.Profile.SqlProfileProvider"  
         connectionStringName="ApplicationServices"  
         applicationName="/" /> 
  </providers> 
  <properties> 
    <add name="Name" type="String"/> 
    <group name="Address"> 
      <add name="Street" type="String"/> 
      <add name="City" type="String"/> 
      <add name="ZipCode" type="String"/> 
      <add name="State" type="String"/> 
    </group> 
  </properties> 
</profile> 

上面定義的屬性都是String型別的,但是Profile支援任何能夠序列化的.NET型別。在我們使用Web Forms的時候,通過跟profiles屬性一致的代理物件來訪問profile資料。這個在MVC裡是不可行的,但是我們可以使用HttpContext.Profile屬性來訪問。如:

View Code
public ActionResult Index() { 
 
    ViewBag.Name = HttpContext.Profile["Name"]; 
    ViewBag.City = HttpContext.Profile.GetProfileGroup("Address")["City"]; 
 
    return View(); 
} 
 [HttpPost] 
public ViewResult Index(string name, string city) { 
 
    HttpContext.Profile["Name"] = name; 
    HttpContext.Profile.GetProfileGroup("Address")["City"] = city; 
 
    return View(); 
} 

ASP.NET框架在我們第一次訪問profile資料時,使用profile提供者載入使用者的profile屬性並且在請求結束時通過profile提供者寫回,我們不用顯示的儲存,這個是自動完成的。

啟用匿名的Profiles

預設情況下,profile資料只對經過驗證的使用者可用,沒有登入的使用者訪問時會拋異常。可以通過啟用對你們profile的支援來解決:

View Code
<configuration> 
  <system.web> 
    <anonymousIdentification enabled="true"/> 
    <profile> 
      <providers> 
        <clear/> 
        <add name="AspNetSqlProfileProvider"  
             type="System.Web.Profile.SqlProfileProvider"  
             connectionStringName="ApplicationServices"  
             applicationName="/" /> 
      </providers> 
      <properties> 
        <add name="Name" type="String" allowAnonymous="true"/> 
        <group name="Address"> 
          <add name="Street" type="String"/> 
          <add name="City" type="String" allowAnonymous="true"/> 
          <add name="ZipCode" type="String"/> 
          <add name="State" type="String"/> 
        </group> 
      </properties> 
    </profile> 
  </system.web> 
</configuration> 

當匿名的身份標識啟用時,ASP.NET框架將會通過一個.ASPXANONYMOUS的cookie來記錄你們使用者,並且cookie的過期時間是10000分鐘(70天左右)。啟用了匿名profile以後,沒有驗證的使用者也可以讀寫profile資料,沒一個沒有驗證的使用者會自動為他們建立一個賬戶並儲存在profile資料庫裡面。

建立自定義的Profile提供者(Profile Provider)

通過從ProfileProvider派生來建立自定義的profile provider。如下:

View Code
using System; 
using System.Collections.Generic; 
using System.Configuration; 
using System.Linq; 
using System.Web.Profile; 
 
namespace MvcApp.Infrastructure { 
    public class CustomProfileProvider : ProfileProvider { 
        private IDictionary<string, IDictionary<string, object>> data =  
            new Dictionary<string, IDictionary<string, object>>(); 
 
        public override SettingsPropertyValueCollection GetPropertyValues( 
            SettingsContext context, SettingsPropertyCollection collection) { 
 
            SettingsPropertyValueCollection result = new SettingsPropertyValueCollection(); 
 
            IDictionary<string, object> userData; 
            bool userDataExists  
                = data.TryGetValue((string)context["UserName"], out userData); 
 
            foreach (SettingsProperty prop in collection) { 
                SettingsPropertyValue spv = new SettingsPropertyValue(prop); 
                if (userDataExists) { 
                    spv.PropertyValue = userData[prop.Name]; 
                } 
                result.Add(spv); 
            } 
            return result; 
        } 
 
        public override void SetPropertyValues(SettingsContext context,  
            SettingsPropertyValueCollection collection) { 
 
            string userName = (string)context["UserName"]; 
            if (!string.IsNullOrEmpty(userName)) { 
                data[userName] = collection 
                    .Cast<SettingsPropertyValue>() 
                    .ToDictionary(x => x.Name, x => x.PropertyValue);             } 
        } 
 
        ...... 
    } 
} 

上面的provider非常簡單,僅僅將資料儲存在記憶體。接著需要註冊:

View Code 複製程式碼
<configuration> 
  <connectionStrings> 
    <add name="ApplicationServices" 
          connectionString="data Source=TITAN\SQLEXPRESS; 
                          Initial Catalog=aspnetdb; 
                          Persist Security Info=True; 
                          User ID=adam;Password=adam" 
          providerName="System.Data.SqlClient" /> 
  </connectionStrings> 
 
  <system.web> 
    <authentication mode="Forms"> 
      <forms loginUrl="~/Account/LogOn" timeout="2880" /> 
    </authentication> 
 
    <membership defaultProvider="MyMembershipProvider"> 
      <providers> 
        <clear/> 
        <add name="MyMembershipProvider" 
             type="MvcApp.Infrastructure.CustomMembershipProvider"/> 
      </providers> 
    </membership> 
 
    <roleManager enabled="true" defaultProvider="MyRoleProvider"> 
      <providers> 
        <clear/> 
        <add name="MyRoleProvider" 
             type="MvcApp.Infrastructure.CustomRoleProvider"/> 
      </providers> 
    </roleManager> 
 
    <profile enabled="true" defaultProvider="MyProfileProvider"> 
      <providers> 
        <clear/> 
        <add name="MyProfileProvider"  
             type="MvcApp.Infrastructure.CustomProfileProvider"  
             />      
</providers> 
      <properties> 
        <add name="Name" type="String"/> 
        <group name="Address"> 
          <add name="Street" type="String"/> 
          <add name="City" type="String"/> 
          <add name="ZipCode" type="String"/>