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

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

一、Forms Authentication

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

    尽管 Forms 认证是 Web 应用程序的首选认证方式,但从 Web 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 服务,这自然是不适合的。

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

    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
服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现。

二、身份(Identify)管理

    1、认证(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 (使用对称密钥)

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

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

三、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。


原文地址:https://www.cnblogs.com/gyche/p/3106311.html