Angular、.NET Web Api 验证(Authentication)与跨来源资源共享(Cors)

验证是任何 Web 服务都必须考虑的基础问题。关于 .NET 平台的验证与授权,按理说微软已经提供了相当充分的文档与示例代码。

.NET Identity 框架提供了现成的数据库结构以及一系列接口,可以实现用户注册、登录登出、口令生成、邮箱的确认、密码重置等基本功能,也可以与社交媒体账户集成实现用户注册与登陆。

需要注意的是,.NET MVC 与 .NET Core 所使用的接口代码和 .NET Web Api 有所不同,如果混合阅读文档与代码,可能会造成混淆与理解上的困扰。不同版本的文档对邮箱验证等功能的处理逻辑也有差别,不可简单合并使用。

后端若部署至 Azure 等云平台,口令的生成也可能会遇到在本地不会发生的问题,报错“ The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread’s user context, which may be the case when the thread is impersonating.”。例如,.NET MVC 版示例代码在用户登录并验证邮箱后生成口令时会试图调用 /Token 接口,触发错误。网上针对此问题有一些解决方案,却都不太有效。事实上,可暂时忽略对邮箱是否得到验证的检查,直接前台调用 /Token 接口,就能避免该错误。对邮箱的确认可以在之后实际执行处理时按需进行。

微软官方的示例代码都没有涉及 Cors(cross origin resource sharing) 问题,即使是 Angular 项目模板,由于前端默认与后端共存于同一个解决方案,也不存在这一问题。然而,如果前后端分离,前端 Angular 程序的域名很可能与后端不同。

微软针对 .NET Web Api 的 Cors 有一些文档,但很遗憾,如果有接口通过 bearer token 进行验证与授权,那些文档中的解决方案很可能会引发新的问题。

  • .NET Web Api 中对 允许跨源访问:介绍多种允许 Cross origin request 的方式

由于 Chrome 等一些浏览器在一定条件下出于安全原因为首先发送 OPTIONS method 的 HTTP 请求(如 headers 中指定 “Content-Type”: “application/x-www-form-urlencoded” 时),情况变得更加复杂。

如果 OPTIONS method 没有被允许,则会遇到 405 错误代码。不当的设置可能会在允许跨源访问资源时却阻碍了用户登录等验证 API。又或是无意中重复允许 Cors origin 设置,会提示 The ‘Access-Control-Allow-Origin’ header contains multiple values 错误而无法获得资源。

网络上简单明了且实际有效的解决方案并不好找,经过反复尝试与分析,要避免所有这类问题,采用全局设置恐怕是最简单的方法之一。

  • 不要像下面这样通过 Web.config 来设置 Cors

  • 不要像下面这样通过 ApiController 属性来设置 Cors

  • 不要再 WebApiConfig 等类中通过诸如 config.EnableCors(); 等代码方式设置 Cors
  • 安装 Microsoft.Owin.Cors 的 NuGet 包
  • 在 startup.cs 的 ConfigureAuth 方法开始时调用语句 app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
  • Angular 前端发送请求时, headers 中只需指定 “Content-Type”: “application/x-www-form-urlencoded” 一项即可
  • 如果使用 RestAngular 获取资源,只需按如下方式设置 Content-Type 与 Authorization 即可

 

至此,运行于 Azure App Service 的后端 .NET Web Api 将可以与非同源的 Angular 程序交互,实现本地用户账号的注册登陆等操作,可以通过 bearer token 实现资源请求过程中的验证与授权。

通过正则表达式过滤/去除 XML 1.0 的非法字符

说明:
XML 1.0 的合法字符如下:

Char::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, And FFFF. */

例如,.NET 框架中的 SQLDataAdapter.Fill() 将自动将 XML 1.0 不支持的 Unicode 字符转换为 HTML 字符(如 #xB 等),因此,在这类场景中,应当首先过滤非法字符,否则 XSL 等可能会无法正确处理由 XmlDocument.LoadXml() 得到的 XML。
另,改用 XML 1.1 也是一种解决方案,XML 1.1 的非法字符仅有 NUL (x00)、xFFFE 与 xFFFF 这三个。

用于匹配 XML 1.0 非法字符的正则表达式模式如下:

VB.NET 代码示例:

SOAP 与 REST 简单比较

SOAP
SOAP是一种协议。
SOAP是Simple Object Access Protocol的缩写,用于传输小规模数据。SOAP消息为XML格式,通常以HTTP协议发送(亦通过TCP/IP)。
由于采用了XML格式,SOAP需要定义数据的类型与功能。如果要传输二进制数据,则必须先以base64格式对其编码。WSDL、XSDs、WS-Addressing等协议或技术都与之相关。
SOAP由于可以同时控制服务器与客户端的行为,因此经常被用于内部网络API。

REST
REST是一种传输架构,与协议无关。
REST是Representational State Transfer的缩写,可以较为灵活地在客户端与服务器之间传输数据,格式可以是JSON、XML甚至纯文本,机制较SOAP更为轻量。
REST采用通常的HTTP方法(如HTTP GET、HTTP PUT等)传输,而无需借助XML。只要框架支持HTTP,就能实现REST。二进制数据也可以通过请求传输。不过需要注意,REST并没有与CRUD方法一一对应。
REST由于简单轻量,且格式更为灵活,不少公网API都采用了这种方式。

在选择SOAP还是REST时,可以考虑以下几点:

REST的优势:

  • 实现简单
  • 学习曲线平缓
  • 传输效率更高(无需定义XML)
  • 传输速度更快(没有引入额外的处理)
  • 与其他一些网络技术在设计理念上有共通之处

REST的劣势:

  • 是否需要独立于平台、框架或协议(REST依赖HTTP)
  • 是否需要在分布式企业环境中运作(REST是直接的点对点通信)
  • 是否需要限定传输格式
  • 是否需要使用WS标准提供的扩展功能(如项目已有的代码或接口)
  • 是否需要利用内建的错误处理机制
  • 是否希望利用语言或框架自带的自动化机制来创建接口

.NET 开发笔记

作为分类目录的一个补充,在这里按照功能类别对 .NET 开发中的一点心得和笔记作一个索引。其中部分是根据自己在查找网络资料时找到的内容的整理与演绎,在此感谢所有那些无私分享经验的人们。

Entity Framework

Web

.NET 中 Entity Framework 的数据库查询

Entity Framework 支持三种类型的数据库查询。

  • LINQ to Entities
  • Entity SQL
  • Native SQL

其中,LINQ(音:link)是较为推荐的方式。它又分为 LINQ Method syntax 与 LINQ Query syntax,如下所示。

Entity SQL 与 Native SQL 都是基于拼接 SQL 语句的方式,如下所示。

此外,Entity Framework 提供了丰富的函数,应了解并掌握用法。以下列举一些。

  • Aggregate*
  • Average
  • All
  • Any
  • ElementAt/ElementAtOrDefault*
  • Single/SingleOrDefault*
  • Contains
  • First/FirstOrDefault/Last/LastOrDefault*
  • Count
  • Max/Min*
  • OrderBy/OrderByDescending
  • Reverse
  • SelectMany
  • SequenceEqual
  • Skip/SkipWhile
  • Take/TakeWhile

其中以星号标记的将返回 Entity 实体。