YbSoftwareFactory 代码生成插件【十三】:Web API 的安全性

    ASP.NET Web API 可很是方便地建立基于 HTTP 的 Services,这些服务能够很是方便地被几乎任何形式的平台和客户端(如浏览器、Windows客户端、Android设备、IOS等)所访问,它可根据请求类型自动提供 JSON、XML 等类型的响应内容。在移动互联网逐渐成为主流的背景下,经过 Web API 对外发布基于标准、通用 HTTP 协议的服务来交换数据无疑具备很是大的优点和吸引力。本文将主要围绕 ASP.NET Web API 的安全性进行讨论。 html

1、Forms Authenticationweb

    Forms 认证基于凭据(Ticket)机制,凭据在登陆时建立,一般会写入到 cookie 中。Forms 认证可应用在任何类型的ASP.NET 应用程序中,例如:WebForms,MVC,甚至 Web API等。默认的配置是 <authentication mode="None" />,所以为了使用Forms Authentication,一般须要在配置文件中进行配置。数据库

    尽管 Forms 认证是 Web 应用程序的首选认证方式,但从 Web API 的安全性来讲,其实它并非一个理想的解决方案,对于非浏览器的客户端来讲,作个比喻,就像是穿西装戴斗笠般不搭调。api

    最大的问题是在ASP.NET Web API中使用 Forms 认证方式的时候,并不会返回一个 302 代码(注:302 表示重定向,Web 应用程序中会被自动导航至设定的登陆页面地址),在 Web API 中使用 Forms 认证方式可参考以下代码:浏览器

    1)在 WebApiConfig 文件中修改默认的代码以下:缓存

public  static  class 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  class 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 服务,这天然是不适合的。cookie

    说到这里,休息一下,温习下 HTTP 响应的状态代码。mvc

    HTTP 响应的状态代码为三位数字的编号,其中第 1 位定义了状态代码的类别:1 开头的表明信息类、2 开头的表明操做成功类、3 开头的表明重定向类、4 开头的表明客户端一侧的请求错误类、5 开头的表明服务器端一侧的错误类。

    常见的状态代码以下:

  • 200 OK
请求已成功,请求所但愿的响应头或数据体将随此响应返回。
  • 201 Created
请求已经被实现,并且有一个新的资源已经依据请求的须要而创建,且其 URI 已经随 Location 头信息返回。假如须要的资源没法及时创建的话,应当返回'202 Accepted'。
  • 204 No Content
服务器成功处理了请求,但不须要返回任何实体内容,而且但愿返回更新了的元信息。响应可能经过实体头部的形式,返回新的或更新后的元信息。若是存在这些头部信息,则应当与所请求的变量相呼应。
若是客户端是浏览器的话,那么用户浏览器应保留发送了该请求的页面,而不产生任何文档视图上的变化,即便按照规范新的或更新后的元信息应当被应用到用户浏览器活动视图中的文档。
因为204响应被禁止包含任何消息体,所以它始终以消息头后的第一个空行结尾。
  • 400 Bad Request
因为包含语法错误,当前请求没法被服务器理解。除非进行修改,不然客户端不该该重复提交这个请求。
  • 401 Unauthorized
当前请求须要用户验证。该响应必须包含一个适用于被请求资源的 WWW-Authenticate 信息头用以询问用户信息。客户端能够重复提交一个包含恰当的Authorization 头信息的请求。若是当前请求已经包含了 Authorization 证书,那么 401 响应表明着服务器验证已经拒绝了那些证书。若是 401 响应包含了与前一个响应相同的身份验证询问,且浏览器已经至少尝试了一次验证,那么浏览器应当向用户展现响应中包含的实体信息,由于这个实体信息中可能包含了相关诊断信息。参见 RFC 2617
  • 403 Forbidden
服务器已经理解请求,可是拒绝执行它。与401响应不一样的是,身份验证并不能提供任何帮助,并且这个请求也不该该被重复提交。若是这不是一个HEAD请求,并且服务器但愿可以讲清楚为什么请求不能被执行,那么就应该在实体内描述拒绝的缘由。固然服务器也能够返回一个 404 响应,假如它不但愿让客户端得到任何信息。
  • 404 Not Found
请求失败,请求所但愿获得的资源未被在服务器上发现。没有信息可以告诉用户这个情况究竟是暂时的仍是永久的。假如服务器知道状况的话,应当使用 410 状态码来告知旧资源由于某些内部的配置机制问题,已经永久的不可用,并且没有任何能够跳转的地址。404 这个状态码被普遍应用于当服务器不想揭示到底为什么请求被拒绝或者没有其余适合的响应可用的状况下。
  • 500 Internal Server Error
服务器遇到了一个不曾预料的情况,致使了它没法完成对请求的处理。通常来讲,这个问题都会在服务器的程序码出错时出现。

2、身份(Identify)管理

    一、认证(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)的安全的实例代码以下:

static  void Main( string[] args)
{
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 (使用对称密钥)

,支持对称密钥和非对称密钥

    二、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方式进行加密处理。

3、YbRapidSolution for MVC 中的 Authentication

    在 YbRapidSolution for MVC 的安全解决方案中,采用了 Base Authentication 和 Form Authentication 相互补充的处理方式,使用 Attribute 方式进行请求的 Authentication 处理。Base Authentication 主要面向非Web应用的处理请求,Form Authentication 则对 MVC 的 Controller 的请求进行处理,核心代码以下:

private  bool AuthorizeRequest(HttpRequestMessage request)
        {
             // 匿名用户的权限验证
            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;
        }
View Code

    从上述代码中能够看出,在 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。

相关文章
相关标签/搜索