有关 ASP.NET Core 的 Security – ASP.NET Core Identity 与 Google authentication

依照我之前谈过的网站基础,最后一块完全没讲过的区块就是 Security.当然,Security 的内容包含的很广泛,有存取控制方面的 security,也有传输数据方面的 security,也有要防止像 cross site scripting 等恶意入侵的 security,这些内容含盖的范围从 runtime, entity framework 到 MVC 到 Razor 都有.而这一篇文章中主要是浅谈 ASP.NET Core Identity 以及如何将 Identity 使用 Google 账号来做验证.


时间很快地来到了六月底,这星期的头一天 PM 的大老板公告了 .Net Core 1.0 , ASP.NET Core 1.0, Entity Framework Core 1.0 正式发行,也传了一些科技新闻的剪影让大家参考,经历了两年多的时间终于把新版本的产品释出了.从某个角度来看,整个团队对微软公司的营收直接贡献是 零 元,而且所有的程序也都 open source 了,只能觉得这真是个佛心的公司,让大家有免费的工具与执行平台可以写 web 应用程序.这几年也可以看到公司配合着世界在改变了.

回归本文章主题 - Identity.

历史

最早在 ASP.NET 刚问世时,其实没有什么 security 的设计,只有简单的 form authentication 算是比较有用的,即便有 form authentication ,自己还是要写不少的 code 做一些配套措施,才让 authentication 比较完整些,而 authorization 仍是一空白.后来,ASP.NET 出了一些 membership provider 之类的东西,把相关的功能做的更多,但以我个人感觉来说,不太好用.后来在几年前推出了 Identity,这个版本就好用多了,让你可以不用写太多额外的 code 就能做好 authentication ,而且还可以整合到其他的验证服务器.

ASP.NET Core Identity

整个项目的程序在 https://github.com/aspnet/Identity/tree/dev/src/Microsoft.AspNetCore.Identity ,在这些源代码中看起来好像有很多的文件,其实有大约 1/3 都是 interface 的定义.如果你熟悉之前版本的 Identity,那这一版对你来说并不会陌生,因为整个程序主要结构都是一样的.基本上来说一样是分为三个层次,最上面的 Manager,中间的 Store,以及最下面的数据存取方式.

Manager 类的程序定义了你的程序如何和 Identity 交互.例如,UserManager (https://github.com/aspnet/Identity/blob/master/src/Microsoft.AspNetCore.Identity/UserManager.cs) 定义了 GetUserName() 让你的应用程序取得使用者名字,或是你的应用程序要新增一个使用者时,可以调用 CreateAsync() 来达成这工作.你可以从 UserManager 的定义里看到许多你需要为 Users 做的事情.再举另外一个例子,SignInManager (https://github.com/aspnet/Identity/blob/dev/src/Microsoft.AspNetCore.Identity/SignInManager.cs) 是负责做与认证登入有关的工作,所以你可以看到它提供的方法有 SignInAsync(), SignOutAsync(), TwoFactorSignInAsync() 之类.

Store 类的程序定义了 Manager 该如何取得所需要的资讯,例如 UserManager 有 GetUserName() ,但 user name 是保存在某一个地方 (例如数据库等),于是 UserManager 就会调用 UserStore 的 GetUserNameAsync() 来取得.所以对 UserManager 来说,它并不知道 user name 在那里,它只是去调用 User Store 的 GetUserNameAsync() 来取得数据,而 User Store 才是真正去取得数据的程序.因此, 在 UserManager 要建立时,你可以看到它的 constructor 第一个参数就是 IUserStore,而任何实做 IUserStore 的程序就是 UserStore,所以实做 Store 的程序就是实际放数据的角色,比如 user 数据都保存在数据库,那么建立 User Store 的人需要和数据库有直接交互,所以 Entity Framework 便是最适合的程序来产生 UserStore.因此,若你有特别的需求时,你能写自己的程序来产生 UserStore 让 UserManager 使用,这样一来整个应用程序在执行时使用 UserManager 来操作 user 数据时便能用你的程序去运行.以后找个时间再来写这样的文章,这种应用应该是相当有趣的.

当你看源代码时,你就能依文件名称 Manager, Store 来区分,依照这样的想法就比较能帮助你知道那些源代码是做什么的.而最下层的数据存取方式,目前 Identity 只提供 Entity Framework 来执行,这部分的源代码在 https://github.com/aspnet/Identity/tree/master/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore.如果你有需要实做特别的数据存取方式,只要参考这里的做法照着做就行了.

目前的版本以提供 Entity Framework 为 Store 的运行者,所以 user, role 等等的相关数据都写在数据库中.

使用 Identity 

在这里用数据库为保存空间为例子来说明,整体的使用上相当简单,如果你使用 Visual Studio 2015 产生 ASP.NET Core Web Application 时,在验证方式上选择使用 Indivudual User 验证方式,则 Visual Studio 会为你载入合适的项目样板.

然后在 Startup.cs 里的 ConfigureServices() 里,你会看到 ASP.NET Core 将 Entity Framework 和 Identity 加入,

            services.AddDbContext(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            services.AddIdentity()
                .AddEntityFrameworkStores()
                .AddDefaultTokenProviders();

            services.AddMvc();

            // Add application services.
            services.AddTransient();
            services.AddTransient();

此时,请记得到 appsettings.json 将 DefaultConection 改成你所使用的数据库连接字符串.

在项目刚建立完成时,你也会看到项目目录下有一个 Data 目录,里面存放的就是上面程序中 ApplicationDbContext 定义.你可以看到整个数据库的部分是用 code first 的方式形成的,在你第一次执行项目时,程序会执行 ef migration,将整个 Identity 所用的 table 都在数据库中建立起来,因此,你得确定你在连线字符串中所设定的数据库使用者有足够的权限.在 Data 目录里还有一个 Migrations 目录,里面的 class 可以让你看到整个 DbContext 的结构,以及套用到数据库上的  db schema.

在上述程序后面两行,将两个 class 加入到 dependency injection 保存空间中,这两个是使用者加入的过程中,可以使用 email 或是 sms 的方式来做验证.但这部分的程序是个空盒子.如果你需要这功能,就要到 AuthMessageSender 和 AuthMessageSender 将传送 email , sms 的程序自行补上去.

其他的东西,相对应的 Controller,Model 和 View 都已经由样板带出来了,所以基本上不用特别修改,可以直接执行,你就会看到一个具有 user authentication 的样板网站了.

Identity 使用 Google 验证

在官方文档网站中,你可以找到一个文章教你如何使用 Facebook 账号做为验证方式 (https://docs.asp.net/en/latest/security/authentication/sociallogins.html),在这篇文章中,我用 Google 来做为例子.

其实 ASP.NET Core 在 Authentication 的部分支持 OAuth,也就是说验证使用者是否为真可以交给其他可信赖的人来做,不用自己做,而我们只要相信别人告诉我的答案就好.许多大型的网站都支持 OAuth,ASP.NET Core 也做出了几个常用的大型网站 OAuth 套件,让你可以很方便地整合其他网站的验证方式.有关其他网站的 OAuth 套件源代码可以在这找到 https://github.com/aspnet/Security/tree/master/src

接下来,用 Google 验证做范例.设定的过程在前面跟介绍 Facebook 验证的文档是一样的,所以我们直接跳到设定 Google 的部分.要使用 Google OAuth,你得先到 Google+ API 网站.https://console.developers.google.com/projectselector/apis/credentials

选择建立新项目,输入项目名称,你会看到如下画面.

然后请选择 OAuth consent screen,设定项目名称与 URL 等.

按下 Save 后,再回到 Credentials 去按下 Create Credentials.

选择 OAuth client ID

选择 Web application,输入名称,在 Authorized redirect URIs 上输入你的网站 URL,这样才能让 Google 知道使用者验证完成后要回到什么地方.

然后按下 Create,你会看画面上出现如下

请把 client ID 和 client secret 记下来,等会要输入在  ASP.NET Core 程序里.

再回到你的 ASP.NET Core 程序的 Startup.cs,在 Configure() 里把 Google 验证加上去

            app.UseIdentity();

            app.UseGoogleAuthentication(new GoogleOptions()
            {
                ClientId= "your client id.apps.googleusercontent.com",
                ClientSecret= "your client secret"
            });

基本上这样就完成了.

接着执行你的项目,然后试着 Log In,选择 Google,画面就会导向到 Google 验证画面,回来之后你就会可以看到已经成功经由 Google 验证了,因为你的 Google 账号名会出现在项目网站上右上角的位置.

Hope this helps :)