本文共 10915 字,大约阅读时间需要 36 分钟。
上一节我们详细讲解了认证及其基本信息,这一节我们通过两种不同方式来实现认证,并且分析如何合理的利用这两种方式,文中涉及到的基础知识,请参看上一篇文中,就不再叙述废话。
对于所谓的认证说到底就是安全问题,在Web API中有多种方式来实现安全,【accepted】方式来处理基于IIS的安全(通过上节提到的WindowsIdentity依赖于HttpContext和IIS认证)或者在Web API里通过使用Web API中的消息处理机制,但是如果我们想应用程序运行在IIS之外此时Windows Idenitity这一方式似乎就不太可能了,同时在Web API中本身就未提供如何处理认证的直接方式,我们不得不自定义来实现认证功能,同时这也是我们所推荐的方式,自己动手,丰衣足食。
通过上述图片的粗略信息我们可以看出在请求到Action方法之间要经过Web API消息处理管道,在请求到目标元素之前要经过HttpMessageHandler和认证过滤器,所以我们可以通过这两者来自定义实现认证。下面我们一一来看。
我们自定义一个认证身份(用户名和密码)的类,那么此类必须也就要继承于 GenericIdentity ,既然是基于基础验证,那么类型当然也就是Basic了。
public class BasicAuthenticationIdentity : GenericIdentity { public string Password { get; set; } public BasicAuthenticationIdentity(string name, string password) : base(name, "Basic") { this.Password = password; } }
我们要自定义一个认证过滤器特性,并继承 AuthorizationFilterAttribute ,此时会变成如下:
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;
一切已经就绪,此时在重写方法中进行相应的调用即可,如下:
[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 {....}
至此对于其认证方式就已经完全实现,接下来我们通过【搜狗浏览器】来验收我们的成果。
看到如下认证其用户名和密码的图片,我们知道我们成功了一半
我们点击取消,观察是否返回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 TaskSendAsync(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
至此完美结束。
转载地址:http://anbsx.baihongyu.com/