背景
任何一个系统,都需要对于底层访问的页面和接口进行安全的处理,其中核心就是认证和授权。
另外一个问题就是在实际编程过程中,我们的代码有不同的模式,不同的分层或者在不同的项目之中,如何在不同的地方取得用户信息,特别是业务系统,根据操作员的id来处理不同的业务权限逻辑,这是很多业务应用所关心的。
微软在不同的版本都有各自的实现,现在以>= .net6的版本为准。
基本概念
这里所说的基本概念,是微软框架下的技术概念,对于一些通识的概念,比如以下内容,假定对这些都是熟悉的,
还有对于基本的微软技术概念,也假定大家都是熟悉的,比如 管道,特别是httpcontext的属性
.net 授权
.net 提供了简单授权、基于角色的授权、基于策略的授权,多样的授权方式在通过简单的Attribute修饰就能满足大部分应用场景。授权中重要的两个Attribute就是AuthorizeAttribute和AllowAnonymousAttribute,所有的授权配置都离不开这两个Attribute。同时,.net 对授权方案的扩展也非常方便,在本节的最后会介绍如何自定义授权处理程序来实现自定义授权逻辑。授权有这三种类型简单授权:只要登录就能访问,在Controller或者Action上加个[Authorize]就行基于角色的授权:特定角色能访问基于策略的授权:顾名思义基于角色的授权.
app.UseAuthentication()
- services.AddAuthentication(options =>) 如果需要个性化,可以在这里展开,一些规则,比如 DefaultAuthenticateScheme DefaultChallengeScheme
- AddJwtBearer Token个性化验证参数
- AuthenticationService, AuthenticationHandlerProvider, AuthenticationSchemeProvider 是的,是通过这三个服务,核心是 AuthenticationMiddleware 逻辑都在这里面
- Scheme 就是用什么标准规定认证,比如用jwt,bearer, 不同的scheme,就有不同的options,就有不同的handler
所以核心就是使用或者重载这些内容
httpcontext.user
- publicabstract System.Security.Claims.ClaimsPrincipal User 这是标准定义
身份认证通过后,身份认证处理程序会返回身份认证票根,即AuthenticationTicket。
AuthenticationTicket是ASP.NET Core封装认证信息的类。
AuthenticationTicket又包含了ClaimsPrincipal,ClaimsPrincipal可以理解为用户主体,由一组ClaimIdentity组成。
ClaimsIdentity可以理解为身份证明,一个用户主体可以有多个身份证明,就好比身份证、驾驶证都可以代表唯一具体的人一样。
ClaimsIdentity包含了一组Claim,Claim就是好比身份证上的姓名、性别、籍贯等信息。一个用户通过身份认证后,就会用以上类来组织用户信息。后续的授权等其他中间件就可以使用这些信息来进行功能设计。
结构示例如下:
- AuthenticationTicket (身份认证票根,其中封装了认证信息)
-
- ClaimsPrincipal (用户主体)
-
-
- ClaimIdentity (身份证明)
-
-
-
-
- Claim
- Claim
- Claim
-
-
-
-
- ClaimIdentity
- ClaimIdentity
-
IHttpContextAccessor
我们知道当请求通过认证模块时,会给当前的HttpContext赋予当前用户身份标识,我们在需要授权的控制器中打上[Authorize]授权标签,就可以在ControllerBase的User属性获取到基于声明的权限标识(ClaimsPrincipal)。
遗憾的是这只是针对Controller层面,很多场景下我们是需要在Service层乃至数据层获直接使用用户信息,这种情况我们就使用不了User了。
解决办法就是通过这个接口来注入一个服务,在业务层面来拉取用户相关信息。可以在登录的时候把userid写进来,另外就是通过token为key,使用redis存放用户信息。
实现基本思路
使用微软安全框架
- 使用微软数据库一套
- builder.Services.AddIdentity<ApplicationUser, ApplicationRole> 构建自己个性化身份 public partial class ApplicationUser : IdentityUser
namespace Microsoft.AspNetCore.Identity;
/// <summary>
/// The default implementation of <see cref="IdentityUser{TKey}"/> which uses a string as a primary key.
/// </summary>
public class IdentityUser : IdentityUser<string>
- public abstract class AuthenticationStateProvider ApplicationAuthenticationStateProvider 自己实现微软的认证,builder.Services.AddScoped<AuthenticationStateProvider, ApplicationAuthenticationStateProvider>();
基于类库个性化认证和授权
清楚了逻辑,后面就是体力活了,不过有一坨的工作要做,需要用户表,应用表,角色表等等
- Services.ConfigureOptions<ConfigureJwtBearerOptions>();
Services.ConfigureOptions<ValidateIdentityTokenOptions>();
var authBuilder = Services
.AddAuthentication(MultiScheme)
.AddPolicyScheme(MultiScheme, MultiScheme, options =>
{
options.ForwardDefaultSelector = context =>
{
return context.Request.Headers.Authorization.Any(x => x!.Contains(ApiKeyDefaults.AuthenticationScheme))
? ApiKeyDefaults.AuthenticationScheme
: JwtBearerDefaults.AuthenticationScheme;
};
})
.AddJwtBearer();
_configureApiKeyAuthorization(authBuilder);
Services.AddSingleton<IAuthorizationHandler, yourhandler>();
Services.AddSingleton<IAuthorizationHandler, yourhandler>();
Services.AddSingleton(ApiKeyProviderType);
Services.AddSingleton<IApiKeyProvider>(sp => (IApiKeyProvider)sp.GetRequiredService(ApiKeyProviderType));
Services.AddAuthorization(options => options.AddPolicy(IdentityPolicyNames.SecurityRoot, policy => policy.AddRequirements(new ())));
这是最终都要有的,围绕这些内容展开。
整个项目挺多,就不展开细节了,代码是.net 7 .net8 环境下跑的。