[翻译]创建ASP.NET WebApi RESTful 服务(8)

本章讨论创建安全的WebApi服务,到目前为止,我们实现的API都是基于未加密的HTTP协议,大家都知道在Web中传递身份信息必须通过HTTPS,接下来我们来实现这一过程。

使用HTTPS

其实可以通过IIS配置,将整个WebApi的访问都配置为Https,但实际上,如果希望只是对部分方法进行认证,那就必须通过认证身份信息进行处理。

下面介绍通过Filter来实现这一过程,如果身份认证不通过,就返回一条信息,提示访问者通过https进行访问。

   1:  public class ForceHttpsAttribute : AuthorizationFilterAttribute
   2:      {
   3:          public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
   4:          {
   5:              var request = actionContext.Request;
   6:   
   7:              if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
   8:              {
   9:                  var html = "<p>Https is required</p>";
  10:   
  11:                  if (request.Method.Method == "GET")
  12:                  {
  13:                      actionContext.Response = request.CreateResponse(HttpStatusCode.Found);
  14:                      actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");
  15:   
  16:                      UriBuilder httpsNewUri = new UriBuilder(request.RequestUri);
  17:                      httpsNewUri.Scheme = Uri.UriSchemeHttps;
  18:                      httpsNewUri.Port = 443;
  19:   
  20:                      actionContext.Response.Headers.Location = httpsNewUri.Uri;
  21:                  }
  22:                  else
  23:                  {
  24:                      actionContext.Response = request.CreateResponse(HttpStatusCode.NotFound);
  25:                      actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");
  26:                  }
  27:   
  28:              }
  29:          }
  30:      }

通过actionContext参数获取Request和Response对象,对URI进行检查,如果不是以HTTPS开头,就返回443代码。

使用的方法有两种,一种是在WebAPIConfig中注册为全局的Attribute。

   1:    public static void Register(HttpConfiguration config)
   2:      {
   3:          config.Filters.Add(new ForceHttpsAttribute());
   4:      }

另一种是对制定的类或者方法进行拦截。

   1:  //Enforce HTTPS on the entire controller
   2:      [Learning.Web.Filters.ForceHttps()]
   3:      public class CoursesController : BaseApiController
   4:      {
   5:          //Enforce HTTPS on POST method only
   6:          [Learning.Web.Filters.ForceHttps()]
   7:          public HttpResponseMessage Post([FromBody] CourseModel courseModel)
   8:          {
   9:   
  10:          }
  11:      }

通过Basic Authentication进行认证

当前所有的API都是Public的,网络上的任意用户都可以请求资源。实际的项目中肯定要对访问者进行必要的限制。

  • 假设对于客户端的请求“http://{your_port}/api/students/{userName}”,当有正确的身份信息时,我们返回username为 “TaiseerJoudeh”的信息,否则,返回错误;
  • 如果请求是POST类型,如“http://{your_port}/api/courses/2/students/{userName}”,那么修改者也必须通过身份认证才可以进行修改。

什么是Basic Authentication

Basic Authentication提供了一种在Http Request被处理之前先行进行身份认证的模式,它在防止Dos等方面具有重要作用。Basic Authentication要求在请求时必须在Http Header提供基于Base64编码的用户名和密码信息。这种认证一般应该通过HTTPS来实现。

   1:   public class LearningAuthorizeAttribute : AuthorizationFilterAttribute
   2:      {
   3:   
   4:          [Inject]
   5:          public LearningRepository TheRepository { get; set; }
   6:   
   7:          public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
   8:          {
   9:              //Case that user is authenticated using forms authentication
  10:              //so no need to check header for basic authentication.
  11:              if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
  12:              {
  13:                  return;
  14:              }
  15:   
  16:              var authHeader = actionContext.Request.Headers.Authorization;
  17:   
  18:              if (authHeader != null)
  19:              {
  20:                  if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) &&
  21:                  !String.IsNullOrWhiteSpace(authHeader.Parameter))
  22:                  {
  23:                      var credArray = GetCredentials(authHeader);
  24:                      var userName = credArray[0];
  25:                      var password = credArray[1];
  26:   
  27:                      if (IsResourceOwner(userName, actionContext))
  28:                      {
  29:                          //You can use Websecurity or asp.net memebrship provider to login, for
  30:                          //for he sake of keeping example simple, we used out own login functionality
  31:                          if (TheRepository.LoginStudent(userName, password))
  32:                          {
  33:                              var currentPrincipal = new GenericPrincipal(new GenericIdentity(userName), null);
  34:                              Thread.CurrentPrincipal = currentPrincipal;
  35:                              return;
  36:                          }
  37:                      }
  38:                  }
  39:              }
  40:   
  41:              HandleUnauthorizedRequest(actionContext);
  42:          }
  43:   
  44:          private string[] GetCredentials(System.Net.Http.Headers.AuthenticationHeaderValue authHeader)
  45:          {
  46:   
  47:              //Base 64 encoded string
  48:              var rawCred = authHeader.Parameter;
  49:              var encoding = Encoding.GetEncoding("iso-8859-1");
  50:              var cred = encoding.GetString(Convert.FromBase64String(rawCred));
  51:   
  52:              var credArray = cred.Split(':');
  53:   
  54:              return credArray;
  55:          }
  56:   
  57:          private bool IsResourceOwner(string userName, System.Web.Http.Controllers.HttpActionContext actionContext)
  58:          {
  59:              var routeData = actionContext.Request.GetRouteData();
  60:              var resourceUserName = routeData.Values["userName"] as string;
  61:   
  62:              if (resourceUserName == userName)
  63:              {
  64:                  return true;
  65:              }
  66:              return false;
  67:          }
  68:   
  69:          private void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
  70:          {
  71:              actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
  72:   
  73:              actionContext.Response.Headers.Add("WWW-Authenticate",
  74:              "Basic Scheme='eLearning' location='http://localhost:8323/account/login'");
  75:   
  76:          }
  77:      }

上述代码实现了以下逻辑:

  1. 从Request Header中获取认证信息;
  2. 确认Header的Schema被设置为basic,并包含了正确的Base64编码字符串;
  3. 将字符串转换为“username:password”格式,获取各自内容;
  4. 验证身份信息,确定是否具有相应权限;
  5. 如果Credentials验证无误,设置当前线程的Identity信息,以便子请求能够重用;
  6. 如果验证有无,则返回401错误。

现在可以对指定的方法进行标记。

   1:      public class StudentsController : BaseApiController
   2:      {
   3:          [LearningAuthorizeAttribute]
   4:          public HttpResponseMessage Get(string userName)
   5:          {
   6:   
   7:          }
   8:      }
   9:   
  10:      public class EnrollmentsController : BaseApiController
  11:      {
  12:          [LearningAuthorizeAttribute]
  13:          public HttpResponseMessage Post(int courseId, [FromUri]string userName, [FromBody]Enrollment enrollment)
  14:          {
  15:   
  16:          }
  17:      }
现在分别用FireFox和Fiddler进行测试。假设路径如下:http://localhost:{your_port}/api/students/TaiseerJoudeh
  • FireFox:返回401,因为没有身份信息,一个弹出框会弹出,要求输入用户名和密码。输入正确的用户名和密码,就可以看到返回的json信息。而接下来的子请求,则无需再进行认证;
  • Fiddler:需要先拼接一个Base64加密“username:password”字符串,可以通过这里实现。注意:这不能算是加密,真正的加密必须通过HTTPS实现。当用户名密码正确时,会返回200和正确的json信息。

 

来源:http://bitoftech.net/2013/12/03/enforce-https-asp-net-web-api-basic-authentication/

posted @ 2014-04-13 18:09  laughter  阅读(619)  评论(0编辑  收藏  举报