本章讨论创建安全的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: }上述代码实现了以下逻辑:
- 从Request Header中获取认证信息;
- 确认Header的Schema被设置为basic,并包含了正确的Base64编码字符串;
- 将字符串转换为“username:password”格式,获取各自内容;
- 验证身份信息,确定是否具有相应权限;
- 如果Credentials验证无误,设置当前线程的Identity信息,以便子请求能够重用;
- 如果验证有无,则返回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/