ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?

在上一章中ASP.NET Core 认证与授权[5]:初识授权,详细介绍了 ASP.NET Core 中的授权策略,在需要授权时,只需要在对应的Controler或者Action上面打上[Authorize]特性,并指定要执行的策略名称即可,但是,授权策略是怎么执行的呢?怀着一颗好奇的心,忍不住来探索一下它的执行流程。

在《(上一章》中提到,AuthorizeAttribute只是一个简单的实现了IAuthorizeData接口的特性,并且在 ASP.NET Core 授权系统中并没有使用到它。我们知道在认证中,还有一个UseAuthentication扩展方法来激活认证系统,但是在授权中并没有类似的机制。

这是因为当我们使用[Authorize]通常是在MVC中,由MVC来负责激活授权系统。本来在这个系列的文章中,我并不想涉及到MVC的知识,但是为了能更好的理解授权系统的执行,就来简单介绍一下MVC中与授权相关的知识。

MVC中的授权

当我们使用MVC时,首先会调用MVC的AddMvc扩展方法,用来注册一些MVC相关的服务:

public static IMvcBuilder AddMvc(this IServiceCollection services){    var builder = services.AddMvcCore();builder.AddAuthorization();...
}public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder){AddAuthorizationServices(builder.Services);    return builder;
}internal static void AddAuthorizationServices(IServiceCollection services){services.AddAuthenticationCore();services.AddAuthorization();services.AddAuthorizationPolicyEvaluator();services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, AuthorizationApplicationModelProvider>());
}

在上面AddAuthorizationServices中的前三个方法都属于 ASP.NET Core 《Security》项目中提供的扩展方法,其中前两个在前面几章已经介绍过了,对于AddAuthorizationPolicyEvaluator放到后面再来介绍,我们先来看一下MVC中的AuthorizationApplicationModelProvider

AuthorizationApplicationModelProvider

在MVC中有一个ApplicationModel的概念,它用来封装ControllerFilterApiExplorer等。对应的,在MVC中还提供了一系列的ApplicationModelProvider来初始化ApplicationModel的各个部分,而AuthorizationApplicationModelProvider就是用来初始化与授权相关的部分。

public class AuthorizationApplicationModelProvider : IApplicationModelProvider{   
 public void OnProvidersExecuting(ApplicationModelProviderContext context)    {        foreach (var controllerModel in context.Result.Controllers){            var controllerModelAuthData = controllerModel.Attributes.OfType<IAuthorizeData>().ToArray();            if (controllerModelAuthData.Length > 0){controllerModel.Filters.Add(GetFilter(_policyProvider, controllerModelAuthData));}            foreach (var attribute in controllerModel.Attributes.OfType<IAllowAnonymous>()){controllerModel.Filters.Add(new AllowAnonymousFilter());}            foreach (var actionModel in controllerModel.Actions){                var actionModelAuthData = actionModel.Attributes.OfType<IAuthorizeData>().ToArray();                if (actionModelAuthData.Length > 0){actionModel.Filters.Add(GetFilter(_policyProvider, actionModelAuthData));}                foreach (var attribute in actionModel.Attributes.OfType<IAllowAnonymous>()){actionModel.Filters.Add(new AllowAnonymousFilter());}}}} }

如上,首先查找每个Controller中实现了IAuthorizeData接口的特性,然后将其转化为AuthorizeFilter并添加到Controller的Filter集合中,紧接着再查找实现了IAllowAnonymous接口的特性,将其转化为AllowAnonymousFilter过滤器也添加到Filter集合中,然后以同样的逻辑查找Action上的特性并添加到Action的Filter集合中。

其中的关键点就是将IAuthorizeData(也就是通过我们熟悉的[Authorize]特性)转化为MVC中的AuthorizeFilter过滤器:

public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authData){    if (policyProvider.GetType() == typeof(DefaultAuthorizationPolicyProvider)){        var policy = AuthorizationPolicy.CombineAsync(policyProvider, authData).GetAwaiter().GetResult();        return new AuthorizeFilter(policy);}    else{        return new AuthorizeFilter(policyProvider, authData);}
}

CombineAsync在上一章的《AuthorizationPolicy》中已经介绍过了,我们往下看看AuthorizeFilter的实现。

AuthorizeFilter

在MVC中有一个AuthorizeFilter过滤器,类似我们在ASP.NET 4.x中所熟悉的[Authorize],它实现了IAsyncAuthorizationFilter接口,定义如下:

public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory{   
 public AuthorizeFilter(AuthorizationPolicy policy) {}  
   public AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) : this(authorizeData) {}    public AuthorizeFilter(IEnumerable<IAuthorizeData> authorizeData) {}    public IEnumerable<IAuthorizeData> AuthorizeData { get; }  
     public AuthorizationPolicy Policy { get; }  
     
       public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context)    {    
          var effectivePolicy = Policy;      
           if (effectivePolicy == null){effectivePolicy = await AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData);}      
            var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();        var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext);        if (context.Filters.Any(item => item is IAllowAnonymousFilter)){            return;}        
            var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context);... // 如果授权失败,返回ChallengeResult或ForbidResult} }

AuthorizeFilter的OnAuthorizationAsync方法会在Action执行之前触发,其调用IPolicyEvaluator来完成授权,将执行流程切回到 ASP.NET Core 授权系统中。关于MVC中IApplicationModelProvider以及Filter的概念,在以后MVC系列的文章中再来详细介绍,下面就继续介绍 ASP.NET Core 的授权系统,也就是《Security》项目。

IPolicyEvaluator

IPolicyEvaluator是MVC调用授权系统的入口点,其定义如下:

public interface IPolicyEvaluator{    Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context);    Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource);
}

在上面介绍的AddMVC中,调用了AddAuthorizationPolicyEvaluator扩展方法,它有如下定义:

public static class PolicyServiceCollectionExtensions{   

 public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services)    {services.TryAdd(ServiceDescriptor.Transient<IPolicyEvaluator, PolicyEvaluator>());        return services;} }

由此可知IPolicyEvaluator的默认实现为PolicyEvaluator,我们就从它入手,来一步一步解剖 ASP.NET Core 授权系统的执行步骤。

AuthorizeFilter中,依次调到了AuthenticateAsyncAuthorizeAsync方法,我们就一一来看。

AuthenticateAsync(AuthenticationSchemes)

为什么还有一个AuthenticateAsync方法呢,这不是在认证阶段执行的吗?我们看下它的实现:

public class PolicyEvaluator : IPolicyEvaluator{   

 public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)    {        if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0){ClaimsPrincipal newPrincipal = null;        
   foreach (var scheme in policy.AuthenticationSchemes){                var result = await context.AuthenticateAsync(scheme);                if (result != null && result.Succeeded){newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal);}}            if (newPrincipal != null){context.User = newPrincipal;          
     return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));}            else{context.User = new ClaimsPrincipal(new ClaimsIdentity());            
   return AuthenticateResult.NoResult();}}      
    return (context.User?.Identity?.IsAuthenticated ?? false) ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")): AuthenticateResult.NoResult();} }

在《上一章》中,我们知道在AuthorizationPolicy中有AuthenticationSchemesIAuthorizationRequirement两个属性,并详细介绍介绍了Requirement,但是没有提到AuthenticationSchemes的调用。

那么,看到这里,也就大概明白了,它与Requirements的执行是完全独立的,并在它之前执行,用于重置Claims,那么为什么要重置呢?

在认证的章节介绍过,在认证阶段,只会执行默认的认证Scheme,context.User就是使用context.AuthenticateAsync(DefaultAuthenticateScheme)来赋值的,当我们希望使用非默认的Scheme,或者是想合并多个认证Scheme的Claims时,就需要使用基于Scheme的授权来重置Claims了。

它的实现也很简单,直接使用我们在授权策略中指定的Schemes来依次调用认证服务的AuthenticateAsync方法,并将生成的Claims合并,最后返回我们熟悉的AuthenticateResult认证结果。

AuthorizeAsync(Requirements)

接下来再看一下PolicyEvaluatorAuthorizeAsync方法:

public class PolicyEvaluator : IPolicyEvaluator{  

 private readonly IAuthorizationService _authorization;
    public PolicyEvaluator(IAuthorizationService authorization)    {_authorization = authorization;}  
    
      public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)    {        var result = await _authorization.AuthorizeAsync(context.User, resource, policy);      
        if (result.Succeeded) return PolicyAuthorizationResult.Success();  
           return (authenticationResult.Succeeded) ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge();} }

该方法会根据Requirements来完成授权,具体的实现是通过调用IAuthorizationService来实现的。

最终返回的是一个PolicyAuthorizationResult对象,并在授权失败时,根据认证结果来返回Forbid(未授权)Challenge(未登录)

public class PolicyAuthorizationResult{   
 private PolicyAuthorizationResult() { }  
  public bool Challenged { get; private set; }  
    public bool Forbidden { get; private set; }  
      public bool Succeeded { get; private set; } }

IAuthorizationService

然后就到了授权的核心对象AuthorizationService,也可以称为授权的外交官,我们也可以直接在应用代码中调用该对象来实现授权,它有如下定义:

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

AuthorizeAsync中还涉及到一个resource对象,用来实现面向资源的授权,放在下一章中再来介绍,而在本章与《前一章》的示例中,该值均为null

ASP.NET Core 中还为IAuthorizationService提供了几个扩展方法:

public static class AuthorizationServiceExtensions{   
 public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, string policyName) {}  
 
  public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, AuthorizationPolicy policy) {}  
    public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, IAuthorizationRequirement requirement) {}  
     public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, AuthorizationPolicy policy) {} }

其默认实现为DefaultAuthorizationService:

public class DefaultAuthorizationService : IAuthorizationService{    private readonly AuthorizationOptions _options;    private readonly IAuthorizationHandlerContextFactory _contextFactory;    private readonly IAuthorizationHandlerProvider _handlers;    private readonly IAuthorizationEvaluator _evaluator;    private readonly IAuthorizationPolicyProvider _policyProvider;    public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)    {        var policy = await _policyProvider.GetPolicyAsync(policyName);        return await this.AuthorizeAsync(user, resource, policy);}    public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)    {        var authContext = _contextFactory.CreateContext(requirements, user, resource);        var handlers = await _handlers.GetHandlersAsync(authContext);        foreach (var handler in handlers){            await handler.HandleAsync(authContext);            if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed){                break;}}        return _evaluator.Evaluate(authContext);}
}

通过上面代码可以看出,在《上一章》中介绍的授权策略,在这里获取到它的Requirements,后续便不再需要了。而在AuthorizationService中是通过调用四大核心对象来完成授权,我们一一来看。

IAuthorizationPolicyProvider

由于在[Authorize]中,我们指定的是策略的名称,因此需要使用IAuthorizationPolicyProvider来根据名称获取到策略对象,默认实现为DefaultAuthorizationPolicyProvider

public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider{   
 private readonly AuthorizationOptions _options;  
 
  public Task<AuthorizationPolicy> GetDefaultPolicyAsync()    {        return Task.FromResult(_options.DefaultPolicy);}  
  
    public virtual Task<AuthorizationPolicy> GetPolicyAsync(string policyName)    {        return Task.FromResult(_options.GetPolicy(policyName));} }

在上一章中介绍过,我们定义的策略都保存在《AuthorizationOptions》的字典中,因此在这里只是简单的将AuthorizationOptions中的同名方法异步化。

IAuthorizationHandlerContextFactory

授权上下文是我们接触较多的对象,当我们自定义授权Handler时就会用到它,它是使用简单工厂模式来创建的:

public class DefaultAuthorizationHandlerContextFactory : IAuthorizationHandlerContextFactory{   

 public virtual AuthorizationHandlerContext CreateContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource)    {    
    return new AuthorizationHandlerContext(requirements, user, resource);} }

授权上下文中主要包含用户的Claims和授权策略的Requirements

public class AuthorizationHandlerContext{  

 private HashSet<IAuthorizationRequirement> _pendingRequirements;    private bool _failCalled;  
 
  private bool _succeedCalled;  
   public AuthorizationHandlerContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource)    {Requirements = requirements; User = user; Resource = resource;_pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements);}  
     public virtual bool HasFailed { get { return _failCalled; } }  
     
      public virtual bool HasSucceeded => !_failCalled && _succeedCalled && !_pendingRequirements.Any();  
      
      public virtual void Fail()    {_failCalled = true;}  
       public virtual void Succeed(IAuthorizationRequirement requirement)    {_succeedCalled = true;_pendingRequirements.Remove(requirement);} }

如上,_pendingRequirements中保存着所有待验证的Requirements,验证成功的Requirement则从中移除。

IAuthorizationHandlerProvider

兜兜转转,终于进入到了授权的最终验证逻辑中了,首先,使用IAuthorizationHandlerProvider来获取到所有的授权Handler

IAuthorizationHandlerProvider的默认实现为DefaultAuthorizationHandlerProvider:

public class DefaultAuthorizationHandlerProvider : IAuthorizationHandlerProvider{   
 private readonly IEnumerable<IAuthorizationHandler> _handlers;    public DefaultAuthorizationHandlerProvider(IEnumerable<IAuthorizationHandler> handlers)    {_handlers = handlers;}    
 
 public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context)=> Task.FromResult(_handlers); }

在《上一章》中,我们还介绍到,我们定义的Requirement,可以直接实现IAuthorizationHandler接口,也可以单独定义Handler,但是需要注册到DI系统中去。

在默认的AuthorizationHandlerProvider中,会从DI系统中获取到我们注册的所有Handler,最终调用其HandleAsync方法。

我们在实现IAuthorizationHandler接口时,通常是继承自AuthorizationHandler<TRequirement>来实现,它有如下定义:

public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler where TRequirement : IAuthorizationRequirement{    public virtual async Task HandleAsync(AuthorizationHandlerContext context)    {      
 foreach (var req in context.Requirements.OfType<TRequirement>()){          
   await HandleRequirementAsync(context, req);}}    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement); }

如上,首先会在HandleAsync过滤出与Requirement对匹配的Handler,然后再调用其HandleRequirementAsync方法。

那我们定义的直接实现IAuthorizationHandler了接口的Requirement又是如何执行的呢?

AddAuthorization扩展方法中可以看到,默认还为IAuthorizationHandler注册了一个PassThroughAuthorizationHandler,定义如下:

public class PassThroughAuthorizationHandler : IAuthorizationHandler{    public async Task HandleAsync(AuthorizationHandlerContext context)    {        foreach (var handler in context.Requirements.OfType<IAuthorizationHandler>()){            await handler.HandleAsync(context);}}
}

它负责调用该策略中所有实现了IAuthorizationHandler接口的Requirement

IAuthorizationEvaluator

最后,通过调用IAuthorizationEvaluator接口,来完成最终的授权结果,默认实现为DefaultAuthorizationEvaluator:

public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator{    public AuthorizationResult Evaluate(AuthorizationHandlerContext context)        => context.HasSucceeded? AuthorizationResult.Success(): AuthorizationResult.Failed(context.HasFailed? AuthorizationFailure.ExplicitFail(): AuthorizationFailure.Failed(context.PendingRequirements));
}

当我们在一个策略中指定多个Requirement时,只有全部验证通过时,授权上下文中的HasSucceeded才会为True,而HasFailed代表授权结果的显式失败。

这里根据授权上下文的验证结果来生成授权结果:

public class AuthorizationResult{  
 public bool Succeeded { get; private set; }  
  public AuthorizationFailure Failure { get; private set; }  
    public static AuthorizationResult Success() => new AuthorizationResult { Succeeded = true };  
      public static AuthorizationResult Failed(AuthorizationFailure failure) => new AuthorizationResult { Failure = failure };
         public static AuthorizationResult Failed() => new AuthorizationResult { Failure = AuthorizationFailure.ExplicitFail() }; }public class AuthorizationFailure{
            private AuthorizationFailure() { }    public bool FailCalled { get; private set; }    public IEnumerable<IAuthorizationRequirement> FailedRequirements { get; private set; }    public static AuthorizationFailure ExplicitFail()    {        return new AuthorizationFailure { FailCalled = true, FailedRequirements = new IAuthorizationRequirement[0] };    }    public static AuthorizationFailure Failed(IEnumerable<IAuthorizationRequirement> failed)        => new AuthorizationFailure { FailedRequirements = failed }; }

整个授权流程的结构大致如下:

总结

通过对 ASP.NET Core 授权系统执行流程的探索,可以看出授权是主要是通过调用IAuthorizationService来完成的,而授权策略的本质是提供 Requirement ,我们完全可以使用它们两个来完成各种灵活的授权方式,而不用局限于策略。在 ASP.NET Core 中,还提供了基于资源的授权,放在下一章中来介绍,并会简单演示一下在一个通用权限管理系统中如何来授权。

相关文章:

  • ASP.NET Core 认证与授权[4]:JwtBearer认证

  • ASP.NET Core 认证与授权[2]:Cookie认证

  • ASP.NET Core 认证与授权[3]:OAuth & OpenID Connect认证

  • Asp.Net Core 2.0 多角色权限认证

  • asp.net core 2.0 web api基于JWT自定义策略授权

  • ASP.NET Core 认证与授权[5]:初识授权


原文:http://www.cnblogs.com/RainingNight/p/authorize-how-to-work-in-asp-net-core.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322818.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jzoj4248-n染色【数学,快速幂】

正题 题目大意 一个环&#xff0c;nnn个部分mmm个颜色求每段连续的都不同颜色的方案数。 解题思路 打表找一下规律发现 fifi−1∗(m−1)fi−2∗(m−1)f_if_{i-1}*(m-1)f_{i-2}*(m-1)fi​fi−1​∗(m−1)fi−2​∗(m−1) 然后特征根:fi(m−1)n(m−1)∗(−1)nf_i(m-1)^n(m-1)*…

SpringBoot2.1.9 多Redis Jedis配置

一、配置文件 pom.xml <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency><groupId>redis.clients</groupId><artifa…

.net core2.0下使用Identity改用dapper存储数据

前言、 已经好多天没写博客了&#xff0c;鉴于空闲无聊之时又兴起想写写博客&#xff0c;也当是给自己做个笔记。过了这么些天&#xff0c;我的文笔还是依然那么烂就请多多谅解了。今天主要是分享一下在使用.net core2.0下的实际遇到的情况。在使用webapi时用了identity做用户验…

如何解决vuepress部署出现样式问题

以前在安装hexo的时候出了样式问题&#xff0c;现在用vuepress也出现了相同的问题。 本地测试完全可以 然而打包之后就彻底乱了 即使是自己本地打包成dist之后也会出现相同的问题 有点困扰&#xff0c;应该是打包配置的问题 通过修改index.html里的内容。将/改为./ 发现部…

jzoj4249-游戏【贪心】

正题 题目大意 0∼n0\sim n0∼n的点&#xff0c;从iii移动到jjj获得aj∗(j−i)a_j*(j-i)aj​∗(j−i)的价值。求最大价值。 解题思路 考虑贪心&#xff0c;每次移动到往后aia_iai​最大的点。 证明: 反证明:我们假设有一种情况i<ji<ji<j且aj<aia_j<a_iaj​<…

想使用Docker容器?先看看这些注意事项

Docker容器无疑是最近十年来最引人注目的技术之一&#xff0c;因为有了它&#xff0c;对我们思考设计、开发和运维软件的方式产生了非常有益的影响。 但是就像每一个开发工具一样&#xff0c;为了充分利用这些工具&#xff0c;需要注意一些使用中问题&#xff0c;Docker容器也是…

SpringBoot2.1.9 多数据源JDBC配置

一、配置文件 pom.xm <dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.10.RELEASE</version> </dependency> <dependency><groupId>mysql</groupId>…

Vs Code如何自定义设置一键代码补全

有时候是不是看到别人输入一个vue就能打印整个代码&#xff0c;感觉很神奇&#xff0c;本文就以vue为案例教你如何使用这种骚操作&#xff01;&#xff01;&#xff01; 点击文件->首选项->用户代码片段 输入vue.json&#xff08;如果没有则新建代码片段&#xff09; &…

欢乐纪中某B组赛【2019.1.30】The penultimate day

前言 TodayisthepenultimatedayToday\ is\ the penultimate dayToday is thepenultimateday 成绩 RankRankRank是有算别人的 RankRankRankPersonPersonPersonScoreScoreScoreAAABBBCCC3332017zyc2017zyc2017zyc2502502509090901001001006060601010102017xxy2017xxy2017xxy22022…

Realm发布Realm .NET,扩展支持.NET技术栈

继去年夏天Realm引入对.NET Core的支持&#xff0c;使开发者可以用C#来构建移动应用之后&#xff0c;Realm发布了Realm .NET。Realm .NET是一个可以让开发者更好地集成他们的.NET技术栈的新组件集。 其中一个组件&#xff0c;是针对.NET的Global Notifier&#xff0c;可以实现大…

SpringBoot2.1.9 多数据源Mybatis—JDBC配置

一、配置文件 pom.xm <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency><groupId>mysql</groupId><artifactId>mysql-c…

居中对齐

自绝父相 现在有一个父盒子和一个子盒子 要想让子盒子相对父盒子居中很简单&#xff0c;在子盒子上加上属性即可 margin: 0 auto;如果说要变成水平垂直居中呢&#xff0c;加上两个auto?结果肯定是不行的 得这样 用到所谓得子绝父相 首先父亲要相对定位 然后儿子设置成这样 l…

P3128-最大流Max Flow【树上差分,LCA】

正题 题目大意 一棵树 若干条路径&#xff0c;哪个点经过的路径最多&#xff0c;求路径条数。 解题思路 对于每条路径计算一次LCALCALCA&#xff0c;然后树上差分就好了。 codecodecode #include<cstdio> #include<queue> #include<cmath> using namespac…

IdentityServer4(10)- 添加对外部认证的支持之QQ登录

前言 前面我们提到过IdentityServer4是可以添加外部认证的&#xff0c;如果外部认证支持OAuth2&#xff0c;那么添加到IdentityServer4是非常简单的&#xff0c;在ASP.NET Core下提供了非常多的外部认证实现&#xff0c;比如Google&#xff0c;Facebook&#xff0c;Twitter&…

css字体样式

以下写几个常用的字体样式&#xff0c;方便以后使用&#xff1a; font-family: FangSong;font-weight: bold;font-size: 20px;font-style: italic;text-decoration: underline;letter-spacing: 1em;line-height: 2em;简写&#xff1a;font:bold 20px FangSong样式作用font-fami…

springboot 多数据源mybatis的两种整合方法

转载自 springboot-mybatis多数据源的两种整合方法 简介&#xff1a; 随着并发量的不断增加&#xff0c;显然单个数据库已经承受不了高并发带来的压力。一个项目使用多个数据库&#xff08;无论是主从复制- - 读写分离还是分布式数据库结构&#xff09;的重要性变得越来越明显…

P1373-小a和uim之大逃离【dp】

正题 题目大意 n∗mn*mn∗m的矩阵有不同的权值&#xff0c;每次只可以往下走或往右走。要求走奇数次&#xff0c;要求偶数次经过的点和奇数次经过的点同余KKK 解题思路 设fi,j,k,0/1f_{i,j,k,0/1}fi,j,k,0/1​表示在第iii行jjj列&#xff0c;两个数只差为kkk&#xff0c;是奇数…

项目实战+感慨

已经好久没写博客了&#xff0c;自从接触了项目以后&#xff0c;发现很难再挤出时间来写博客&#xff0c;大部分时间都是在关注项目的进展&#xff0c;以及查阅相关的资料。我想这也是很多程序员入职以后就很少写博客的原因。 前言 最近一段时间我接触了很多前端的东西&#x…

解决mybatis generator无法覆盖XML

转载自 解决mybatis generator无法覆盖XML 今天发现mybatis generator maven plugin在重复生成的时候xml文件只会merge&#xff0c;不会覆盖。 明明在pom.xml中配置了如下&#xff1a; <configuration><configurationFile>src/main/resources/mybatis/generato…

laravel构造器的CURD

这里主要记一下关于用laravel写相关接口的一些知识点&#xff0c;方便以后查阅&#xff0c;基本都是Controller里面的一些操作 查询数据&#xff1a; // 查询数据$query DB::table(think_data)->where([ [status,>,500]])->get();插入数据&#xff1a; // 新增单条数…