ASP.NET Web API 可非常方便地創建基於 HTTP 的 Services,這些服務可以非常方便地被幾乎任何形式的平台和客戶端(如瀏覽器、Windows客戶端、Android設備、IOS等)所訪問,它可根據請求類型自動提供 JSON、XML 等類型的響應內容。在移動互聯網逐漸成為主流的背景下,通過 Web API 對外發布基於標准、通用 HTTP 協議的服務來交換數據無疑具有非常大的優勢和吸引力。本文將主要圍繞 ASP.NET Web API 的安全性進行討論。
一、Forms Authentication
Forms 認證基於憑據(Ticket)機制,憑據在登錄時創建,通常會寫入到 cookie 中。Forms 認證可應用在任何類型的ASP.NET 應用程序中,例如:WebForms,MVC,甚至 Web API等。默認的配置是 <authentication mode="None" />,因此為了使用Forms Authentication,通常需要在配置文件中進行配置。
盡管 Forms 認證是 Web 應用程序的首選認證方式,但從 Web API 的安全性來說,其實它並不是一個理想的解決方案,對於非瀏覽器的客戶端來說,做個比喻,就像是穿西裝戴斗笠般不搭調。
最大的問題是在ASP.NET Web API中使用 Forms 認證方式的時候,並不會返回一個 302 代碼(注:302 表示重定向,Web 應用程序中會被自動導航至設定的登錄頁面地址),在 Web API 中使用 Forms 認證方式可參考如下代碼:
1)在 WebApiConfig 文件中修改默認的代碼如下:
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: " DefaultApi ",
routeTemplate: " api/{controller}/{id} ",
defaults: new { id = RouteParameter.Optional }
);
config.Filters.Add( new AuthorizeAttribute());
}
}
2)修改 MVC 的 FilterConfig 默認代碼如下:
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add( new HandleErrorAttribute());
filters.Add( new AuthorizeAttribute());
}
}
通過上述設置,對非授權的資源進行訪問時將返回一個 401 代碼(注:401 表示未經授權的請求),這和在 MVC 中非授權的請求返回到登錄頁面的含義是一樣的。
有童鞋可能會問,為什么不能重定向到登錄頁面呢?其實真正的問題是 Forms 認證本身,它的原理決定了它是面向 Web 應用程序的,它有 Cookie 和重定向等的支持,而 ASP.NET Web API 卻是無狀態的 RESTful 服務,這自然是不適合的。
說到這里,休息一下,溫習下 HTTP 響應的狀態代碼。
HTTP 響應的狀態代碼為三位數字的編號,其中第 1 位定義了狀態代碼的類別:1 開頭的代表信息類、2 開頭的代表操作成功類、3 開頭的代表重定向類、4 開頭的代表客戶端一側的請求錯誤類、5 開頭的代表服務器端一側的錯誤類。
常見的狀態代碼如下:
- 200 OK
- 201 Created
- 204 No Content
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
- 500 Internal Server Error
二、身份(Identify)管理
1、認證(Authentication)和授權(Authorization)
1)認證的方式
身份認證的方式主要有如下三個方面:
- 根據用戶知曉的東西,例如密碼、PIN等
- 根據用戶擁有的東西,例如證書、U盾等
- 根據用戶的生物特征,例如指紋、DNA等
典型的身份認證方式可以為上述的其中一種,其中第一種使用最為普遍,如果一個應用程序需要考慮更高的安全性,需要至少使用上述列表中的兩種身份認證方式。例如銀行卡,本身就屬於第二種認證,但在ATM上取錢時往往還需要密碼,這又使用了第一種認證方式。
2)基本角色的安全
在企業級的應用中,基於角色的安全是最常見的安全模型。在.NET Framework中提供了 Identify 對象,一個 Identify 對象代表了某個用戶,最重要就是這兩個接口:IIdentity 和 IPrincipal,這兩個接口提供了基於角色的訪問控制,其中 IIdentify 代表用戶的身份標識,IPrincipal 代表用戶關聯的身份和角色。IPrincipal 有一個 Identity 屬性和 IsInRole(string) 的方法,該方法判斷當前用戶是否屬於某個角色。
在 .NET 中,每個線程都有一個類型為 IPrincipal 的 CurrentPrincipal 屬性。通常,在身份認證通過后需要創建一個 Principal 對象並賦予主線程的 CurrentPrincipal 屬性,任何新創建的線程會自動創建同樣的 Principal 對象。其實在.NET Framework中已經實現了兩種類型的 Principal。
- GenericIdentity 和 GenericPrincipal,用於自定義的場合。
- WindowsIdentity and WindowsPrincipal,用於基於 Windows 認證的場合。
當然,你也可以自己實現符合自身要求的 Identity 和 Principal。例如在CSLA.NET中就實現了一個自定義的 Identity 和 Principal,可以設置 Serialized 特性,該 Principal 對象可以通過服務從客戶端傳到服務器端進行身份的認證和授權處理,以滿足其 N-Tier 部署下進行身份認證和授權的需要,通過繼承,甚至還可以新增必要的屬性,非常方便。
3)基本聲明(Claims)的安全
典型的基於聲明(Claims)的 Identity 如下:
- 用戶的姓名是誰
- 用戶的電子郵件是什么
- 用戶的性別
- 用戶的年齡是多少
- 該用戶被允許創建新用戶
和前面提到的安全模型相比,在基於聲明的安全模型中,聲明的值必須來自於應用程序所信任的某個實體或機構(通常用戶是/不是什么通過第三方驗證,可以/不可以做什么由應用程序自身確定)。
基於聲明(Claims)的安全模型往往更加貼近現實生活,可以簡化某單個應用程序的身份驗證邏輯,因為這些應用程序不用再重復提供諸如創建賬戶、密碼、密碼重置等機制(注:這些邏輯統一由第三方應用程序完成)。
同時聲明(Claims)的安全模型也不必要求某個用戶多次登錄到多個應用程序,大大簡化了用戶的身份驗證過程。
因此,聲明(Claims)的安全非常適合於諸如OAuth等第三方授權的方式和雲計算環境。
基於聲明(Claims)的安全的實例代碼如下:
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, " Yellbuy "),
new Claim(ClaimTypes.Email, " yb@yellbuy.com "),
new Claim(ClaimTypes.Role, " 系統管理員 "),
new Claim(ClaimTypes.Role, " 系統操作員 ")
};
var id = new ClaimsIdentity(claims, " Dummy "); // Non-empty string is needed as authentication type
var principal = new ClaimsPrincipal( new[] { id });
Thread.CurrentPrincipal = principal;
CreateUser(); // Call the method that needs authorization
}
[PrincipalPermission(SecurityAction.Demand, Role = " 系統管理員 ")] // Declarative
private static void CreateUser()
{
new PrincipalPermission( null, " NewUser ").Demand(); // Imperative
Console.WriteLine(Thread.CurrentPrincipal.IsInRole( " 系統管理員 "));
Console.WriteLine( " 用戶已創建 ");
}
很明顯,用戶是/不是什么通常由第三方確定,因此很多第三方提供了所謂的安全令牌(Security Token)服務。目前有三種標准的令牌(Token)格式,它們是:SAML(安全斷言標記語言)、SWT(簡單 Web 令牌)、JWT(JSON Web 令牌)。三種令牌格式對比如下:
|
|
SAML |
SWT |
JWT |
| 表現形式 |
XML |
HTML Form encoding |
JSON |
| 處理方式 |
SOAP |
REST |
REST |
| 直接支持WIF |
是 |
否 |
否 |
| 協議 |
WS-Trust and WS-Federation
|
OAuth 2.0 |
OAuth 2.0 |
| 通常的載體 |
HTTP body or URL |
HTTP Auth header (Bearer) |
HTTP Auth header (Bearer) |
| 支持簽名 |
是,非對稱密鑰-X509證書 |
是, HMAC SHA-256 (使用對稱密鑰) |
是,支持對稱密鑰和非對稱密鑰 |
2、Basic Authentication
在Web Api中不能不提到 Basic Authentication。Basic Authentication 是 HTTP 規范的一部分,它非常的基礎和簡單。主要工作原理如下:
- 客戶端向服務器發送資源請求。
- 假如資源請求需要進行身份認證,則服務器發送回一個 401 代碼 - 未經授權響應狀態碼和響應頭 WWW-Authenticate: Basic。這個響應報頭還可以包含一個字符串,它是服務器所需要的一個有效的憑據來進行后續成功處理請求的唯一標識。
- 現在客戶端發送授權請求信息,例如:WWW-Authenticate: Basic YeMfc1mgUdV2cMj0U0Kjp2C=。授權請求頭值僅僅是一個用戶ID和密碼進行base64編碼后的字符串,然后使用一個冒號進行分割,並且不以任何方式加密。
- 假如憑據有效,則服務器返回 200 響應狀態碼。
因為 Base Authentication 的安全性較差,但對於無 Cookie 的 Web Api 來說,應用上非常的簡單和方便。
Base Authentication 最大的缺點是憑據會被瀏覽器緩存——直到你關閉瀏覽器為止。如果你已經對某個URI獲得了授權,瀏覽器就會在授權頭發送相應的憑據,這使其更容易受到跨站點請求偽造(CSRF)攻擊
Base Authentication 通常需要使用HTTPS方式進行加密處理。
三、YbRapidSolution for MVC 中的 Authentication
在 YbRapidSolution for MVC 的安全解決方案中,采用了 Base Authentication 和 Form Authentication 相互補充的處理方式,使用 Attribute 方式進行請求的 Authentication 處理。Base Authentication 主要面向非Web應用的處理請求,Form Authentication 則對 MVC 的 Controller 的請求進行處理,核心代碼如下:
{
// 匿名用戶的權限驗證
AuthenticationHeaderValue authValue = request.Headers.Authorization;
// Base Authenticated 是否無效
var isNotValidatedBaseAuthenticated = authValue == null
|| string.IsNullOrWhiteSpace(authValue.Parameter)
|| string.IsNullOrWhiteSpace(authValue.Scheme)
|| authValue.Scheme.Equals(BasicAuthResponseHeaderValue);
// 客戶端授權標記 有效,則創建Principal並附加到HttpContext.Current.User
if (!isNotValidatedBaseAuthenticated)
{
string[] parsedHeader = ParseAuthorizationHeader(authValue.Parameter);
if (parsedHeader != null)
{
IPrincipal principal = null;
if (TryCreatePrincipal(parsedHeader[ 0], parsedHeader[ 1], out principal))
{
HttpContext.Current.User = principal;
}
}
}
// HttpContent未授權,則檢查匿名用戶的權限
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
string roleKey = string.Format(CacheKeyList.PERMISSION_ROOT_BY_ROLE_KEY, " EveryOne ");
var permissionKeys = _cacheManager.Get(roleKey, () =>
{
var permissionsOfEveryOne = PermissionApi.GetPermissionsInRole( " EveryOne ");
if (permissionsOfEveryOne == null || permissionsOfEveryOne.Length == 0)
return new string[] { };
var list = permissionsOfEveryOne.Select(c => c.PermissionKey).ToArray();
return list;
});
return CheckPermission(request, permissionKeys);
}
// 未設置權限Key,則任何已授權用戶均可訪問
if ( string.IsNullOrWhiteSpace(PermissionKey)) return true;
// 登錄用戶的權限驗證
string userKey = string.Format(CacheKeyList.PERMISSION_CHILDREN_BY_USER_KEY, HttpContext.Current.User.Identity.Name);
var allowPermissionKeys = _cacheManager.Get(userKey, () =>
{
var permissions = PermissionApi.GetPermissionsForUser();
if (permissions == null || permissions.Length == 0)
return new string[] { };
var list = permissions.Select(c => c.PermissionKey).ToArray();
return list;
});
return CheckPermission(request, allowPermissionKeys);
}
private string[] ParseAuthorizationHeader( string authHeader)
{
string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader)).Split( new[] { ' : '});
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[ 0]) || string.IsNullOrEmpty(credentials[ 1]))
return null;
return credentials;
}
從上述代碼中可以看出,在 YbRapidSolution for MVC 首先進行 Base Authentication,如果不通過,繼續進行 Form Authentication;如果 Base Authentication 通過,則創建 Form 下的 Principal 然后按 Form Authentication 的方式進行統一處理,這可以確保任何類型的客戶端都能進行相應的 Authentication 處理,充分發揮出 Web Api 的特性,在為各種類型的客戶端和設備提供 API 支持的同時也提供相應的安全保障。
附1:YbRapidSoluton for MVC 在線 Demo 地址:http://mvcdemo.yellbuy.com/。
附2:最新發布的 YbRapidSolution for WinForm Demo下載:運行環境-.NET 4.0。服務層部署在 Internet
上,,可直接運行;如需在本地部署,除了安裝數據庫外,就是修改配置文件,這里不再詳述。
附3:最新發布的 YbSoftwareFactory V2 下載,運行環境-.NET 4.0。
