目录
知识准备
ASP.NET Core 使用了不同传统Web程序的实现方式,在读本文之前,需要先了解ASP.NET Core的运作方式。主要是将传统Web中的模块(IHttpModule)和处理器(IHttpHandler)合并成了中间件(Middleware),以下ASP.NET Core 中间件的官方解释,以及如何进行代码迁移,不了解的小伙伴可以点开学习一下,这里不再重复。
ASP.NET Core Middleware | Microsoft Docs
Migrate HTTP handlers and modules to ASP.NET Core middleware | Microsoft Docs
Identity使用的RazorPage的实现,有关RazorPage请参考:
Introduction to Razor Pages in ASP.NET Core | Microsoft Docs
理解身份认证
ASP.NET Core 自带的安全管理分为两部分,一个是身份认证(Authentication ),另一个是鉴权(Authorization)。身份认证(Authentication )用于用户的登录登出,鉴权(Authorization)用于权限控制。这两个都是以相应的中间件(Middleware)来表示的。
身份认证(Authentication )
对于用户登录ASP.NET Core提供了多种认证方式,不过没找到具体的文档说明,这里参照源码总结了一下,大致分成四大类:
- 表单认证
- JWT认证
- 远程第三方认证
- 使用系统认证
这四类认证方式都实现了IAuthenticationHandler接口,但实际使用中并不直接在代码中操作这些具体的实现,可能是这个原因,微软的文档中并没有具体提及,AuthenticationHandler类结构图如下:
鉴权(Authorization)
同样鉴权也有四种实现,分别是:
- 基于条件断言的,比如级别达到多少的可以访问。
- 防匿名的,比如某些功能需要登录才能使用。
- 基于用户的,比如某些指定的用户才可以使用。
- 基于角色的,比如某一类型的用户。
IAuthorizeData
IAuthorizeData表明了具体资源的权限特征,实际项目中使用AuthorizeAttribute来表示。在鉴权的时候主要用到两个属性:Policy和Roles,这两个属性确定最终使用哪个鉴权方式。比如两个属性都不指定就是防匿名方式,指定Policy就是条件断言的方式。
比如在类申明中指定角色:
[Authorize(Roles = "Administrator")]
public class AdministrationController : Controller
{
}
Identity
Identity是基于表单认证的一套用户管理组件,包括用户登录(含第三方登录)、用户管理的定义和操作接口,以及相应的用户界面。Identity通常使用EntityFramkeworkCore作为持久层,将用户数据保存到关系数据库中。
认证功能配置
ConfigureServices
虽然认证中间件(AuthenticationMiddleware)是ASP.NET Core 内置的,但因为有多种认证方式,所以在应用启动时要作相应的配置。比如表单验证时的登录页面、登录出链接、保存凭据的Cookie名等。好在ASP.NET Core 给每种认证方式都提供了相应的扩展方法,可以轻松地进行配置。比如添加表单验证,登录页为/Login
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options=> {
options.LoginPath = "/Login";
});
services.AddControllersWithViews();
}
AuthenticationScheme
AuthenticationScheme是个字符串,跟每一种验证方式一一对应。不过,AuthenticationScheme跟Middleware一样,都没有使用接口进行约束,而是都遵守一同一个约定,每个验证组件中提供一个AuthenticationScheme 的常量,比如提供:
public static class FacebookDefaults
{
/// <summary>
/// The default scheme for Facebook authentication. The value is <c>Facebook</c>.
/// </summary>
public const string AuthenticationScheme = "Facebook";
/// <summary>
/// The default display name for Facebook authentication. Defaults to <c>Facebook</c>.
/// </summary>
public static readonly string DisplayName = "Facebook";
/// <summary>
/// The default endpoint used to perform Facebook authentication.
/// </summary>
/// <remarks>
/// For more details about this endpoint, see https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#login.
/// </remarks>
public static readonly string AuthorizationEndpoint = "https://www.facebook.com/v8.0/dialog/oauth";
/// <summary>
/// The OAuth endpoint used to retrieve access tokens.
/// </summary>
public static readonly string TokenEndpoint = "https://graph.facebook.com/v8.0/oauth/access_token";
/// <summary>
/// The Facebook Graph API endpoint that is used to gather additional user information.
/// </summary>
public static readonly string UserInformationEndpoint = "https://graph.facebook.com/v8.0/me";
}
AuthenticationScheme通常是由相应的验证组件提供的扩展方法中自动调用的,但在多认证系统方式的系统中,需要指定通过AuthenticationScheme指定默认的认证系统。比如前边的ConfigService方法中,指定表单认证为默认认证方式。
Identity项目示例
创建项目
为了便于理解,我们从最简单的项目开始。我们在Visual Studio里创建一个空白Web项目,运行环境选择3.1,如图所示:
接下来,我们增加两条EndPoint,一条用于模拟登录,另一条模拟管理:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
//新增两条终结点
endpoints.MapGet("/Account/Login", async context =>
{
await context.Response.WriteAsync("Login");
});
endpoints.MapGet("/Manage", async context =>
{
await context.Response.WriteAsync("Manage");
});
});
配置认证与鉴权
配置服务
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
services.AddAuthorization();
}
启用中间件
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//……
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
//……
//使用RequireAuthorization()方法,对manage请求启用默认鉴权
endpoints.MapGet("/Manage", async context =>
{
await context.Response.WriteAsync("Manage");
}).RequireAuthorization();
});
}
运行测试
等Hello World出现后,在浏览器地址栏里输入:https://localhost:44362/manane,就会跳到模拟的登录页:
总结
此时我们已经让 ASP.NET Core 身份认证及鉴权生效,并开始工作。当然这里并没有实现登录等功能,如果要实现完整功能还需要写很多代码,不过这并不是我们目的,到此只要知道怎么工作的就行了。
Identity
添加Identity功能
前边我们演示了一个最简单的认证鉴权功能。接下来我们通过增加Identity来实现完整的功能,为了省事,我们使用Visual Studio来添加:
弹出框中选择标识,点击添加:
添加一个下文和一个用户类,点击添加:
添加成功后,会自动添加相关依赖包,及需要的项目文件:
配置数据库连接
默认使用的Sql Server的驱动,且自动更新了配置,我们将连接字符串改成下边的样子:
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
IdentityHostingStartup
加完标识后会自动生成一个IdentityHostingStartup.cs的文件,里边配置了数据库上下文及Identity,并通过assembly标注附加到启动项:
[assembly: HostingStartup(typeof(WebApplication2.Areas.Identity.IdentityHostingStartup))]
namespace WebApplication2.Areas.Identity
{
public class IdentityHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
services.AddDbContext<WebApplication2Context>(options =>
options.UseSqlServer(
context.Configuration.GetConnectionString("WebApplication2ContextConnection")));
services.AddDefaultIdentity<WebApplication2User>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<WebApplication2Context>();
});
}
}
}
配置RazorPage及静态文件
Startup里添加RazorPage中间件:
services.AddRazorPages();
使用静态文件:
app.UseStaticFiles();
添加映射:
endpoints.MapRazorPages();
Startup.cs完整内容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using WebApplication2.Areas.Identity.Data;
using WebApplication2.Data;
namespace WebApplication2
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
endpoints.MapGet("/Account/Login", async context =>
{
await context.Response.WriteAsync("Login");
});
endpoints.MapGet("/Manage", async context =>
{
await context.Response.WriteAsync("Manage");
}).RequireAuthorization();
endpoints.MapRazorPages();
});
}
}
}
生成数据库
到目前为止程序能运行了,只是还不能登录,因为数据库还没创建。下边开始一步步添加数据库、
生成迁移代码
打开软件包控制台,输入以下命令:
Add-Migration Identity_Init
生成数据库:
Update-Database
功能测试
为了方便起见,先关闭密码策略
public class IdentityHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) =>
{
services.AddDbContext<WebApplication2Context>(options =>
options.UseSqlServer(
context.Configuration.GetConnectionString("WebApplication2ContextConnection")));
services.AddDefaultIdentity<WebApplication2User>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<WebApplication2Context>();
});
}
}
编译,在VS中起动,打开下边的链接,注册一个账号:
https://localhost:44359/Identity/Account/Register
因为没有限制,注册完就可以使用了。打开下边的链接,就可以修改自己的信息了。
https://localhost:44359/identity/account/manage
自定义UI
由于自带的UI并没有提供语言包,所以如果换成中文,需要使用自己的UI,我们可以重新添加标识,生成所需要的页面,在添加界面上勾选替代所有文件。
添加完成后,这些文件就全部在解决方案里了,我们尝试改其中的一个文件,比如Identity/Account/Manage/Index.cshtml.cs,给UserName属性加上Display属性,修改Input中的PhoneNumber的Display:
然后编译运行,查看效果:
至此Identity功能就算完成了。