UseAuthorization 及相关源代码 - dreamw - 博客园

mikel阅读(623)

来源: UseAuthorization 及相关源代码 – dreamw – 博客园

学习:《asp.net core 3.x 授权中的概念》《asp.net core 3.x 授权默认流程》

——————————————————————————————————–

在 Controller 中可以这样标注特性  /*注意,这里的名字取决于你添加AuthenticationHandler时的名字*/

[ApiController]
[Route("api/[controller]")]
public class ValueController : ControllerBase
{ [Authorize(AuthenticationSchemes ="jwtxxx", Policy ="role-policy")] 
     //[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]  
     [HttpPost]
     public async Task<IActionResult> Test()
} 
    


——————————————————————————————————–

app.UseAuthorization 是Aspnet Core 3.X 的启动 Startup 的配置授权中间件。

提示:授权 Authorization 与身份验证(Scheme)、endpoint 路由(元属性)有关联!

官方定义:https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.authorizationappbuilderextensions.useauthorization?view=aspnetcore-3.1

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authorization.authorizationmiddleware?view=aspnetcore-3.1

Github 库源代码如下:

public static class AuthorizationAppBuilderExtensions
{
public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}

VerifyServicesRegistered(app);

return app.UseMiddleware<AuthorizationMiddleware>();
}

private static void VerifyServicesRegistered(IApplicationBuilder app)
{
// 在调用 UseAuthorization 之前校验 AddAuthorizationPolicy
// 用 AuthorizationPolicyMarkerService 以保证所有 services  被 added.
if (app.ApplicationServices.GetService(typeof(AuthorizationPolicyMarkerService)) == null)
{
throw new InvalidOperationException(Resources.FormatException_UnableToFindServices(
nameof(IServiceCollection),
nameof(PolicyServiceCollectionExtensions.AddAuthorization),
“ConfigureServices(…)”));
}
}

——————   AuthorizationMiddleware  的源代码:

https://github.com/dotnet/aspnetcore/blob/b56f84131af2e1ece61241a016e191f5f2fe3fc0/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs

 

public class AuthorizationMiddleware
{
// Property key is used by Endpoint routing to determine if Authorization has run
private const string AuthorizationMiddlewareInvokedWithEndpointKey = “__AuthorizationMiddlewareWithEndpointInvoked”;
private static readonly object AuthorizationMiddlewareWithEndpointInvokedValue = new object();

private readonly RequestDelegate _next;
private readonly IAuthorizationPolicyProvider _policyProvider;

public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_policyProvider = policyProvider ?? throw new ArgumentNullException(nameof(policyProvider));
}

public async Task Invoke(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

var endpoint = context.GetEndpoint();

if (endpoint != null)
{
// EndpointRoutingMiddleware uses this flag to check if the Authorization middleware processed auth metadata on the endpoint.
// The Authorization middleware can only make this claim if it observes an actual endpoint.
context.Items[AuthorizationMiddlewareInvokedWithEndpointKey] = AuthorizationMiddlewareWithEndpointInvokedValue;
}

// IMPORTANT: Changes to authorization logic should be mirrored in MVC’s AuthorizeFilter
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
if (policy == null)
{
await _next(context);
return;
}

// Policy evaluator has transient lifetime so it fetched from request services instead of injecting in constructor
var policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>();

var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, context);

// Allow Anonymous skips all authorization
if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
{
await _next(context);
return;
}

// Note that the resource will be null if there is no matched endpoint
var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource: endpoint);

if (authorizeResult.Challenged)
{
if (policy.AuthenticationSchemes.Count > 0)
{
foreach (var scheme in policy.AuthenticationSchemes)
{
await context.ChallengeAsync(scheme);
}
}
else
{
await context.ChallengeAsync();
}

return;
}
else if (authorizeResult.Forbidden)
{
if (policy.AuthenticationSchemes.Count > 0)
{
foreach (var scheme in policy.AuthenticationSchemes)
{
await context.ForbidAsync(scheme);
}
}
else
{
await context.ForbidAsync();
}

return;
}

await _next(context);
}
}

 

[Authorize(AuthenticationSchemes JwtBearerDefaults.AuthenticationScheme)]
[Route(“api/[controller]”)]
[ApiController]
public class ValueController : ControllerBase

asp.net core 3.x 授权中的概念

mikel阅读(561)

ASP.NET core 3.x 授权中的概念,

 

前言

预计是通过三篇来将清楚ASP.NET core 3.x中的授权:1、基本概念介绍;2、ASP.NET core 3.x中授权的默认流程;3、扩展。

在完全没有概念的情况下无论是看官方文档还是源码都晕乎乎的,希望本文能帮到你。不过我也是看源码结合官方文档看的,可能有些地方理解不对,所以只作为参考。

要求对asp.net core的基础有所有了解:Host、依赖注入、日志、配置、选项模式、终结点路由、身份验证等。还是推荐A大博客

 

概述

归纳来说授权就是:某人  针对某个资源  可以做什么操作,如:张三   针对销售订单    可以查看、审核、取消等操作

  • 某人:这个好理解,只要登录系统的用户我们就晓得他是谁;额外的他属于某个角色、属于某个部门、甚至我们可以规定年龄段在18-30岁的能干什么,30-50岁的能干啥,这些都属于所属角色、所属部门、所属年龄段都是用户的一个属性,会作为权限判断的一个依据
  • 资源:可以任何形式的资源,比如销售订单、商品、等等;也可以有更复杂的规则,比如金额大于10000以上的,必须经过老总审核这种要求;另外比如一个页面也可以看做是资源,比如是否允许谁可以访问某个页面。对资源的限定也将作为权限判断的一部分
  • 操作:比如上面说的查看、审核、新增、修改..巴拉巴拉…当然操作也作为权限判断的一部分。

除了上面这3个概念外加一个权限判断逻辑,就组成了授权系统。下面逐一介绍asp.net core 3.x中授权系统涉及到的相关概念

注:
功能权限:这是我们通常说的某人(包含所属角色,所属部门等)可以访问某个菜单下的某个按钮
数据权限:上面说的订单金额大于10000的必须要经理角色才可以审核
这个说来也没啥区别,一个按钮通常是对应到mvc中的某个action,所以还是可以看成是操作;金额大于10000,这个也只是对资源的一种选定

还有一种情况,说一个按钮的点击对应到一个action,那么这个按钮到底是看做“操作”呢,还是把这个Action看成是一个页面地址,作为资源呢?这个就看怎么设计了,mvc默认是当做资源。

 

用户标识ClaimsPrincipal

ClaimsPrincipal理解为一个登录到系统的主体更合理。

在一个系统中可能同时存在多种身份验证方案,比如我们系统本身做了用户管理功能,使用最简单的cookie身份验证方案,或者使用第三方登录,微信、QQ、支付宝账号登录,通常一个身份验证方案可以产生一张证件(ClaimsIdentity),当然某个身份验证方案也可以将获得的Claim添加到一张现有的证件中,这个是灵活的。默认情况下,用户登录时asp.net core会选择设置好的默认身份验证方案做身份验证,本质是创建一个ClaimsPrincipal,并根据当前请求创建一个证件(ClaimsIdentity),然后将此ClaimsIdentity加入到ClaimsPrincipal,最后将这个ClaimsPrincipal设置到HttpContext.User属性上。身份验证不是本篇重点,详细描述参考:《asp.net core 3.x 身份验证-1涉及到的概念》。我们目前只要记住一个字符串代表一个身份验证方案,它可以从当前请求或第三方去获得一张证件(ClaimsIdentity)

当用户登录后,我们已经可以从HttpContext.User拿到当前用户,里面就包含一张或多张证件,后续的权限判断通常就依赖里面的信息,比如所属角色、所属部门,除了证件的信息我们也可以通过用户id去数据库中查询得到更多用户信息作为权限判断的依据。

 

资源

资源的概念很宽泛,上面说的销售订单、客户档案、属于资源,我们可以控制某个用户是否能查看、新增、审核订单。或者说一个页面也是一种资源,我们希望控制某用户是否能访问某个页面。在asp.net core中直接以object类型来表示资源,因为asp.net core作为一个框架,它不知道将来使用此框架的开发者到底是对什么类型的资源做权限限制。

在我们日常开发中经常在Action上应用AuthorizeAttribute标签来进行授权控制,其实这里就是将这个Action当做资源。由于目前asp.net core 3.x中默认使用终结点路由,所以现在在asp.net core 3.x中的默认授权流程中当前Endpoint就是资源

记住权限判断中不一定需要资源的参与,比如只要用户登录,就允许使用系统中所有功能。此时整个系统就是资源,允许所有操作。

 

授权依据IAuthorizationRequirement

试想这样一种权限需求:要求属于角色”经理”或”系统管理员”的用户可以访问系统任何功能。当我们做权限判断时我们可以从HttpContext.User得到当前用户,从其证件列表中总能找到当前用户的所属角色,那么这里需要进行比较的两个角色”经理”、”系统管理员”从哪里获得呢?
再比如:要求只要当前用户的证件中包含一个”特别通行证”的Calim,就允许他访问系统的任何功能。同上面的情况一样,在判断权限时我们可以知道当前登录用户的Calim列表,那需要进行比对的”特别通行证”这个字符串从哪来呢?

asp.net core将这种权限判断时需要用来比对的数据定义为IAuthorizationRequirement,我这里叫做”授权依据”,在一次权限判断中可能会存在多个判断,所以可能需要多个授权依据,文件后面会讲如何定制授权依据

其实某种意义上说“当前用户(及其包含的Calim列表)”也可以看做是一种依据,因为它也是在授权判断过程中需要访问的数据,但是这个我们是直接通过HttpContext.User来获取的,不需要我们来定义。

当我们针对某个页面或Action进行授权时可以直接从当前路由数据中获取Action名,在asp.net core 3.x中甚至更方便,可以在请求管道的早期就能获得当前请求的终结点。所以针对Action的访问也不需要定义成授权依据中

所以授权依据算是一种静态数据,为了更好的理解,下面列出asp.net core中已提供的几种授权依据

  • ClaimsAuthorizationRequirement
public string ClaimType { get; }
public IEnumerable<string> AllowedValues { get; }

将来在权限判断是会判断当前用户的Claim列表中是否包含一个类型为ClaimType的Claim,若AllowedValues有数据,则进一步判断是否完整包含AllowedValues中定义的值

  • DenyAnonymousAuthorizationRequirement:权限判断发现存在这个依据,则直接拒绝匿名用户访问
  • RolesAuthorizationRequirement:这就是最常见的基于角色的授权时会使用的,它定义了 public IEnumerable<string> AllowedRoles { get; } ,将来做权限判断时会看当前用户是否属于这里允许的角色中的一种
  • OperationAuthorizationRequirement:这个也比较常用,在做功能授权时比较常用。它定义了 public string Name { get; set; } ,Name代表当前操作名,比如“Order.Add”就是新增订单,将来权限判断是可以根据当前用户Id、所属角色和”Order.Add”到数据库去做对比
  • AssertionRequirement:这个就更强大了,它定义了  public Func<AuthorizationHandlerContext, Task<bool>> Handler { get; } ,将来权限判断时发现是这个类型,直接调用这个委托来进行权限判断,所以灵活性非常大

 

授权策略AuthorizationPolicy

策略同时作为身份验证方案和授权依据的容器,它包含本次授权需要的数据。

请求抵达时asp.net core会找到默认身份验证方案进行身份验证(根据请求获取用户ClaimsPrincipal),但有时候我们希望由自己来指定本次授权使用哪些身份验证验证方案,而不是使用默认的,这样将来身份验证过程中会调用设置的这几个身份验证方案去获得多张证件,此时HttpContext.User中就包含多张证件。所以授权策略里包含多种身份验证方案。

一次授权可能需要多种判断,比如同时判断所属角色、并且是否包含哪种类型的Calim并且…..,某些判断可能需要对比“授权依据”,所以一个授权策略包含多个授权依据。

另外我们可以将多个授权策略合并成一个对吗?所有的身份验证方案合并,所有的“授权依据”合并

将来授权检查时将根据身份验证方案获取当前用户的多个证件(里面包含很多Cliam可以用作权限判断),然后逐个判断授权依据,若都满足则认为授权检查成功。

若是针对某个资源的授权,授权方法大概是这样定义的xxxx.Authorize(策略,订单),这里不一定直接传入整个订单,可能只传入订单金额,这个根据业务需要。若是简单的情况只判断页面访问权限,则xxx.Authorize(策略),因为当前页面可以直接通过当前请求获取。

在asp.net core 3.x中启动阶段我们可以定义一个授权策略列表,这个看成是全局授权策略,一直存在于应用中。
在应用运行时,每次进行授权时会动态创建一个授权策略,这个策略是最终进行本次授权检查用的,它可能会引用某一个或多个全局策略,所谓的引用就是合并其“身份验证方案”列表和“授权依据列表”,当然其自身的“身份验证方案”列表和“授权依据列表”也是可以定制的,待会在AuthorizeAttribute部分再详细说

 

策略构造器AuthorizationPolicyBuilder

主要用来帮助创建一个授权策略(.net中典型的Builder模式),使用步骤是:

下面用伪代码感受下

var builder = new AuthorizationPolicyBuilder();
builder.RequireRole("Manager","Admin");
//builder....继续配置
var authorizationPolicy = builder.Build();

RequireRole将为最终会生成的策略中的“授权依据”列表加入一个RolesAuthorizationRequirement(“Manager”,”Admin”)。其它类似的api就不介绍了。

 

授权处理器AuthorizationHandler

上面说的当前用户、授权依据、以及授权时传递进来的资源都是可以看成是静态的数据,作为授权判断的依据,真正授权的逻辑就是用IAuthorizationHandler来表示的,先看看它的定义

public interface IAuthorizationHandler
{
    Task HandleAsync(AuthorizationHandlerContext context);
}

 

AuthorizationHandlerContext

中包含当前用户、授权依据列表和参与授权判断的资源,前者是根据授权策略中的多个身份验证方案经过身份验证后得到的;后者就是授权策略中的授权依据列表。在方法内部处理成功或失败的结果是直接存储到context对象上的。

一个应用中可以存在多个AuthorizationHandler,在执行授权检查时逐个调用它们进行检查,若都成功则本次授权成功。

 

针对特定授权依据类型  的  授权处理器AuthorizationHandler<TRequirement>

上面聊过授权依据是有多种类型的,将来还可能自定义,通常授权依据不同,授权的判断逻辑也不同。

  • 比如RolesAuthorizationRequirement这个授权依据,它里面包含角色列表,授权判断逻辑应该是判断当前用户是否属于这里面的角色;
  • 再比如OperationAuthorizationRequirement它里面定义了操作的名称,所以授权判断逻辑应该是拿当前用户以及它所属角色和这个操作(比如是“新增”)拿到数据库去做对比

所以这样看来一种“授权依据”类型应该对应一种“授权处理器”,所以微软定义了public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler ,这个TRequirement就代表这个授权处理器类型是针对哪种类型的“授权依据的”

一个授权策略AuthorizationPolicy是包含多个“授权依据”的,这其中可能有几个“授权依据”的类型是一样的,只是里面存储的值不同,以OperationAuthorizationRequirement为例,一个授权策略里可能包含如下授权依据列表:

new OperationAuthorizationRequirement{ Name="新增" }
new OperationAuthorizationRequirement{ Name="审核" }
new RolesAuthorizationRequirement("Manager","Admin");
//其它。。。

所以一个授权处理器AuthorizationHandler虽然只关联一种类型“授权依据”,但是一个授权处理器实例可以处理多个相同类型的“授权依据”

在授权过程中,每个AuthorizationHandler<TRequirement>会找到自己能处理的“授权依据”,逐个进行检查

 

针对特定授权依据类型、特定类型的资源  的  授权处理器AuthorizationHandler<TRequirement, TResource>

定义是这样的 public abstract class AuthorizationHandler<TRequirement, TResource> : IAuthorizationHandler
跟AuthorizationHandler<TRequirement>定义及处理逻辑唯一的区别是多了个TResource,在授权过程中是可以对给定资源进行判断的,资源在AuthorizationHandlerContext.Resource,这个是object类型,为了方便子类降重重写,所以由这里的父类将AuthorizationHandlerContext.Resource转换为TResource

干脆贴下源码吧

 1 public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler 2 where TRequirement : IAuthorizationRequirement 3 { 4 public virtual async Task HandleAsync(AuthorizationHandlerContext context) 5 { 6 foreach (var req in context.Requirements.OfType<TRequirement>()) 7 { 8 await HandleRequirementAsync(context, req); 9 } 10 } 11 12 protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement); 13 } 14 15 public abstract class AuthorizationHandler<TRequirement, TResource> : IAuthorizationHandler 16 where TRequirement : IAuthorizationRequirement 17 { 18 public virtual async Task HandleAsync(AuthorizationHandlerContext context) 19 { 20 if (context.Resource is TResource) 21 { 22 foreach (var req in context.Requirements.OfType<TRequirement>()) 23 { 24 await HandleRequirementAsync(context, req, (TResource)context.Resource); 25 } 26 } 27 } 28 29 protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, TResource resource); 30 } View Code

 

合并AuthorizationHandler & AuthorizationRequirement

我们发现通常一个授权依据的类型会有个对应的授权处理器,如果只定义一个类,实现这两种接口事情不是更简单吗?举个例子:

 1 public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement 2 { 3 public RolesAuthorizationRequirement(IEnumerable<string> allowedRoles) 4 { 5 AllowedRoles = allowedRoles; 6 } 7 public IEnumerable<string> AllowedRoles { get; } 8 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement) 9 { 10 if (context.User != null) 11 { 12 bool found = false; 13 if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any()) 14 { 15 // Review: What do we want to do here? No roles requested is auto success? 16 } 17 else 18 { 19 found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r)); 20 } 21 if (found) 22 { 23 context.Succeed(requirement); 24 } 25 } 26 return Task.CompletedTask; 27 } 28 } View Code

我们上面讲的微软定义的那几个授权依据基本都是这样定义的

 

何时实施授权检查?

如果是用的asp.net core 3.x之前的版本,那么在Action执行前做授权判断比较合适,常用的就是过滤器Filter咯。这个我不是特别确定,至少在.net framework时代是用的授权过滤器AuthorizeAttribute
请求  >  其它中间件  >   路由中间件  >  身份验证中间件  >  MVC中间件  >  Controller  >  [授权过滤器]Action

若是asp.net core 3.x之后,由于目前用的终结点路由,所以在 路由中间件 和 身份验证中间件  后做权限判断(使用授权中间件)比较合适,因为 路由中间件执行后我们可以从当前请求上下文中获取当前终结点(它代表一个Action或一个页面)。身份验证中间件执行后可以通过HttpContext.User获取当前用户,此时有了访问的页面和当前用户 就可以做权限判断了
请求  >  其它中间件  >   路由中间件(这里就拿到终结点了)  >  身份验证中间件  >  授权中间件  >  MVC中间件  >  Controller  >  Action

还有一种情况是在业务代码内部去执行权限判断,比如:希望销售订单金额大于10000的,必须要经理角色才可以审核,此时因为我们要先获取订单才知道它的金额,所以我们最好在Action执行内部根据路由拿到订单号,去数据库查询订单金额后,调用某个方法执行权限判断。

 

授权服务AuthorizationService

所以执行权限判断的点不同,AuthorizationService就是用来封装授权检查的,我们在不同的点都可以来调用它执行权限判断。看看接口定义

public interface IAuthorizationService
{
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);
}

user:要进行判断的用户,它里面可能存在一张或多张证件
resource:可能是一个终结点,也可能是一个页面RazorPage,也可能是一个订单(或者是单独的订单金额)
requirements:授权依据列表
policyName:一个授权策略名

在授权中间件和在业务逻辑代码中手动进行授权检查时都是调用此接口
它内部会去调用AuthorizationHandler来进行权限判断。

 

定制授权依据AuthorizeAttribute : IAuthorizeData

在asp.net core 3.x中 启动阶段可以配置一个全局策略列表,它一直存在于系统中,暂时称为“静态策略列表”
在每次执行授权检查时也需要一个策略,暂时称为“运行时授权策略”,授权中间件执行时就会创建此策略,然后调用AuthorizationService根据此策略进行权限判断,那此策略中的“授权依据”和“身份验证方案”这俩列表从哪来的呢?就是在Action通过AuthorizeAttribute来定制的,它实现 IAuthorizeData接口

如果你对早期版本mvc有一丢丢了解的话,你可能记得有个授权过滤器的概念AuthorizeAttribute,在Action执行前会先去做授权判断,若成功才会继续执行Action,否则就返回403.

在asp.net core 3.x中不是这样了,AuthorizeAttribute只是用来定制当前授权策略(AuthorizationPolicy)的,并不是过滤器,它实现IAuthorizeData接口,此接口定义如下:

public interface IAuthorizeData
{
    string Policy { get; set; }//直接指定此Action将来授权验证时要使用的授权策略AuthorizationPolicy,此策略会被合并到当前授权策略
    string Roles { get; set; } //它会创建一个基于角色的授权依据RolesAuthorizationRequirement,此依据会被放入当前授权策略
    string AuthenticationSchemes { get; set; }//它用来定义当前授权策略里要使用哪些身份验证方案
}

Policy属性指明从“静态策略列表”拿到指定策略,然后将其“授权依据”和“身份验证方案”这俩列表合并到“运行时授权策略”

看个例子:

1 [Authorize(AuthenticationSchemes = "cookie,jwtBearer")]
2 [Authorize(Roles = "manager,admin")]
3 [Authorize(policy:"test")]
4 [Authorize]
5 public IActionResult Privacy()
6 {
7      return View();
8 }

以上定制只是针对使用授权中间件来做权限判断时,对当前授权策略进行定制。若我们直接在业务代码中调用AuthorizationService手动进行权限判断呢,就截止调用咯。参考上面的描述

 

授权中间件AuthorizationMiddleware

上面我们介绍了何时实施授权检查,授权中间件(AuthorizationMiddleware)就是其中最为常用的一个授权检查点,相当于是一个授权检查的入口方法,它在进入MVC中间件之前就可以做授权判断,所以比之前的在Action上做判断更早。并且由于授权检查是根据终结点的,因此同一套授权代码可以应用在mvc/webapi/razorPages…等多种web框架。由于授权检查依赖当前访问的终结点(若不理解终结点,可以暂时认为它=Action及其之上应用的各种Attribute) 和 当前登录用户,因此  授权中间件  应该在   路由中间件 和 身份验证中间件  之后注册

 1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 2 {
 3        //略....
 4        app.UseHttpsRedirection();
 5        app.UseStaticFiles();
 6        app.UseRouting();
 7        app.UseAuthentication();
 8        app.UseAuthorization();
 9        app.UseEndpoints(endpoints =>
10        {
11            endpoints.MapRazorPages();
12        });
13 }

它的核心步骤大致如下:

所以重点是可以在执行mvc中间件之前拿到终结点及其之上定义的AuthorizeAttribute,从其中的数据就可以构建出本次权限判断的“授权策略”,有了授权策略就可以通过AuthorizationService执行授权判断,内部会使用到授权处理器AuthorizationHandler

 

结束

暂时就BB到这里,先有个大概印象,下一篇按asp.net core的默认授权流程走走源码,再结合此篇应该就差不多了…

C#操作MongoDB入门 - 荆棘中的百合花 - 博客园

mikel阅读(708)

来源: C#操作MongoDB入门 – 荆棘中的百合花 – 博客园

1、MongoDB安装及配置

(1)下载:

mongodb官网 https://www.mongodb.com/download-center

进入官网下载页,你会发现版本都是windows Server……  但是不要担心,本人亲测win10也可以用,所以放心大胆的下载就可以了。安装过程比较简单,下一步就可以了,就不多说了。

(2)配置

在“E:\MongoDB“(与默认安装位置不同,需要自己找到这个文件夹)目录下新建“data”文件夹,它将会作为数据存放的根文件夹。

在“E:\MongoDB”目录下新建“log”文件夹,作为日志文件夹。

 

    配置Mongo服务端:

以管理员身份打开CMD窗口,按照如下方式输入命令:
> E:
> cd MongoDB

>cd bin
> mongod –dbpath “E:\MongoDB\data”

 

然后在浏览器输入:http://localhost:27017/,可以看到如下提示:

You are trying to access MongoDB on the native driver port. For http diagnostic access, add 1000 to the port number (可能会和这个不太一样,有字就行)

如此,MongoDB数据库服务已经成功启动了。

 

封装服务:

还是以管理员身份运行cmd,进入bin目录,执行下列命令
>mongod -dbpath “D:\MongoDB\data” -logpath “D:\MongoDB\log\MongoDB.log” -install -serviceName “MongoDB”

这里–MongoDB.log就是开始建立的日志文件,–serviceName “MongoDB” 服务名为MongoDB。

接着启动mongodb服务

> E:\MongoDB>NET START MongoDB

服务启动成功后可以测试一下

进入bin目录,输入mongo显示如下信息,则服务启动成功。

E:\MongoDB\bin>mongo
MongoDB shell version: 3.2.9
connecting to: test
>

至此安装配置就完成了。

2、安装MongoDB的C#驱动

打开C#,新建项目,点击 工具>Nuget程序包管理器>管理解决方案的Nuget程序包>联机,

在搜索栏搜索mongodb

安装MongoDB.Driver,MongoDB.Bson,MongoDB.Driver.Core三个程序包。

然后添加引用

using MongoDB.Driver;
using MongoDB.Bson;

3、代码

接下来就是写代码了,以下是我写的最简单的增删改查操作(水平有限,凑合看)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using MongoDB.Driver;
using MongoDB.Bson;
namespace MongoDBTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            Mongo();
        }
        public void Mongo()
        {
            //建立连接
            var client = new MongoClient();
            //建立数据库
            var database = client.GetDatabase("TestDb");
            //建立collection
            var collection = database.GetCollection<BsonDocument>("foo");
            var document = new BsonDocument
            {
                {"name","MongoDB"},
                {"type","Database"},
                {"count",1},
                {"info",new BsonDocument{{"x",203},{"y",102}}}
            };
            //插入数据
            collection.InsertOne(document);
            var count = collection.Count(document);
            Console.WriteLine(count);
            //查询数据
            var document1 = collection.Find(document);
            Console.WriteLine(document1.ToString());
            //更新数据
            var filter = Builders<BsonDocument>.Filter.Eq("name""MongoDB");
            var update = Builders<BsonDocument>.Update.Set("name""Ghazi");
            collection.UpdateMany(filter, update);
            //删除数据
            var filter1 = Builders<BsonDocument>.Filter.Eq("count", 101);
            collection.DeleteMany(filter1);
            BsonDocument document2 = new BsonDocument();
            document2.Add("name""MongoDB");
            document2.Add("type""Database");
            document2.Add("count""1");
            collection.InsertOne(document2);
        }
    }
}

 

4、参考资料

http://mongodb.github.io/mongo-csharp-driver/2.2/getting_started/ 这个一定要静下心去看,虽然是英文的,但是看代码就行,还是不难的。

http://www.cnblogs.com/qq75077027/category/441114.html 这个是大神写的,讲的很全面。

http://wenku.baidu.com/link?url=NYAYJfx907QwrzVmcvrZAYqZUML6tiqClsJMNIxKjmR MongoDB权威指南中文版。

Redis的事务功能详解_一念永恒-CSDN博客_redis事务

mikel阅读(491)

来源: Redis的事务功能详解_一念永恒-CSDN博客_redis事务

MULTI、EXEC、DISCARD和WATCH命令是Redis事务功能的基础。Redis事务允许在一次单独的步骤中执行一组命令,并且可以保证如下两个重要事项:

Redis会将一个事务中的所有命令序列化,然后按顺序执行。Redis不可能在一个Redis事务的执行过程中插入执行另一个客户端发出的请求。这样便能保证Redis将这些命令作为一个单独的隔离操作执行。 > 在一个Redis事务中,Redis要么执行其中的所有命令,要么什么都不执行。因此,Redis事务能够保证原子性。EXEC命令会触发执行事务中的所有命令。因此,当某个客户端正在执行一次事务时,如果它在调用MULTI命令之前就从Redis服务端断开连接,那么就不会执行事务中的任何操作;相反,如果它在调用EXEC命令之后才从Redis服务端断开连接,那么就会执行事务中的所有操作。当Redis使用只增文件(AOF:Append-only File)时,Redis能够确保使用一个单独的write(2)系统调用,这样便能将事务写入磁盘。然而,如果Redis服务器宕机,或者系统管理员以某种方式停止Redis服务进程的运行,那么Redis很有可能只执行了事务中的一部分操作。Redis将会在重新启动时检查上述状态,然后退出运行,并且输出报错信息。使用redis-check-aof工具可以修复上述的只增文件,这个工具将会从上述文件中删除执行不完全的事务,这样Redis服务器才能再次启动。

从2.2版本开始,除了上述两项保证之外,Redis还能够以乐观锁的形式提供更多的保证,这种形式非常类似于“检查再设置”(CAS:Check And Set)操作。本文稍后会对Redis的乐观锁进行描述。

一、相关命令
1. MULTI
用于标记事务块的开始。Redis会将后续的命令逐个放入队列中,然后才能使用EXEC命令原子化地执行这个命令序列。

这个命令的运行格式如下所示:

MULTI

这个命令的返回值是一个简单的字符串,总是OK。

2. EXEC
在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。

当使用WATCH命令时,只有当受监控的键没有被修改时,EXEC命令才会执行事务中的命令,这种方式利用了检查再设置(CAS)的机制。

这个命令的运行格式如下所示:

EXEC

这个命令的返回值是一个数组,其中的每个元素分别是原子化事务中的每个命令的返回值。 当使用WATCH命令时,如果事务执行中止,那么EXEC命令就会返回一个Null值。

3. DISCARD
清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。

如果使用了WATCH命令,那么DISCARD命令就会将当前连接监控的所有键取消监控。

这个命令的运行格式如下所示:

DISCARD
这个命令的返回值是一个简单的字符串,总是OK。

4. WATCH
当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的。

这个命令的运行格式如下所示:

WATCH key [key …]
这个命令的返回值是一个简单的字符串,总是OK。

对于每个键来说,时间复杂度总是O(1)。

5. UNWATCH
清除所有先前为一个事务监控的键。

如果你调用了EXEC或DISCARD命令,那么就不需要手动调用UNWATCH命令。

这个命令的运行格式如下所示:

UNWATCH
这个命令的返回值是一个简单的字符串,总是OK。

时间复杂度总是O(1)。

二、使用方法
使用MULTI命令便可以进入一个Redis事务。这个命令的返回值总是OK。此时,用户可以发出多个Redis命令。Redis会将这些命令放入队列,而不是执行这些命令。一旦调用EXEC命令,那么Redis就会执行事务中的所有命令。

相反,调用DISCARD命令将会清除事务队列,然后退出事务。

以下示例会原子化地递增foo键和bar键的值:

 

 

 

正如从上面的会话所看到的一样,EXEC命令的返回值是一个数组,其中的每个元素都分别是事务中的每个命令的返回值,返回值的顺序和命令的发出顺序是相同的。

当一个Redis连接正处于MULTI请求的上下文中时,通过这个连接发出的所有命令的返回值都是QUEUE字符串(从Redis协议的角度来看,返回值是作为状态回复(Status Reply)来发送的)。当调用EXEC命令时,Redis会简单地调度执行事务队列中的命令。

三、事务内部的错误
在一个事务的运行期间,可能会遇到两种类型的命令错误:

一个命令可能会在被放入队列时失败。因此,事务有可能在调用EXEC命令之前就发生错误。例如,这个命令可能会有语法错误(参数的数量错误、命令名称错误,等等),或者可能会有某些临界条件(例如:如果使用maxmemory指令,为Redis服务器配置内存限制,那么就可能会有内存溢出条件)。
在调用EXEC命令之后,事务中的某个命令可能会执行失败。例如,我们对某个键执行了错误类型的操作(例如,对一个字符串(String)类型的键执行列表(List)类型的操作)。

可以使用Redis客户端检测第一种类型的错误,在调用EXEC命令之前,这些客户端可以检查被放入队列的命令的返回值:如果命令的返回值是QUEUE字符串,那么就表示已经正确地将这个命令放入队列;否则,Redis将返回一个错误。如果将某个命令放入队列时发生错误,那么大多数客户端将会中止事务,并且丢弃这个事务。

然而,从Redis 2.6.5版本开始,服务器会记住事务积累命令期间发生的错误。然后,Redis会拒绝执行这个事务,在运行EXEC命令之后,便会返回一个错误消息。最后,Redis会自动丢弃这个事务。

在Redis 2.6.5版本之前,如果发生了上述的错误,那么在客户端调用了EXEC命令之后,Redis还是会运行这个出错的事务,执行已经成功放入事务队列的命令,而不会关心先前发生的错误。从2.6.5版本开始,Redis在遭遇上述错误时,会采用先前描述的新行为,这样便能轻松地混合使用事务和管道。在这种情况下,客户端可以一次性地将整个事务发送至Redis服务器,稍后再一次性地读取所有的返回值。

相反,在调用EXEC命令之后发生的事务错误,Redis不会进行任何特殊处理:在事务运行期间,即使某个命令运行失败,所有其他的命令也将会继续执行。

这种行为在协议层面上更加清晰。在以下示例中,当事务正在运行时,有一条命令将会执行失败,即使这条命令的语法是正确的:

 

 

 

上述示例的EXEC命令的返回值是批量的字符串,包含两个元素,一个是OK代码,另一个是-ERR错误消息。客户端会根据自身的程序库,选择一种合适的方式,将错误信息提供给用户

需要注意的是,即使某个命令执行失败,事务队列中的所有其他命令仍然会执行 —— Redis不会停止执行事务中的命令。

再看另一个示例,再次使用telnet通信协议,观察命令的语法错误是如何尽快报告给用户的:

 

 

 

这一次,由于INCR命令的语法错误,Redis根本就没有将这个命令放入事务队列。

四、为什么Redis不支持回滚?
如果你具备关系型数据库的知识背景,你就会发现一个事实:在事务运行期间,虽然Redis命令可能会执行失败,但是Redis仍然会执行事务中余下的其他命令,而不会执行回滚操作,你可能会觉得这种行为很奇怪。

然而,这种行为也有其合理之处:

只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。
Redis已经在系统内部进行功能简化,这样可以确保更快的运行速度,因为Redis不需要事务回滚的能力。

对于Redis事务的这种行为,有一个普遍的反对观点,那就是程序有可能会有缺陷(bug)。但是,你应当注意到:事务回滚并不能解决任何程序错误。例如,如果某个查询会将一个键的值递增2,而不是1,或者递增错误的键,那么事务回滚机制是没有办法解决这些程序问题的。请注意,没有人能解决程序员自己的错误,这种错误可能会导致Redis命令执行失败。正因为这些程序错误不大可能会进入生产环境,所以我们在开发Redis时选用更加简单和快速的方法,没有实现错误回滚的功能。

五、丢弃命令队列
DISCARD命令可以用来中止事务运行。在这种情况下,不会执行事务中的任何命令,并且会将Redis连接恢复为正常状态。示例如下所示:

 

 

 

六、通过CAS操作实现乐观锁
Redis使用WATCH命令实现事务的“检查再设置”(CAS)行为。

作为WATCH命令的参数的键会受到Redis的监控,Redis能够检测到它们的变化。在执行EXEC命令之前,如果Redis检测到至少有一个键被修改了,那么整个事务便会中止运行,然后EXEC命令会返回一个Null值,提醒用户事务运行失败。

例如,设想我们需要将某个键的值自动递增1(假设Redis没有INCR命令)。

首次尝试的伪码可能如下所示:

val = GET mykey
val = val + 1
SET mykey $val
如果我们只有一个Redis客户端在一段指定的时间之内执行上述伪码的操作,那么这段伪码将能够可靠的工作。如果有多个客户端大约在同一时间尝试递增这个键的值,那么将会产生竞争状态。例如,客户端-A和客户端-B都会读取这个键的旧值(例如:10)。这两个客户端都会将这个键的值递增至11,最后使用SET命令将这个键的新值设置为11。因此,这个键的最终值是11,而不是12。

现在,我们可以使用WATCH命令完美地解决上述的问题,伪码如下所示:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
由上述伪码可知,如果存在竞争状态,并且有另一个客户端在我们调用WATCH命令和EXEC命令之间的时间内修改了val变量的结果,那么事务将会运行失败。

我们只需要重复执行上述伪码的操作,希望此次运行不会再出现竞争状态。这种形式的锁就被称为乐观锁,它是一种非常强大的锁。在许多用例中,多个客户端可能会访问不同的键,因此不太可能发生冲突 —— 也就是说,通常没有必要重复执行上述伪码的操作。

七、WATCH命令详解
那么WATCH命令实际做了些什么呢?这个命令会使得EXEC命令在满足某些条件时才会运行事务:我们要求Redis只有在所有受监控的键都没有被修改时,才会执行事务。(但是,相同的客户端可能会在事务内部修改这些键,此时这个事务不会中止运行。)否则,Redis根本就不会进入事务。(注意,如果你使用WATCH命令监控一个易失性的键,然后在你监控这个键之后,Redis再使这个键过期,那么EXEC命令仍然可以正常工作。)

WATCH命令可以被调用多次。简单说来,所有的WATCH命令都会在被调用之时立刻对相应的键进行监控,直到EXEC命令被调用之时为止。你可以在单条的WATCH命令之中,使用任意数量的键作为命令参数。

当调用EXEC命令时,所有的键都会变为未受监控的状态,Redis不会管事务是否被中止。当一个客户单连接被关闭时,所有的键也都会变为未受监控的状态。

你还可以使用UNWATCH命令(不需要任何参数),这样便能清除所有的受监控键。当我们对某些键施加乐观锁之后,这个命令有时会非常有用。因为,我们可能需要运行一个用来修改这些键的事务,但是在读取这些键的当前内容之后,我们可能不打算继续进行操作,此时便可以使用UNWATCH命令,清除所有受监控的键。在运行UNWATCH命令之后,Redis连接便可以再次自由地用于运行新事务。

如何使用WATCH命令实现ZPOP操作呢?

本文将通过一个示例,说明如何使用WATCH命令创建一个新的原子化操作(Redis并不原生支持这个原子化操作),此处会以实现ZPOP操作为例。这个命令会以一种原子化的方式,从一个有序集合中弹出分数最低的元素。以下源码是最简单的实现方式:

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
如果伪码中的EXEC命令执行失败(例如,返回Null值),那么我们只需要重复运行这个操作即可。

八、Redis脚本和事务
根据定义,Redis脚本也是事务型的。因此,你可以通过Redis事务实现的功能,同样也可以通过Redis脚本来实现,而且通常脚本更简单、更快速。

由于Redis从2.6版本才开始引入脚本特性,而事务特性是很久以前就已经存在的,所以目前的版本才有两个看起来重复的特性。但是,我们不太可能在短时间内移除对事务特性的支持。因为,即使不用求助于Redis脚本,用户仍然能够规避竞争状态,这从语义上来看是适宜的。还有另一个更重要的原因,Redis事务特性的实现复杂度是最小的。

但是,在相当长的一段时间之内,我们不大可能看到整个用户群体都只使用Redis脚本。如果发生这种情况,那么我们可能会废弃,甚至最终移除Redis事务。
————————————————
版权声明:本文为CSDN博主「森林屿麓」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013967628/article/details/84501515

用宝塔本地搭建php,Windows系统如何使用宝塔面板一键快速搭建本地服务器环境(LNMP/LAMP)..._无名沙的博客-CSDN博客

mikel阅读(889)

来源: 用宝塔本地搭建php,Windows系统如何使用宝塔面板一键快速搭建本地服务器环境(LNMP/LAMP)…_无名沙的博客-CSDN博客

宝塔面板

宝塔面板类似于wamp,xampp等一键集成环境。但是宝塔面板操作更简单,更新更快。个人建议使用宝塔替代wamp。

(LNMP等环境不再赘述,请自行百度。建议使用LNMP环境,即Nginx+MySQL+PHP环境)

下载

前往宝塔面板官网https://www.bt.cn/,点击Windows版,然后点击立即安装,下载好之后是一个很小压缩包,安装程序就在其中。(图片见文章头图)

安装

直接一键安装,宝塔面板的安装目录请添加至杀毒软件的白名单里面,否则可能导致服务无法启动。

安装环境

配置环境

30744f580054b6c2ca640dc0a44a5ccf.png

安装好之后,点击最顶部的环境,依次安装Nginx,PHP,MySQL

10f560fe92db6a0b19e632c188c2003d.png

初始化面板密码

e76b7e7507c0d2a369c03be2bd5d9099.png

这一步很重要,在右上角点击选项(三条横线),然后点击初始化密码。此密码是用来登录web面板得账户与密码。

添加站点

ed7c0d2a96f22e6217a8066191720c6e.png

打开web面板之后,登录。

a5b14130935ebc22b5c52284f32757fc.png

然后在左侧点击网站,域名就是网址,输入127.0.0.1,创建数据库,记住账号与密码。

在浏览器输入127.0.0.1即可访问新建的网站

其余软件安装

进入web面板之后,在左侧点击软件管理,可以看到PHPmyadmin等软件,直接点击安装即可。

数据库管理请点击相应菜单,直接选择即可。

修改数据库端口

进入web面板之后,点击左侧软件管理,找到MySQL,点击右边的设置

如图,找到端口,修改即可。

9cca176560a83f7a8a99b186c2c845d2.png

其余软件端口修改

ftp,ssh等端口修改与MySQL一致

建站:

上传建站程序到后台,绑定域名即可,譬如小程序后端程序、PC建站程序

ab9b75b15d14f25114c6d1e0df74549b.png

一般而言,小程序都有前端显示代码和后端逻辑层代码,所以上传到服务器上的都是后台文件。

现在的小程序开源软件很多,一般都是开源直接下载到的,截图仅是个案,大家实际操作的时候以对应项目为准。

如果有什么需要指导的也可以加我QQ好友,一起交流1975768644

文章来源:https://www.src1024.com/xy/jc/2135.html

ASP.NET Web API与Owin OAuth:调用与用户相关的Web API - dudu - 博客园

mikel阅读(760)

来源: ASP.NET Web API与Owin OAuth:调用与用户相关的Web API – dudu – 博客园

在前一篇博文中,我们通过以 OAuth 的 Client Credential Grant 授权方式(只验证调用客户端,不验证登录用户)拿到的 Access Token ,成功调用了与用户无关的 Web API。

在这篇博文中,我们将以 OAuth 的 Resource Owner Password Credentials Grant 的授权方式( grant_type=password )获取 Access Token,并以这个 Token 调用与用户相关的 Web API。

对应的应用场景是:为自家的网站开发手机 App(非第三方 App),只需用户在 App 上登录,无需用户对 App 所能访问的数据进行授权。

根据 OAuth 规范,客户端获取 Access Token 的请求方式如下:

复制代码
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w
复制代码

根据上面的请求方式,在 C# 中用 HttpClient 实现一个简单的客户端,代码如下:

复制代码
public class OAuthClientTest
{
    private HttpClient _httpClient;

    public OAuthClientTest()
    {
        _httpClient = new HttpClient();
        _httpClient.BaseAddress = new Uri("http://openapi.cnblogs.com");
    } 

    [Fact]
    public async Task Get_Accesss_Token_By_Resource_Owner_Password_Credentials_Grant()
    {
        Console.WriteLine(await GetAccessToken());
    }

    private async Task<string> GetAccessToken()
    {
        var clientId = "1234";
        var clientSecret = "5678";

        var parameters = new Dictionary<string, string>();            
        parameters.Add("grant_type", "password");
        parameters.Add("username", "博客园团队");
        parameters.Add("password", "cnblogs.com");

        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
            "Basic",
            Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret))
            );

        var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
        var responseValue = await response.Content.ReadAsStringAsync();
        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            return JObject.Parse(responseValue)["access_token"].Value<string>();
        }
        else
        {
            Console.WriteLine(responseValue);
            return string.Empty;
        }
    }
}
复制代码

(注:与之前相比,这里的 client_id/client_secret 改为了 Basic Authorization,以更好的遵循 OAuth 规范)

在服务端,基于 Owin OAuth, 针对 Resource Owner Password Credentials Grant 的授权方式,只需重载 OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials() 方法即可。代码如下:

复制代码
public class CNBlogsAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    //...

    public override async Task GrantResourceOwnerCredentials(
        OAuthGrantResourceOwnerCredentialsContext context)
    {
        //调用后台的登录服务验证用户名与密码

        var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
        oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
        context.Validated(ticket);

        await base.GrantResourceOwnerCredentials(context);
    }
}
复制代码

完整的CNBlogsAuthorizationServerProvider实现代码如下(与之前相比,context.TryGetFormCredentials 改为了 context.TryGetBasicCredentials):

复制代码
public class CNBlogsAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        string clientId;
        string clientSecret;
        context.TryGetBasicCredentials(out clientId, out clientSecret);

        if (clientId == "1234"
            && clientSecret == "5678")
        {
            context.Validated(clientId);
        }

        await base.ValidateClientAuthentication(context);
    }

    public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
    {
        var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
        var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
        context.Validated(ticket);

        await base.GrantClientCredentials(context);
    }

    public override async Task GrantResourceOwnerCredentials(
        OAuthGrantResourceOwnerCredentialsContext context)
    {
        //调用后台的登录服务验证用户名与密码

        var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
        oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
        context.Validated(ticket);

        await base.GrantResourceOwnerCredentials(context);
    }
}
复制代码

这样,运行客户端程序就可以拿到 Access Token 了。

接下来,我们拿着以这种方式获取的 Access Token,就可以调用与用户相关的 Web API 了。

在服务端我们通过一个简单的 Web API 测试一下,代码如下:

复制代码
public class UsersController : ApiController
{
    [Authorize]
    public string GetCurrent()
    {
        return User.Identity.Name;
        //这里可以调用后台用户服务,获取用户相关数所,或者验证用户权限进行相应的操作
    }
}
复制代码

然后,客户端用以 grant_type=password 方式拿到的 Access Token 调用这个Web API,客户端增加的代码如下:

复制代码
[Fact]
public async Task Call_WebAPI_By_Resource_Owner_Password_Credentials_Grant()
{
    var token = await GetAccessToken();
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    Console.WriteLine(await (await _httpClient.GetAsync("/api/users/current")).Content.ReadAsStringAsync());
}
复制代码

客户端运行结果如下:

"博客园团队"

调用成功!运行结果正是获取 Access Token 时所用的 username 。

结合 ASP.NET 现有的安全机制,借助 OWIN 的威力,Microsoft.Owin.Security.OAuth 的确让开发基于 OAuth 的 Web API 变得更简单。

ASP.NET Web API与Owin OAuth:使用Access Toke调用受保护的API - dudu - 博客园

mikel阅读(671)

来源: ASP.NET Web API与Owin OAuth:使用Access Toke调用受保护的API – dudu – 博客园

在前一篇博文中,我们使用OAuth的Client Credential Grant授权方式,在服务端通过CNBlogsAuthorizationServerProvider(Authorization Server的一个实现)成功发放了Access Token,并在客户端成功拿到了Access Token。

那Access Token有什么用呢?在OAuth中对Resource Server(比如Web API)访问权限的验证都是基于Access Token。不管是什么样的客户端来调用,Resource Server总是铁面无私,只认Access Token。

ASP.NET Web API中启用OAuth的Access Token验证非常简单,只需在相应的Controller或Action加上[Authorize]标记,比如:

复制代码
[Authorize]
public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
}
复制代码

加上[Authorize]之后,如果不使用Access Token,调用API时就会出现如下的错误:

{"Message":"Authorization has been denied for this request."}

这时你也许会问,为什么一加上[Authorize]就会有这个效果?原来的Forms验证怎么不起作用了?

原因是你在用Visual Studio创建ASP.NET Web API项目时,VS自动帮你添加了相应的代码,打开WebApiConfig.cs,你会看到下面这2行代码:

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

就是这2行代码,改变了[Authorize]的作用。

在ASP.NET Web API中启用OAuth验证就这么简单(简单的背后是微软实现了基于OWIN的OAuth,实现源代码在Katana项目中)。

那在客户端如何使用Access Token调用Web API呢?

也很简单,只要在http请求头中加上Bearer:Token即可,客户端调用示例代码如下:

复制代码
    public class OAuthClientTest
    {
        private HttpClient _httpClient;

        public OAuthClientTest()
        {
            _httpClient = new HttpClient();
            _httpClient.BaseAddress = new Uri("http://openapi.cnblogs.com");
        }

        [Fact]
        public async Task Call_WebAPI_By_Access_Token()
        {
            var token = await GetAccessToken();
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
            Console.WriteLine(await (await _httpClient.GetAsync("/api/values")).Content.ReadAsStringAsync());
        }

        private async Task<string> GetAccessToken()
        {
            var parameters = new Dictionary<string, string>();
            parameters.Add("client_id", "1234");
            parameters.Add("client_secret", "5678");
            parameters.Add("grant_type", "client_credentials");

            var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
            var responseValue = await response.Content.ReadAsStringAsync();                

            return JObject.Parse(responseValue)["access_token"].Value<string>();
        }
    }
复制代码

运行结果如下:

["value1","value2"]

搞定!

ASP.NET Web API与基于Owin实现的OAuth的整合,让原本复杂的问题变得简单。

在ASP.NET中基于Owin OAuth使用Client Credentials Grant授权发放Token - dudu - 博客园

mikel阅读(703)

来源: 在ASP.NET中基于Owin OAuth使用Client Credentials Grant授权发放Token – dudu – 博客园

OAuth真是一个复杂的东东,即使你把OAuth规范倒背如流,在具体实现时也会无从下手。因此,Microsoft.Owin.Security.OAuth应运而生(它的实现代码在Katana项目中),帮助开发者偷了不少工,减了不少料。

这篇博文试图通过一个简单的示例分享一下如何基于Microsoft.Owin.Security.OAuth,使用Client Credentials Grant授权方式给客户端发放access token。

Client Credentials Grant的授权方式就是只验证客户端(Client),不验证用户(Resource Owner),只要客户端通过验证就发access token。举一个对应的应用场景例子,比如我们想提供一个“获取网站首页最新博文列表”的WebAPI给iOS App调用。由于这个数据与用户无关,所以不涉及用户登录与授权,不需要Resource Owner的参与。但我们不想任何人都可以调用这个WebAPI,所以要对客户端进行验证,而使用OAuth中的 Client Credentials Grant 授权方式可以很好地解决这个问题。

具体实现方式如下:

1)用Visual Studio 2013/2015创建一个Web API项目,VS会生成一堆OAuth相关代码。

2)打开Startup.Auth.cs ,精简一下代码,我们只需要实现以Client Credentials Grant授权方式拿到token,其它无关代码全部清除,最终剩下如下代码:

复制代码
public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        var OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/token"),
            Provider = new CNBlogsAuthorizationServerProvider(),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            AllowInsecureHttp = true
        };

        app.UseOAuthBearerTokens(OAuthOptions);
    }
}
复制代码

3)创建一个新的类 CNBlogsAuthorizationServerProvider,并继承自 OAuthAuthorizationServerProvider,重载 OAuthAuthorizationServerProvider() 与 GrantClientCredentials() 这两个方法。代码如下:

复制代码
public class CNBlogsAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        string clientId;
        string clientSecret;
        context.TryGetFormCredentials(out clientId, out clientSecret);

        if (clientId == "1234" && clientSecret == "5678")
        {
            context.Validated(clientId);
        }

        return base.ValidateClientAuthentication(context);
    }

    public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
    {
        var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
        oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, "iOS App"));
        var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
        context.Validated(ticket);

        return base.GrantClientCredentials(context);
    }
}
复制代码

在 ValidateClientAuthentication() 方法中获取客户端的 client_id 与 client_secret 进行验证。

在 GrantClientCredentials() 方法中对客户端进行授权,授了权就能发 access token 。

这样,OAuth的服务端代码就完成了。这么简单?是的,就这么简单,因为有了Microsoft.Owin.Security.OAuth。

4)然后写客户端调用代码测试一下:

复制代码
public class OAuthClientTest
{
    private HttpClient _httpClient;

    public OAuthClientTest()
    {
        _httpClient = new HttpClient();
        _httpClient.BaseAddress = new Uri("http://openapi.cnblogs.com");
    }

    [Fact]
    public void Get_Accesss_Token_By_Client_Credentials_Grant()
    {
        var parameters = new Dictionary<string, string>();
        parameters.Add("client_id", "1234");
        parameters.Add("client_secret", "5678");
        parameters.Add("grant_type", "client_credentials");

        Console.WriteLine(_httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters))
            .Result.Content.ReadAsStringAsync().Result);
    }
}
复制代码

运行结果如下:

{"access_token":"8PqaWilv_SJT7vRXambP7Mebyaf3KO1GXYHsqA-oPMOQF6xk1YpluczOZGo-WwATU5YmGb0wSR0cUQMC8RSZfwO8nwom7yG11FIANhy2PNiqTg2CYdJF0sf0ggFs6it_i3mc_m1iEFCK2dLBPDJXPI24wngCPR0wP_zugZvyKv314BM0PQmnnwg3kLXR1DISKRbs5-i59VCtFSZgkM7A0w","token_type":"bearer","expires_in":1209599}

搞定!

【更新】

建议使用Basic Authentication传递clientId与clientSecret,服务端CNBlogsAuthorizationServerProvider中的TryGetFormCredentials()改为TryGetBasicCredentials(),客户端的调用代码如下:

复制代码
public class OAuthClientTest
{
    private HttpClient _httpClient;

    public OAuthClientTest()
    {
        _httpClient = new HttpClient();
        _httpClient.BaseAddress = new Uri("http://openapi.cnblogs.com");
    }

    [Fact]
    public void Get_Accesss_Token_By_Client_Credentials_Grant()
    {
        var clientId =  "1234";
        var clientSecret = "5678";        
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                "Basic",
                Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret)));
                
        var parameters = new Dictionary<string, string>();        
        parameters.Add("grant_type", "client_credentials");       

        Console.WriteLine(_httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters))
            .Result.Content.ReadAsStringAsync().Result);
    }
}
复制代码

【参考资料】

ASP.Net MVC: Creating an OAuth client credentials grant type token endpoint

【MongoDB】Element '_id' does not match any field or property of class MongoDBDemo.Student._GreAmbWang的博客-CSDN博客

mikel阅读(770)

来源: 【MongoDB】Element ‘_id’ does not match any field or property of class MongoDBDemo.Student._GreAmbWang的博客-CSDN博客

问题
在查询数据时出现异常

var res = collection.Find(Builders<Student>.Filter.Eq(“Name”, “哈哈”)).ToList();
异常

 

分析
因为在MongoDB数据中,有_id字段,而Student类中没有_id字段

 

解决方法
在类添加_id属性

public ObjectId _id { get; set; }

在类添加BsonIgnoreExtraElements特性

[MongoDB.Bson.Serialization.Attributes.BsonIgnoreExtraElements]
public class Student

————————————————
版权声明:本文为CSDN博主「GreAmbWang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_38211198/article/details/100716198

未能加载文件或程序集“log4net, Version=2.0.8.0_伊人回眸泪倾城的博客-CSDN博客

mikel阅读(586)

来源: 未能加载文件或程序集“log4net, Version=2.0.8.0_伊人回眸泪倾城的博客-CSDN博客

异常信息
System.IO.FileLoadException
HResult=0x80131040
Message=未能加载文件或程序集“log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)

发生异常原因
A项目 引用用了 B项目,结果A 项目引用了低版本的 log4net

B项目应用了高版本的
2.0.8.0,

编译A项目时候, 会提示如上错误;

解决方案
调整B项目引用的log4net 版本改为和 A项目 引用的同一个版本 1.2.10.0 即可;就是 B 项目引用log4net 的路径有问题;
————————————————
版权声明:本文为CSDN博主「静心物语313」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xcl13014673050/article/details/89317225