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

经过前面几章的姗姗学步,我们了解了在 ASP.NET Core 中是如何认证的,终于来到了授权阶段。在认证阶段我们通过用户令牌获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥有Admin的角色,姓名是否叫XXX等等。本章就来介绍一下 ASP.NET Core 的授权系统的简单使用。

简单授权

在ASP.NET 4.x中,我们通常使用Authorize过滤器来进行授权,它可以作用在Controller和Action上面,也可以添加到全局过滤器中。而在ASP.NET Core中也有一个Authorize特性(但不是过滤器),用法类似:

[Authorize] // Controller级别
public class SampleDataController : Controller{[Authorize] // Action级别public IActionResult SampleAction()    {} }

IAllowAnonymous

在ASP.NET 4.x中,我们最常用的另一个特性便是AllowAnonymous,用来设置某个Controller或者Action跳过授权,它在 ASP.NET Core 中同样适用:

[Authorize]
public class AccountController : Controller{[AllowAnonymous]    public ActionResult Login()    {}    public ActionResult Logout()    {} }

如上,LoginAction便不再需要授权,同样,在 ASP.NET Core 中提供了一个统一的IAllowAnonymous接口,在授权逻辑中都是通过该接口来判断是否跳过授权验证的。

public interface IAllowAnonymous{
}[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]public class AllowAnonymousAttribute : Attribute, IAllowAnonymous{
}

IAuthorizeData

上面提到,在 ASP.NET Core 中,AuthorizeAttribute不再是一个MVC中的Filter了,而只是一个简单的实现了IAuthorizeData接口的Attribute

public interface IAuthorizeData{   
 string Policy { get; set; }    
 Roles { get; set; }  
 string AuthenticationSchemes { get; set; } }[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]public class AuthorizeAttribute : Attribute, IAuthorizeData{    public AuthorizeAttribute() { }    public AuthorizeAttribute(string policy)    {Policy = policy;}  
   public string Policy { get; set; }    
   public string Roles { get; set; }  
   public string AuthenticationSchemes { get; set; } }

记得第一次在ASP.NET Core中实现自定义授权时,按照以前的经验,直接继承自AuthorizeAttribute,然后准备重写OnAuthorization方法,结果懵逼了。然后在MVC的源码中,苦苦搜寻AuthorizeAttribute的踪迹,却毫无所获,后来才注意到它实现了IAuthorizeData接口,该接口才是认证的源头,而Authorize特性只是认证信息的载体,并不包含任何逻辑。IAuthorizeData中定义的Policy, Roles, AuthenticationSchemes三个属性分别代表着 ASP.NET Core 授权系统中的三种授权方式。

基于角色的授权

基于角色的授权,我们都比较熟悉,使用方式如下:

[Authorize(Roles = "Admin")] // 多个Role可以使用,分割

public class SampleDataController : Controller{... }

基于角色的授权的逻辑与ASP.NET 4.x类似,都是使用我在《初识认证》中介绍的IsInRole方法来实现的。

基于Scheme的授权

对于AuthenticationScheme我在前面几章也都介绍过,比如Cookie认证默认使用的AuthenticationScheme就是Cookies,在JwtBearer认证中,默认的Scheme就是Bearer

当初在学习认证时,还在疑惑,如何在使用Cookie认证的同时又支持Bearer认证呢?因为在认证中只能设置一个Scheme来执行,当看到这里豁然开朗,后面会详细介绍。

[Authorize(AuthenticationSchemes = "Cookies")] // 多个Scheme可以使用,分割

public class SampleDataController : Controller{... }

当我们的应用程序中,同时使用了多种认证Scheme时,AuthenticationScheme授权就非常有用,在该授权模式下,会通过context.AuthenticateAsync(scheme)重新获取Claims。

基于策略的授权

在ASP.NET Core中,重新设计了一种更加灵活的授权方式:基于策略的授权,也是授权的核心。

在使用基于策略的授权时,首先要定义授权策略,而授权策略本质上就是对Claims的一系列断言。

public void ConfigureServices(IServiceCollection services){services.AddMvc();services.AddAuthorization(options =>{options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));});
}

如上,我们定义了一个名称为EmployeeOnly的授权策略,它要求用户的Claims中必须包含类型为EmployeeNumber的Claim。

其实,基于角色的授权和基于Scheme的授权,只是一种语法上的便捷,最终都会生成授权策略,后文会详解介绍。

然后便可以在Authorize特性中通过Policy属性来指定授权策略:

[Authorize(Policy = "EmployeeOnly")]public class SampleDataController : Controller{}

授权策略详解

AddAuthorization

授权策略的定义使用了AddAuthorization扩展方法,我们来看看它的源码:

public static class AuthorizationServiceCollectionExtensions{   
 public static IServiceCollection AddAuthorization(this IServiceCollection services)    {        services.TryAdd(ServiceDescriptor.Transient<IAuthorizationService, DefaultAuthorizationService>());services.TryAdd(ServiceDescriptor.Transient<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>());services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>());services.TryAdd(ServiceDescriptor.Transient<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>());services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>());services.TryAddEnumerable(ServiceDescriptor.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>());        return services;}    public static IServiceCollection AddAuthorization(this IServiceCollection services, Action<AuthorizationOptions> configure)    {services.Configure(configure);      
   return services.AddAuthorization();} }

首先,是对授权进行配置的AuthorizationOptions,然后在DI系统中注册了几个核心对象的默认实现,我们一一来看。

AuthorizationOptions

对于Options模式,大家应该都比较熟悉了,AuthorizationOptions是添加和获取授权策略的入口点:

public class AuthorizationOptions{    
private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);    // 在上一个策略验证失败后,是否继续执行下一个授权策略public bool InvokeHandlersAfterFailure { get; set; } = true;
   public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();    public void AddPolicy(string name, AuthorizationPolicy policy)    {PolicyMap[name] = policy;}    
   
   public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)    {        var policyBuilder = new AuthorizationPolicyBuilder();configurePolicy(policyBuilder);AddPolicy(name,policyBuilder.Build());}    public AuthorizationPolicy GetPolicy(string name)    {    
      return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;} }

首先是一个PolicyMap字典,我们定义的策略都保存在其中,AddPolicy方法只是简单的将策略添加到该字典中,而其DefaultPolicy属性表示默认策略,初始值为:“已认证用户”。

AuthorizationOptions中主要涉及到AuthorizationPolicyAuthorizationPolicyBuilder两个对象。

AuthorizationPolicy

在 ASP.NET Core 中,授权策略具体表现为一个AuthorizationPolicy对象:

public class AuthorizationPolicy{  

 public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> authenticationSchemes) {}    public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }    public IReadOnlyList<string> AuthenticationSchemes { get; }    public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies) {        return Combine((IEnumerable<AuthorizationPolicy>)policies);}  
 
  public static AuthorizationPolicy Combine(IEnumerable<AuthorizationPolicy> policies) {        foreach (var policy in policies){builder.Combine(policy);}        return builder.Build();}  
  
   public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) {        foreach (var authorizeDatum in authorizeData){any = true;            var useDefaultPolicy = true;            if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy)){policyBuilder.Combine(await policyProvider.GetPolicyAsync(authorizeDatum.Policy));useDefaultPolicy = false;}            var rolesSplit = authorizeDatum.Roles?.Split(',');            if (rolesSplit != null && rolesSplit.Any()){policyBuilder.RequireRole(rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim()));useDefaultPolicy = false;}            var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');            if (authTypesSplit != null && authTypesSplit.Any()){                foreach (var authType in authTypesSplit){                    if (!string.IsNullOrWhiteSpace(authType)){policyBuilder.AuthenticationSchemes.Add(authType.Trim());}}}      
        if (useDefaultPolicy){policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());}}      
      return any ? policyBuilder.Build() : null;} }

如上,Combine方法通过调用AuthorizationPolicyBuilder来完成授权策略的合并,而CombineAsync则是将我们上面介绍的IAuthorizeData转换为授权策略,因此上面说基于角色/Scheme的授权本质上都是基于策略的授权。

对于AuthenticationSchemes属性,我们在前几章介绍认证时经常看到,用来表示我们使用哪个认证Scheme来获取用户的Claims,如果指定多个,则会合并它们的Claims,其实现下一章再来介绍。

Requirements属性则是策略的核心了,每一个Requirement都代表一个授权条件,我们就先来了解一下它。

IAuthorizationRequirement

Requirement使用IAuthorizationRequirement接口来表示:

public interface IAuthorizationRequirement{
}

IAuthorizationRequirement接口中并没有任何成员,在 ASP.NET Core 中内置了一些常用的实现:

  • AssertionRequirement :使用最原始的断言形式来声明授权策略。

  • DenyAnonymousAuthorizationRequirement :用于表示禁止匿名用户访问的授权策略,并在AuthorizationOptions中将其设置为默认策略。

  • ClaimsAuthorizationRequirement :用于表示判断Cliams中是否包含预期的Claims的授权策略。

  • RolesAuthorizationRequirement :用于表示使用ClaimsPrincipal.IsInRole来判断是否包含预期的Role的授权策略。

  • NameAuthorizationRequirement:用于表示使用ClaimsPrincipal.Identities.Name来判断是否包含预期的Name的授权策略。

  • OperationAuthorizationRequirement:用于表示基于操作的授权策略。

其逻辑也都非常简单,我就不再一一介绍,只展示一下RolesAuthorizationRequirement的代码片段:

public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement{    public IEnumerable<string> AllowedRoles { get; }    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)    {...        if (requirement.AllowedRoles.Any(r => context.User.IsInRole(r))){context.Succeed(requirement);}        return Task.CompletedTask;}
}

AllowedRoles表示允许授权通过的角色,而它还实现了IAuthorizationHandler接口,用来完成授权的逻辑。

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

AuthorizationRequirement并不是一定要实现IAuthorizationHandler接口,后文会详细介绍。

AuthorizationPolicyBuilder

在上面已经多次用到AuthorizationPolicyBuilder,它提供了一系列创建AuthorizationPolicy的快捷方法:

public class AuthorizationPolicyBuilder{  
 public AuthorizationPolicyBuilder(params string[] authenticationSchemes);  
 
   public AuthorizationPolicyBuilder(AuthorizationPolicy policy);  
   
     public IList<IAuthorizationRequirement> Requirements { get; set; }
    public IList<string> AuthenticationSchemes { get; set; }  
    public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes);    public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequirement[] requirements);    public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, bool> handler);    public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, Task<bool>> handler)    {Requirements.Add(new AssertionRequirement(handler));      
      return this;}  
   public AuthorizationPolicyBuilder RequireAuthenticatedUser()    {Requirements.Add(new DenyAnonymousAuthorizationRequirement());  
            return this;}  
   public AuthorizationPolicyBuilder RequireClaim(string claimType);  
     public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] requiredValues);    public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable<string> requiredValues)    {Requirements.Add(new ClaimsAuthorizationRequirement(claimType, requiredValues));        return this;}  
 
   public AuthorizationPolicyBuilder RequireRole(params string[] roles);
        public AuthorizationPolicyBuilder RequireRole(IEnumerable<string> roles)    {Requirements.Add(new RolesAuthorizationRequirement(roles));        return this;}  

 public AuthorizationPolicyBuilder RequireUserName(string userName)    {Requirements.Add(new NameAuthorizationRequirement(userName));    
    return this;}    public AuthorizationPolicy Build();  
      public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy); }

在上面介绍的几个Requirement,除了OperationAuthorizationRequirement外,都有对应的快捷添加方法,由于OperationAuthorizationRequirement并不属于基于资源的授权,所以不在这里,其用法留在其后续章节再来介绍。

整个授权策略的内容也就这么多,并不复杂,整个结构大致如下:

基于策略的授权进阶

在上一小节,我们探索了一下授权策略的源码,现在就来实战一下。

我们使用AuthorizationPolicyBuilder可以很容易的在策略定义中组合我们需要的Requirement

public void ConfigureServices(IServiceCollection services){   
 var commonPolicy = new AuthorizationPolicyBuilder().RequireClaim("MyType").Build();services.AddAuthorization(options =>{options.AddPolicy("User", policy => policy.RequireAssertion(context => context.User.HasClaim(c => (c.Type == "EmployeeNumber" || c.Type == "Role"))));options.AddPolicy("Employee", policy => policy.RequireRole("Admin").RequireUserName("Alice").RequireClaim("EmployeeNumber").Combine(commonPolicy));}); }

如上,如果需要,我们还可以定义一个公共的策略对象,然后在策略定义中直接将其合并进来。

自定义策略

当内置的Requirement不能满足我们的需求时,我们也可以很容易的定义自己的Requirement

public class MinimumAgeRequirement : AuthorizationHandler<NameAuthorizationRequirement>, IAuthorizationRequirement{    public MinimumAgeRequirement(int minimumAge)    {MinimumAge = minimumAge;}   

 public int MinimumAge { get; private set; }  
 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NameAuthorizationRequirement requirement)    {        if (context.User != null && context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth){            var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).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;} }

然后就可以直接在AddPolicy中使用了:

services.AddAuthorization(options =>
{options.AddPolicy("Over21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

我们自定义的 Requirement 若想得到 ASP.NET Core 授权系统的执行,除了上面示例中的实现IAuthorizationHandler接口外,也可以单独定义AuthorizationHandler,这样可以更好的使用DI系统,并且还可以定义多个Handler,下面就来演示一下。

多Handler模式

授权策略中的多个Requirement,它们属于 & 的关系,只用全部验证通过,才能最终授权成功。但是在有些场景下,我们可能希望一个授权策略可以适用多种情况,比如,我们进入公司时需要出示员工卡才可以被授权进入,但是如果我们忘了带员工卡,可以去申请一个临时卡,同样可以授权成功:

public class EnterBuildingRequirement : IAuthorizationRequirement{
}

public class BadgeEntryHandler : AuthorizationHandler<EnterBuildingRequirement> {  
 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EnterBuildingRequirement requirement)    {        if (context.User.HasClaim(c => c.Type == ClaimTypes.BadgeId){context.Succeed(requirement);}        else{            // context.Fail();}        return Task.CompletedTask;} }

public
class HasTemporaryStickerHandler : AuthorizationHandler<EnterBuildingRequirement> {    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EnterBuildingRequirement requirement)    {        if (context.User.HasClaim(c => c.Type == ClaimTypes.TemporaryBadgeId){context.Succeed(requirement);}        return Task.CompletedTask;} }

如上,我们定义了两个Handler,但是想让它们得到执行,还需要将其注册到DI系统中:

services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>();
services.AddSingleton<IAuthorizationHandler, HasTemporaryStickerHandler>();

此时,在我们的应该程序中使用EnterBuildingRequirement的授权时,将会依次执行这两个Handler。而在上面介绍AuthorizationOptions时,提到它还有一个InvokeHandlersAfterFailure属性,在这里就派上用场了,只有其为true时(默认为True),才会在当前 AuthorizationHandler 授权失败时,继续执行下一个 AuthorizationHandler

在上面的示例中,我们使用context.Succeed(requirement)将授权结果设置为成功,而失败时并没有做任何标记,正常情况下都是这样做的。但是如果需要,我们可以通过调用context.Fail()方法显式的将授权结果设置为失败,那么,不管其他 AuthorizationHandler 是成功还是失败,最终结果都将是授权失败。

总结

ASP.NET Core 授权策略是一种非常强大、灵活的权限验证方案,能够满足大部分的授权场景。通过本文对授权策略的详细介绍,我想应该能够灵活的使用基于策略的授权了,但是授权策略到底是怎么执行的呢?在下一章中,就来完整的探索一下 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自定义策略授权


原文:http://www.cnblogs.com/RainingNight/p/authorization-in-asp-net-core.html


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

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

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

相关文章

uni-app打包h5

如果我们想打包成直接浏览的h5&#xff0c;我们需要配置manifest.json这个文件&#xff0c;在其中的h5配置中加入publicPath配置&#xff0c;配置如下&#xff1a; 代码为&#xff1a; "h5" : {"publicPath": "./"},配置好这个后&#xff0c;以后…

[52ABP实战系列] .NET CORE实战入门视频课程出来啦

“ .NET CORE实战入门视频&#xff0c;要是有讲的不好的地方&#xff0c;还请留言。” 早安&#xff01; 各位道友好&#xff0c;.NET CORE入门视频的第一章已经录制完毕了。视频会放在传课网、网易云课堂及segment fault。 本来想的是第一章合计6个小节就可以播放完毕的&#…

ASP.NET Core缓存静态资源

背景 缓存样式表&#xff0c;JavaScript或图像文件等静态资源可以提高您网站的性能。在客户端&#xff0c;总是从缓存中加载一个静态文件&#xff0c;这样可以减少对服务器的请求数量&#xff0c;从而减少获取页面及其资源的时间。在服务器端&#xff0c;由于它们的请求较少&am…

【程序员】保持一颗虚心好学的心态去敲代码

最近&#xff0c;我感觉是自己突破最大的一段时间&#xff0c;为什么呢&#xff1f;主要是打通了接口这一块&#xff0c;就是用postman发送各种数据&#xff0c;我都能用后端接受到相关数据&#xff0c;并且解析出来。 在这之前我尝试过spring boot 和node&#xff0c;前者是太…

跟着老桂学ASP.NET Core 2.0

.net core作为微软开发技术中跨平台的利器&#xff0c;2.0的发布已经有一段时间了&#xff0c;asp.net core是新一代微软的BS开发框架&#xff0c;同时兼容.net core和.net framework&#xff0c;它的出现&#xff0c;使基于微软体系的BS开发迎来新的契机&#xff0c;开源&…

如何安装并启动django

这里我用的是pip3&#xff0c;一般没装两个版本的用pip就行了 安装 pip3 install django如何检测 python3 -m django --version显示版本号即可 如何创建并启动项目 创建 django-admin startproject HelloWorld启动 然后cd到HelloWorld目录里 python manage.py runserver…

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

在上一章中ASP.NET Core 认证与授权[5]:初识授权&#xff0c;详细介绍了 ASP.NET Core 中的授权策略&#xff0c;在需要授权时&#xff0c;只需要在对应的Controler或者Action上面打上[Authorize]特性&#xff0c;并指定要执行的策略名称即可&#xff0c;但是&#xff0c;授权策…

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

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

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

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

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

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

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

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

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

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

居中对齐

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

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

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

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

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

项目实战+感慨

已经好久没写博客了&#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…

中间件中渲染Razor视图

前言 上一篇文章《ASP.NET Core 奇技淫巧&#xff08;1&#xff09;&#xff1a;中间件实现服务端静态化缓存》中介绍了中间件的使用方法、以及使用中间件实现服务端静态化缓存的功能。本系列文章取名“奇技淫巧”不是没道理的&#xff0c;因为这写技巧都是我最近在做的公司实际…

解决idea启动项目报错:Unable to open debugger port(127.0.0.1:60157):java.net.SocketExceptionsocket closed

转载自 解决idea启动项目报错:Unable to open debugger port(127.0.0.1:60157):java.net.SocketException"socket closed 1.问题描述: 工作当中免不了要重启服务,debug模式下偶尔启动项目,却启动失败报错: Unable to open debugger port (127.0.0.1:60157): java.net.S…

下一个计划 : .NET/.NET Core应用性能管理

前言 最近几个月一直在研究开源的APM和监控方案&#xff0c;并对比使用了Zipkin,CAT,Sky-walking,PinPoint(仅对比,未实际部署),Elastic APM,TICK Stack,Prometheus等开源产品&#xff0c;其中不乏功能强大的监控和追踪系统&#xff0c;但它们都对.NET/.NET Core没有支持或支持…