上一節咱們詳細講解了認證及其基本信息,這一節咱們經過兩種不一樣方式來實現認證,而且分析如何合理的利用這兩種方式,文中涉及到的基礎知識,請參看上一篇文中,就再也不廢敘述廢話。html
對於所謂的認證說到底就是安全問題,在Web API中有多種方式來實現安全,【accepted】方式來處理基於IIS的安全(經過上節提到的WindowsIdentity依賴於HttpContext和IIS認證)或者在Web API裏經過使用Web API中的消息處理機制,可是若是咱們想應用程序運行在IIS以外此時Windows Idenitity這一方式彷佛就不太可能了,同時在Web API中自己就未提供如何處理認證的直接方式,咱們不得不自定義來實現認證功能,同時這也是咱們所推薦的方式,本身動手,豐衣足食。瀏覽器
經過上述圖片的粗略信息咱們能夠看出在請求到Action方法之間要通過Web API消息處理管道,在請求到目標元素以前要通過HttpMessageHandler和認證過濾器,因此咱們能夠經過這二者來自定義實現認證。下面咱們一一來看。安全
咱們自定義一個認證身份(用戶名和密碼)的類,那麼此類必須也就要繼承於 GenericIdentity ,既然是基於基礎驗證,那麼類型固然也就是Basic了。併發
1
2
3
4
5
6
7
8
9
|
public
class
BasicAuthenticationIdentity : GenericIdentity
{
public
string
Password {
get
;
set
; }
public
BasicAuthenticationIdentity(
string
name,
string
password)
:
base
(name,
"Basic"
)
{
this
.Password = password;
}
}
|
咱們要自定義一個認證過濾器特性,並繼承 AuthorizationFilterAttribute ,此時會變成以下:ide
1
2
3
4
5
|
public
class
BasicAuthenticationFilter : AuthorizationFilterAttribute
{
public
override
void
OnAuthorization(HttpActionContext actionContext)
{}
}
|
那麼在這個重寫的方法咱們應該寫什麼呢?咱們慢慢來分析!請往下看。函數
string authParameter = null; var authValue = actionContext.Request.Headers.Authorization; //actionContext:Action方法請求上下文 if (authValue != null && authValue.Scheme == "Basic") authParameter = authValue.Parameter; //authparameter:獲取請求中通過Base64編碼的(用戶:密碼) if (string.IsNullOrEmpty(authParameter)) return null;
authParameter = Encoding.Default.GetString(Convert.FromBase64String(authParameter)); //對編碼的參數進行解碼 var authToken = authParameter.Split(':'); //解碼後的參數格式爲(用戶名:密碼)將其進行分割 if (authToken.Length < 2) return null; return new BasicAuthenticationIdentity(authToken[0], authToken[1]); //將分割的用戶名和密碼傳遞給此類構造函數進行初始化
public virtual BasicAuthenticationIdentity ParseHeader(HttpActionContext actionContext) { string authParameter = null; var authValue = actionContext.Request.Headers.Authorization; if (authValue != null && authValue.Scheme == "Basic") authParameter = authValue.Parameter; if (string.IsNullOrEmpty(authParameter)) return null; authParameter = Encoding.Default.GetString(Convert.FromBase64String(authParameter)); var authToken = authParameter.Split(':'); if (authToken.Length < 2) return null; return new BasicAuthenticationIdentity(authToken[0], authToken[1]); }
void Challenge(HttpActionContext actionContext) { var host = actionContext.Request.RequestUri.DnsSafeHost; actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized); actionContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", host)); }
public virtual bool OnAuthorize(string userName, string userPassword, HttpActionContext actionContext) { if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(userPassword)) return false; else return true; }
var principal = new GenericPrincipal(identity, null); Thread.CurrentPrincipal = principal; //下面是針對ASP.NET而設置 //if (HttpContext.Current != null) // HttpContext.Current.User = principal;
一切已經就緒,此時在重寫方法中進行相應的調用便可,以下:this
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] public class BasicAuthenticationFilter : AuthorizationFilterAttribute { public override void OnAuthorization(HttpActionContext actionContext) { var userIdentity = ParseHeader(actionContext); if (userIdentity == null) { Challenge(actionContext); return; } if (!OnAuthorize(userIdentity.Name, userIdentity.Password, actionContext)) { Challenge(actionContext); return; } var principal = new GenericPrincipal(userIdentity, null); Thread.CurrentPrincipal = principal; base.OnAuthorization(actionContext); }
自定義 CustomBasicAuthenticationFilter 並繼承於 BasicAuthenticationFilter ,重寫其虛方法。編碼
public class CustomBasicAuthenticationFilter : BasicAuthenticationFilter { public override bool OnAuthorize(string userName, string userPassword, HttpActionContext actionContext) { if (userName == "xpy0928" && userPassword == "cnblogs") return true; else return false; } }
註冊自定義認證特性並進行調用加密
config.Filters.Add(new CustomBasicAuthenticationFilter()); [CustomBasicAuthenticationFilter] public class ProductController : ApiController {....}
至此對於其認證方式就已經徹底實現,接下來咱們經過【搜狗瀏覽器】來驗收咱們的成果。spa
看到以下認證其用戶名和密碼的圖片,咱們知道咱們成功了一半
咱們點擊取消,觀察是否返回401並添加質詢頭即WWW-Authenticate,如咱們所料
咱們輸入正確的用戶名和密碼再試試看,結果認證成功,以下:
咱們知道HttpMessageHandler是Web API中請求-響應中的消息處理管道的重要角色,可是真正實現管道串聯的是DelegatingHandler,若你不懂Web API消息管道,請參考前面系列文章,因此咱們能夠自定義管道來進行攔截經過繼承DelegatingHandler。下面咱們一步步來實現基於此管道的認證。
和第一種方法一致再也不敘述。
這一步固然是自定義管道進行處理並繼承DelegatingHandler,重載在此類中的SendAsync方法,經過得到其請求並處理從而進行響應,若不懂此類中的具體實現,請參看前面系列文章。
public virtual BasicAuthenticationIdentity ParseHeader(HttpRequestMessage requestMessage) { string authParameter = null; var authValue = requestMessage.Headers.Authorization; if (authValue != null && authValue.Scheme == "Basic") authParameter = authValue.Parameter; if (string.IsNullOrEmpty(authParameter)) return null; authParameter = Encoding.Default.GetString(Convert.FromBase64String(authParameter)); var authToken = authParameter.Split(':'); if (authToken.Length < 2) return null; return new BasicAuthenticationIdentity(authToken[0], authToken[1]); }
void Challenge(HttpRequestMessage request,HttpResponseMessage response) { var host = request.RequestUri.DnsSafeHost; response.Headers.Add(authenticationHeader, string.Format("Basic realm=\"{0}\"", host)); }
public class BasicAuthenticationHandler : DelegatingHandler { private const string authenticationHeader = "WWW-Authenticate"; protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var crendentials = ParseHeader(request); if (crendentials != null) { var identity = new BasicAuthenticationIdentity(crendentials.Name, crendentials.Password); var principal = new GenericPrincipal(identity, null); Thread.CurrentPrincipal = principal; //針對於ASP.NET設置 //if (HttpContext.Current != null) // HttpContext.Current.User = principal; } return base.SendAsync(request, cancellationToken).ContinueWith(task => { var response = task.Result; if (crendentials == null && response.StatusCode == HttpStatusCode.Unauthorized) { Challenge(request, response); } return response; }); } void Challenge(HttpRequestMessage request,HttpResponseMessage response) { var host = request.RequestUri.DnsSafeHost; response.Headers.Add(authenticationHeader, string.Format("Basic realm=\"{0}\"", host)); } public virtual BasicAuthenticationIdentity ParseHeader(HttpRequestMessage requestMessage) { string authParameter = null; var authValue = requestMessage.Headers.Authorization; if (authValue != null && authValue.Scheme == "Basic") authParameter = authValue.Parameter; if (string.IsNullOrEmpty(authParameter)) return null; authParameter = Encoding.Default.GetString(Convert.FromBase64String(authParameter)); var authToken = authParameter.Split(':'); if (authToken.Length < 2) return null; return new BasicAuthenticationIdentity(authToken[0], authToken[1]); } }
上述咱們自定義的BasicAuthenticationFilter此時就得繼承 AuthorizeAttribute 該特性也是繼承於上述的 AuthorizationFilterAttribute ,咱們須要利用AuthorizeAttribute中的 IsAuthorized 方法來驗證當前線程中的Principal是否已經被受權。
public class BasicAuthenticationFilter : AuthorizeAttribute { protected override bool IsAuthorized(HttpActionContext actionContext) { var identity = Thread.CurrentPrincipal.Identity; if (identity != null && HttpContext.Current != null) identity = HttpContext.Current.User.Identity; if (identity != null && identity.IsAuthenticated) { var basicAuthIdentity = identity as BasicAuthenticationIdentity;
//能夠添加其餘須要的業務邏輯驗證代碼 if (basicAuthIdentity.Name == "xpy0928" && basicAuthIdentity.Password == "cnblogs") { return true; } } return false; } }
經過 IsAuthorized 方法返回值來看,若爲false,則返回401狀態碼,此時會觸發 BasicAuthenticationHandler 中的質詢,而且此方法裏面主要是咱們須要添加認證用戶的業務邏輯代碼。同時咱們也說過咱們第一種方法自定義實現的過濾器特性是 AuthorizationFilterAttribute (若是咱們有更多邏輯使用這個特性是個不錯的選擇),而在這裏是 AuthorizeAttribute (對於驗證用戶而且返回bool值使用此過濾器特性是個不錯的選擇)。
註冊自定義管道以及認證過濾器特性
config.MessageHandlers.Add(new BasicAuthenticationHandler()); config.Filters.Add(new BasicAuthenticationFilter());
[BasicAuthenticationFilter] public class ProductController : ApiController {.....}
下面咱們經過【360極速瀏覽器】來驗收成果。點擊按鈕直接請求控制器
接下來取消,是否返回401
至此完美結束。