首页 > 编程语言 >Asp-Net-Core权限认证

Asp-Net-Core权限认证

时间:2024-10-13 22:59:15浏览次数:1  
标签:Core Asp Claim 认证 授权 var new Net ClaimTypes

翻了很多的博客,文档,发现asp.net core自带的权限认证还是比较复杂的,极少有哪篇文章把整个体系涉及到的知识点都讲清楚的,因此自己整理出了这篇文章,相当于自己的一个个人理解和总结吧

关键概念

认证和授权#

asp.net core中将权限认证分成了两个部分,一个是认证(Authentication),一个是授权(Authorization),他们的作用分别是:

  1. Authentication是根据不同方案(Scheme),来校验用户是否通过认证,比如用户名密码是否正确,通过了的话就会调用下面的语句来写入登录信息
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,claimPrincipal);
  1. Authorization是根据规则来判断用户是否通过了认证,通过了认证才授权这个用户访问对应的接口

举一个简单的例子:你们小区出入都有保安在守着,还有门禁,没有权限的人不让进入(接口上使用了Authorize特性),如果你是小区的业主,你得去物业那边登记一下个人资料,物业会记录你的手机号,身份证等个人信息,然后给你颁发一个门禁卡(这一步相当于认证),之后你每次进入小区,都需要刷门禁卡才能进入(授权)

用户角色#

用户登录后我们可以通过HttpContext.User访问当前用户的个人信息,其中包含了以下三个重要的概念:

  1. ClaimsPrincipal:当前登录用户的角色
  2. ClaimsIdentity:当前用户持有的证件,比如身份证,银行卡
  3. Claim:证件上的每一个信息,比如身份证上的姓名,出生日期,过期时间,每个都是一个claim

还是接着上面那个例子讲,去物业登记的时候给你颁发的门禁卡,就相当于ClaimsIdentity,上面的每一个信息就是一个Claim,你是ClaimsPrincipal,一个人可以拥有多个证件,比如门禁卡、银行卡、身份证,在进入小区的时候就会通过门禁卡里面的信息判断是否授权给你进去

授权方式#

Authorize特性其实已经包含了几种不同的授权方式

  1. 基于角色 Roles
  2. 基于策略 Policy
  3. 基于方案 Scheme

还是上面那个例子,进入小区的时候必须得刷门禁卡,刷身份证、银行卡肯定是进不去的,为什么呢?这就好比小区门禁的授权采用的是基于方案Scheme的,认证方式是物业认证,认证通过之后给你颁发了一个门禁卡,进入小区的时候根据物业指定的方案来验证颁发的门禁卡是否符合授权的要求

实操案例

新建一个Webapi项目,首先我们需要安装一个jwt的nuget包:

Microsoft.AspNetCore.Authentication.JwtBearer

首先我们需要添加认证的服务:

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddJwtBearer(
        JwtBearerDefaults.AuthenticationScheme,
        opt =>
        {
            opt.TokenValidationParameters = new TokenValidationParameters
            {
                ValidIssuer = "benji",
                ValidAudience = "benji",
                // 这里是签名秘钥
                IssuerSigningKey = new                    SymmetricSecurityKey(Encoding.ASCII.GetBytes("miyaomiyao12312312312312312312")),
                // 是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
                ValidateLifetime = true
            };
        })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opt =>
    {
        // opt.Cookie
        opt.Cookie.Name = "MyCookie";
        opt.ExpireTimeSpan = TimeSpan.FromMinutes(10);
        opt.Events.OnRedirectToLogin = context =>
        {
            context.Response.Headers["Location"] = context.RedirectUri;
            // 认证失败返回401 否则返回的是404
            context.Response.StatusCode = 401;
            return Task.CompletedTask;
        };
    }).AddScheme<AuthenticationSchemeOptions, CustomAuthHandler>(CustomAuthHandler.SchemeName, it => { });

这里一共添加了三种认证方案,一种是基于Jwt的,一种是基于Cookie的,还有一种是我们自定义的认证方案,并且第一行就设置了默认的认证方案是Cookie认证

自定义认证方案需要继承AuthenticationHandler<AuthenticationSchemeOptions>类,下面是示例代码:

public class CustomAuthHandler:AuthenticationHandler<AuthenticationSchemeOptions>
{
    public CustomAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
    {

    }

    public const string SchemeName= "自定义验证";

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var auth=Request.Headers["Authorization"].ToString();
        if (auth == "自定义验证条件")
        {
            //验证成功后创建用户信息
            var claimsIdentity = new ClaimsIdentity(new Claim[]
            {
                new Claim(ClaimTypes.Name, "testUser"),
                new Claim(ClaimTypes.Role, "testRole")
            }, SchemeName);

            var principal = new ClaimsPrincipal(claimsIdentity);
            var ticket = new AuthenticationTicket(principal, this.Scheme.Name);
            return AuthenticateResult.Success(ticket);
        }
        else
        {
            return AuthenticateResult.Fail("验证失败");
        }
    }
}

认证方案添加后,我们需要在管道内添加认证和授权的中间件:

app.UseAuthentication();
app.UseAuthorization();

添加完成后就可以使用了,我们先给三种认证授权写对应的登录接口,从而颁发对应的token:

 [HttpPost]
    public IActionResult LoginByJwt(string username,string password)
    {
        if (username == "test" && password == "test")
        {
            //JWT载荷(Payload)
            var key = Encoding.ASCII.GetBytes("miyaomiyao12312312312312312312");
            var authTime = DateTime.UtcNow;
            var expiresAt = authTime.AddDays(7);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Issuer = "benji",
                Audience = "benji",
                //自定义内容
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(ClaimTypes.Name,"local"),
                    new Claim(ClaimTypes.Sid,"123456"),
                    new Claim("随便定义一个字段","字段对应的值"),
                    new Claim(ClaimTypes.Role,"admin"),
                    new Claim(ClaimTypes.Role,"user"),
                    new Claim(ClaimTypes.Role,"superadmin"),
                }),
                //过期时间
                Expires = expiresAt,
                //签证
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokenDescriptor);
            var tokenString = tokenHandler.WriteToken(token);
            return Ok(new
            {
                access_token = tokenString,
                token_type = "Bearer"
            });
        }
        else
        {
            return BadRequest("账号错误");
        }
    }

    [HttpPost]
    public async Task<IActionResult> LoginByCookie(string username, string password)
    {
        if (username == "test" && password == "test")
        {
            //1.创建cookie 保存用户信息,使用claim。将序列化用户信息并将其存储在cookie中
            var claims = new List<Claim>()
            {
                new Claim(ClaimTypes.MobilePhone,"123"),
                new Claim(ClaimTypes.Name,"test"),
                new Claim(ClaimTypes.Role,"admin"),
                new Claim("Id","123"),
                new Claim(ClaimTypes.Role,"user"),
                new Claim(ClaimTypes.Role,"superadmin"),
            };
 
            //2.创建声明主题 指定认证方式 这里使用cookie
            var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
 
            //3.配置认证属性 比如过期时间,是否持久化。。。。
            var authProperties = new AuthenticationProperties
            {
                // ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
                
                // 是否持久化,类似于前端勾选记住密码
                //IsPersistent = true,
 
                //IssuedUtc = <DateTimeOffset>,
            };
 
            //4.登录
            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
            return Ok();
        }
        else
        {
            return BadRequest("账号密码错误");
        }
}

这里只提供了Jwt和Cookie的创建token的方案,自定义认证方案的话可以根据自己的需求来定制怎么颁发这个token,想弄在header上或者添加一个cookie都行,下面我们就可以对其进行授权测试了:

[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[HttpGet]
public List<string> TestCookie()
{
    List<string> res = new List<string>();
    foreach (var claim in HttpContext.User.Claims)
    {
        res.Add( claim.Type + "-" + claim.Value);
    }

    return res;
}

[Authorize(JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
public List<string> TestJwt()
{
    List<string> res = new List<string>();
    foreach (var claim in HttpContext.User.Claims)
    {
        res.Add( claim.Type + "-" + claim.Value);
    }

    return res;
}
[Authorize(AuthenticationSchemes = CustomAuthHandler.SchemeName)]
[HttpGet]
public List<string> TestCustom()
{
    List<string> res = new List<string>();
    foreach (var claim in HttpContext.User.Claims)
    {
        res.Add( claim.Type + "-" + claim.Value);
    }

    return res;
}

这是针对三种不同认证方案的对应授权策略的写法,其中基于Cookie认证的授权方案可以不用写AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme,因为默认的就是这个,这里就相当于上面说的基于Scheme方案的授权

下面我们再介绍一下基于角色和基于策略的授权,我们先添加一些自定义的授权策略:

builder.Services.AddAuthorization(options =>
{
    //基于角色组的策略
    options.AddPolicy("管理员", policy =>
    {
        policy.RequireRole("admin", "system");
        // 三种认证结果的证件都算进来
        policy.AuthenticationSchemes=new []{ JwtBearerDefaults.AuthenticationScheme,CustomAuthHandler.SchemeName,CookieAuthenticationDefaults.AuthenticationScheme};
    });
    //基于用户名
    options.AddPolicy("用户名是张三", policy => policy.RequireUserName("张三"));
    // 基于ClaimType
    options.AddPolicy("地址是中国", policy => policy.RequireClaim(ClaimTypes.Country,"中国"));
    //自定义值
    options.AddPolicy("自定义Claim要求", policy => policy.RequireClaim("date","2017-09-02"));
   
});

上面都是基于策略的授权,每个策略都可以使用不同认证方案颁发的证件,如果不写的话就是只能使用默认方案的认证结果证件,当然,这里也可以自定义自己的策略,只需要继承IAuthorizationRequirement接口就行了,如下:



/// <summary>
/// 最小年龄限制
/// </summary>
public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int age)
    {
        MinimumAge = age;
    }

    public int MinimumAge { get; set; }
}


public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = context.User.FindFirst(
            c => c.Type == ClaimTypes.DateOfBirth );

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

MinimumAgeRequirement只需要包含我们需要处理的一些信息,当然不写也行,主要是这个Handler才是核心,在这里我们要写对应的校验逻辑,上面这个示例是微软官方文档中的用户最小年龄判断,接下来我们需要添加对应的服务和策略:

builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

options.AddPolicy("自定义策略", policy =>
                  {
                      policy.Requirements.Add(new MinimumAgeRequirement(1));
                  });

注意,如果同一个Requirement有多个对应的handler,那么都会执行,只要一个校验的结果是fail,那么整个都是fail,然后在我们颁发token时,需要添加对应的字段:

  new Claim(ClaimTypes.DateOfBirth,DateTime.Now.AddYears(-200).ToString()),

接着我们写一个对应的权限校验接口:

[Authorize(Policy = "自定义策略",AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[HttpGet]
public List<string> TestCustomPolicy()
{
    List<string> res = new List<string>();
    foreach (var claim in HttpContext.User.Claims)
    {
        res.Add( claim.Type + "-" + claim.Value);
    }

    return res;
}

那么基于策略的授权方案这里就介绍完了,下面介绍一下基于角色的授权方案,这种方案比较简单,就是判断认证的证件里面的Role这个Claim是否有自己要求的角色,比如:

[Authorize(Roles = "admin",AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[HttpGet]
public List<string> TestRole()
{
    List<string> res = new List<string>();
    foreach (var claim in HttpContext.User.Claims)
    {
        res.Add( claim.Type + "-" + claim.Value);
    }

    return res;
}

在颁发token的时候一定要加对应的角色:

new Claim(ClaimTypes.Role,"admin"),
new Claim(ClaimTypes.Role,"user"),
new Claim(ClaimTypes.Role,"superadmin"),

角色可以有多个,授权的时候也可以判断多个:

[Authorize(Roles = "admin,user")]

不过直接使用这种基于角色的授权有个问题,就是必须得指定使用某个认证方案,否则使用的是默认的认证方案给出的证件(也有可能是有但是我没找到对应的方法),所以如果需要针对所有的认证方案都进行收集并判断是否有某个角色,可以直接使用基于策略的授权方法,比如上面写过的:

options.AddPolicy("管理员", policy =>
                  {
                      policy.RequireRole("admin", "system");
                      // 三种认证的结果都算进来
                      policy.AuthenticationSchemes=new []{ JwtBearerDefaults.AuthenticationScheme,CustomAuthHandler.SchemeName,CookieAuthenticationDefaults.AuthenticationScheme};
                  });

总结

上面基本把所有认证授权相关的内容都介绍完了,还有一些更细节的地方,比如默认的认证方案可以根据条件动态选择、Token的过期刷新、自定义AuthorizeAttribute特性来定义自己的授权方法,这些就属于细枝末节的东西了,可以在后面的参考链接中找到官方文档、博客自行查阅

博客中所有的示例代码可以在这下载: https://github.com/li-zheng-hao/AspNetCore.AuthDemo

参考链接

  1. https://aspdotnetcore.net/docs/claims-based-authentication/
  2. https://zhuanlan.zhihu.com/p/359691679
  3. https://www.cnblogs.com/diudiu1/p/15818648.html
  4. https://zhuanlan.zhihu.com/p/359691679
  5. https://blog.csdn.net/icoolno1/article/details/108190010
  6. https://www.cnblogs.com/fanfan-90/p/11918537.html
  7. https://www.cnblogs.com/danvic712/p/use-cookie-authentication-in-asp-net-core.html
  8. Cookie刷新有效期
  9. https://zhuanlan.zhihu.com/p/364928893
  10. https://blog.csdn.net/qq_25991955/article/details/100540155
  11. https://www.cnblogs.com/axzxs2001/p/7482777.html
  12. 自定义授权Handler和Requirement
  13. 基于角色授权
  14. https://www.cnblogs.com/RainingNight/p/authorization-in-asp-net-core.html#iauthorizationrequirement
  15. asp.net core 6认证示例代码
  16. 微软官方认证授权的文档

作者:lizhenghao126

出处:https://www.cnblogs.com/lizhenghao126/p/17045495.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

标签:Core,Asp,Claim,认证,授权,var,new,Net,ClaimTypes
From: https://www.cnblogs.com/Alex80/p/18463198

相关文章

  • QToss:基于.NET架构的跨境电商的工具,助力企业实现智能数据营销
    2024年10月13日下午参加了一场在深圳举办的跨境电商大佬们的聚会,现场参加的人数上千人。大会分享嘉宾中有位来自美国的,他告诉我们不用担心美国政府会把TikTok禁掉,TikTok在全世界都很受欢迎的。回归正题说说今天大会上发布的这个产品QtossAI选品,这是一个由.NET助力的AI驱动跨境......
  • .NET云原生应用实践(二):Sticker微服务RESTful API的实现
    本章目标完成数据访问层的基本设计实现Sticker微服务的RESTfulAPI引言:应该使用ORM框架吗?毋庸置疑,Sticker微服务需要访问数据库来管理“贴纸”(也就是“Sticker”),因此,以什么方式来存储数据,就是一个无法绕开的话题。如果你遵循领域驱动设计的思想,那么你可以说,保存到数据库的数......
  • asp网站后台密码忘记怎么办?
    如果你忘记了ASP网站后台的登录密码,可以尝试以下几个步骤来解决这个问题:检查官方文档或帮助文件查看是否有官方提供的密码找回机制或者联系技术支持的方式。使用数据库直接修改密码如果你有数据库的访问权限,可以直接通过SQL语句来更新后台管理用户的密码。检查配置文件有......
  • WeNet与FunASR对比:全面解析
    目录1.项目背景2.技术架构3.识别性能4.模型训练与优化5.应用场景与部署6.社区与生态7.未来发展总结随着语音识别技术的快速发展,越来越多的开源语音识别框架涌现,其中WeNet和FunASR都是备受关注的项目。它们都提供了强大的语音识别功能,但在架构设计、技术细节、......
  • 分享几个实用且高效的EF Core扩展类库,提高开发效率!
    前言今天大姚给大家分享3款开源且实用的EFCore扩展类库,希望能帮助你在使用EFCore进行数据库开发变得更加高效和灵活,提高开发效率。EFCore介绍EntityFramework(EF)Core是轻量化、可扩展、开源和跨平台版的常用EntityFramework数据访问技术,EFCore是适用于.NET的......
  • 基于卷积神经网络的脊柱骨折识别系统,resnet50,mobilenet模型【pytorch框架+python】
       更多目标检测和图像分类识别项目可看我主页其他文章功能演示:基于卷积神经网络的脊柱骨折识别系统,resnet50,mobilenet【pytorch框架,python,tkinter】_哔哩哔哩_bilibili(一)简介基于卷积神经网络的脊柱骨折识别系统是在pytorch框架下实现的,这是一个完整的项目,包括代码,数据......
  • Entity Framework Core 中使用仓库和工作单元事务,服务层和控制器
    定义实体首先定义一个实体,例如Product:publicclassProduct{publicintId{get;set;}publicstringName{get;set;}publicdecimalPrice{get;set;}}CopyInsert2.创建DbContext创建一个DbContext类:publicclassAppDbContext:DbContext{public......
  • 使用Entity Framework Core(EF Core)进行开发时,结合仓库模式和工作单元模式,服务层以及控
    仓储(Repository)仓储模式封装对数据源的访问逻辑,包括CRUD操作。以下是一个简单的仓储接口和实现示例:publicinterfaceIRepositorywhereT:class{Task<IEnumerable>GetAllAsync();TaskGetByIdAsync(intid);TaskAddAsync(Tentity);TaskUpdateAsync(Tentity);Tas......
  • 如何在kubernetes环境中共享GPU
    随着人工智能和大模型的快速发展,云上GPU资源共享变得必要,因为它可以降低硬件成本,提升资源利用效率,并满足模型训练和推理对大规模并行计算的需求。在kubernetes内置的资源调度功能中,GPU调度只能根据“核数”进行调度,但是深度学习等算法程序执行过程中,资源占用比较高的是显存,这样就......
  • 操作方法分享:4G模组中移OneNET轻松上云平台
    ​一、简介 1.1IoT_CLOUD的功能IoT_CLOUD库本质就是上层设计一套通用的API,用库来实现每个平台功能的对接。目前已经实现了各个平台的所有注册方式,其中自动注册会将相关验证信息保存KV,随后使用此验证信息进行连接,通知针对每个平台添加了特有系统实现,比如:设备上线通知、设备......