在 ASP.NET Core 中配置证书身份验证 (TLS,HTTPS 证书)
ASP.NET Core 中基于策略的授权
IdentityServer4 官网
jwt.io
.NET 5 IdentityServer4 4.X版本配置
1、在.NET Core 3.X 配置[AllowAnonymous]
2、配置白名单:whiteController,whiteAction
3、Policy 策略授权 ASP.NET Core 中基于策略的授权
1、Controller
NuGet 程序包:Microsoft.AspNetCore.Authorization
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace WebAPI.Controllers
{
[ApiVersion("1.0")]
[Route("v{version:apiVersion}/agencys")]
public class TestController : ApiBaseController
{
private readonly IAgencyRepository _agencyRepo;
private readonly IMapper _mapper;
public TestController(IAgencyRepository agencyRepo, IMapper mapper)
{
_agencyRepo = agencyRepo;
_mapper = mapper;
}
[AllowAnonymous]
[HttpGet]
public QueryAgencyResponse GetPageList([FromQuery] QueryAgencyRequest request)
{
PagingDto<AgencyDto> result = _agencyRepo.GetPageList(request);
return new QueryAgencyResponse { data = result };
}
[Authorize(Roles = "admin,customer")]
[Authorize(Policy = "super_admin")]
[Authorize(Policy = "require_claim_user_name")]
[HttpGet("page_async")]
public async Task<QueryAgencyResponse> GetPageListAsync([FromQuery] QueryAgencyRequest request)
{
PagingDto<AgencyDto> result = await _agencyRepo.GetPageListAsync(request);
return new QueryAgencyResponse { data = result };
}
}
}
2、Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mime;
using System.Threading.Tasks;
using Autofac;
using AutoMapper;
using IdentityModel.AspNetCore.OAuth2Introspection;
using IdentityServer4.AccessTokenValidation;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Nuclein.WebAPI
{
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
readonly string AllowSpecificOrigins = "AllowSpecificOrigins";
public void ConfigureContainer(ContainerBuilder containerBuilder)
{
containerBuilder.RegisterModule<AutofacModule>();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<NucleinDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
#region 认证
services.AddAuthentication(config =>
{
config.DefaultScheme = "Bearer";
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration["IdentityAuthentication:Authority"];
options.ApiName = Configuration["IdentityAuthentication:ApiName"];
options.ApiSecret = Configuration["IdentityAuthentication:ApiSecret"];
options.RequireHttpsMetadata = true;
//options.EnableCaching = true;
//options.CacheDuration = TimeSpan.FromMinutes(10);
options.OAuth2IntrospectionEvents = new OAuth2IntrospectionEvents
{
OnAuthenticationFailed = context =>
{
// 身份验证失败
context.Response.ContentType = "application/json";
context.Response.WriteAsync(JsonConvert.SerializeObject(new { code = (int)EnumCode.UnAuthorized, message = "Unauthorized" }));
return Task.CompletedTask;
}
};
options.TokenRetriever = request =>
{
// 令牌检索器,获取token值
var token = TokenRetrieval.FromAuthorizationHeader()(request);
if (string.IsNullOrWhiteSpace(token))
token = TokenRetrieval.FromQueryString()(request);
return token;
};
});
#endregion
#region 授权策略
services.AddAuthorization(options =>
{
//基于角色组策略
options.AddPolicy("require_claim_role", policy => policy.RequireRole("admin", "system"));
//基于用户名策略
options.AddPolicy("require_claim_user_name", policy => policy.RequireUserName("admin"));
//基于ClaimType策略
options.AddPolicy("require_claim_type", policy => policy.RequireClaim(ClaimTypes.Country, "中国"));
//自定义值策略
options.AddPolicy("super_admin", policy => policy.RequireClaim("super_admin"));
});
#endregion
services.AddControllers(options =>
{
options.Filters.Add<AuthorizeAttribute>();
options.Filters.Add<ValidateModelAttribute>();
options.UseCentralRoutePrefix(new RouteAttribute($"api"));
options.MaxModelValidationErrors = 50;
})
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var result = new BadRequestObjectResult(context.ModelState);
result.ContentTypes.Add(MediaTypeNames.Application.Json);
result.ContentTypes.Add(MediaTypeNames.Application.Xml);
return result;
};
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[404].Link = "https://httpstatuses.com/404";
})
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm";
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(0, 1);
});
#region Swagger
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
#region 添加header参数
var securityScheme = new OpenApiSecurityScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
};
var securityRequirement = new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme{Reference = new OpenApiReference{Type = ReferenceType.SecurityScheme,Id = "bearerAuth"}},
new string[] {}
}
};
c.AddSecurityDefinition("bearerAuth", securityScheme);
c.AddSecurityRequirement(securityRequirement);
#endregion
});
#endregion
#region 跨域访问
var urls = Configuration["AppSettings:Cores"].Split(',');
services.AddCors(options =>
{
options.AddPolicy(AllowSpecificOrigins, builder =>
{
builder.WithOrigins(urls).AllowAnyMethod().AllowAnyHeader().AllowCredentials();
//builder.AllowAnyMethod().AllowAnyHeader().AllowCredentials();
});
});
#endregion
#region AutoMapper
services.AddAutoMapper(typeof(AutoMapperInit));
#endregion
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStatusCodePages();
app.UseCors(AllowSpecificOrigins);
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); });
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseApiVersioning();
}
}
}
3、AuthorizeFilter 过滤器
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using Nuclein.CoreLibrary;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Nuclein.WebAPI
{
public class AuthorizeAttribute : AuthorizeFilter
{
private static AuthorizationPolicy _policy_ = new AuthorizationPolicy(new[] { new DenyAnonymousAuthorizationRequirement() }, new string[] { });
public AuthorizeAttribute() : base(_policy_) { }
/// <summary>
/// 请求验证,当前验证部分不要抛出异常,ExceptionFilter不会处理
/// </summary>
/// <param name="context">请求内容信息</param>
public override async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
#region AllowAnonymous【OK】
//var endpoint = context.HttpContext.Features.Get<IEndpointFeature>()?.Endpoint;
//if (endpoint != null && endpoint.Metadata.GetMetadata<AllowAnonymousAttribute>() != null)
// return;
#endregion
#region AllowAnonymous【OK】
//var action = context.ActionDescriptor;
//if (action.EndpointMetadata.Any(p => p is AllowAnonymousAttribute))
// return;
#endregion
#region AllowAnonymous【OK】
var action = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
var allowanyone = cad.ControllerTypeInfo.GetCustomAttributes(typeof(IAllowAnonymous), true).Any()
|| action.MethodInfo.GetCustomAttributes(typeof(IAllowAnonymous), true).Any();
if (allowanyone)
return;
#endregion
List<string> whiteController = StringPlus.StringToListString(ConfigManager.Configuration["AppSettings:whiteController"]);
List<string> whiteAction = StringPlus.StringToListString(ConfigManager.Configuration["AppSettings:whiteAction"]);
#region Header
var request = context.HttpContext.Request;
var headers = request.Headers;
#endregion
#region url地址
var originalUrl = request.Path.Value;
string controllerName = string.Empty;
string actionName = string.Empty;
var list = originalUrl.Split("/").Where(x => !string.IsNullOrEmpty(x) && !x.Contains("v") && !x.Contains("api")).ToArray();
if (list.Length > 0)
{
controllerName = list[0].ToString().Trim();
}
if (list.Length > 1)
{
actionName = list[1].ToString().Trim();
}
#endregion
if (whiteController.Contains(controllerName))
return;
if (controllerName.ToLower() == "passport" && (actionName.ToLower() == "login" || actionName.ToLower() == "refresh_token"))
return;
if (!context.HttpContext.User.Identity.IsAuthenticated || context.Filters.Any(item => item is IAllowAnonymousFilter))
{
context.Result = new JsonResult(new { code = "401", msg = "Unauthorized" });
return;
}
if (context.HttpContext.User.Identity.IsAuthenticated)
{
var claimIdentity = (ClaimsIdentity)context.HttpContext.User.Identity;
Claim userClaim = claimIdentity.Claims.Where(x => x.Type.Contains("user_code")).FirstOrDefault();
if (IsHaveAllow(context.Filters))
{
context.Result = new JsonResult(new { code = "401", msg = "Unauthorized" });
return;
}
}
//if (context.HttpContext.User.Identity.Name != "admin")
//{
// //未通过验证则跳转到无权限提示页
// RedirectToActionResult content = new RedirectToActionResult("NoAuth", "Exception", null);
// context.Result = content;
//}
await base.OnAuthorizationAsync(context);
}
/// <summary>
/// 判断是否不需要权限
/// </summary>
/// <param name="filers"></param>
/// <returns></returns>
public static bool IsHaveAllow(IList<IFilterMetadata> filers)
{
for (int i = 0; i < filers.Count; i++)
{
if (filers[i] is IAllowAnonymousFilter)
{
return true;
}
}
return false;
}
}
}
4、.NET 5 IdentityServer4 4.X版本配置【\AuthService\Security\IdentityConfig.cs】
using AuthService.CoreLibrary;
using IdentityServer4.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AuthService.Security
{
public class IdentityConfig
{
public static IEnumerable<ApiResource> GetResources()
{
return new List<ApiResource>
{
new ApiResource(ConfigManager.Configuration["IdentityAuthentication:Scope"],ConfigManager.Configuration["IdentityAuthentication:ClientName"])
{
#region ids4 4.X 新特性
Scopes = { "Asset",ConfigManager.Configuration["IdentityAuthentication:Scope"]},
#endregion
ApiSecrets = { new Secret(ConfigManager.Configuration["IdentityAuthentication:Secret"].Sha256()) }
}
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
}
/// <summary>
/// ids4 4.X 新特性
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiScope> GetScopes()
{
return new ApiScope[]
{
new ApiScope("Asset"),
new ApiScope(ConfigManager.Configuration["IdentityAuthentication:Scope"]),
};
}
/// <summary>
/// ids4 4.X 新特性
/// </summary>
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("Asset"),
new ApiScope(ConfigManager.Configuration["IdentityAuthentication:Scope"]),
};
}
}
5、.NET 5 IdentityServer4 4.X版本配置【\AuthService\Startup.cs】
using AuthService.Filters;
using IdentityServer4.Configuration;
using IdentityServer4.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;
namespace AuthService
{
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer(options => { })
.AddDeveloperSigningCredential()
.AddInMemoryClients(Security.Clients.GetClients())
.AddInMemoryIdentityResources(Security.IdentityConfig.GetIdentityResources())
.AddInMemoryApiResources(Security.IdentityConfig.GetResources())
#region ids4 4.X 新特性
.AddInMemoryApiScopes(Security.IdentityConfig.GetScopes())
//.AddInMemoryApiScopes(Security.IdentityConfig.ApiScopes)
#endregion
.AddProfileService<Security.ProfileService>()
.AddResourceOwnerValidator<Security.ResourceOwnerPasswordValidator>();
}
}
}
6、.NET 5 Policy 策略授权配置【\AuthService\Security\ResourceOwnerPasswordValidator.cs】
添加了new Claim("super_admin", "true")
private Claim[] GetUserClaims(UserDto user)
{
if (user.user_name.ToString() == "admin")
{
return new Claim[]
{
new Claim("user_id", user.id.ToString()),
new Claim("user_name", user.user_name.ToString()),
new Claim("real_name", user.real_name.ToString()),
#region Policy
new Claim("super_admin", "true"),
#endregion
new Claim(JwtClaimTypes.Name,user.user_name),
new Claim(JwtClaimTypes.Role, user.role == null ? "customer" : user.role)
};
}
else
{
return new Claim[]
{
new Claim("user_id", user.id.ToString()),
new Claim("user_name", user.user_name.ToString()),
new Claim("real_name", user.real_name.ToString()),
new Claim(JwtClaimTypes.Name,user.user_name),
new Claim(JwtClaimTypes.Role, user.role == null ? "customer" : user.role)
};
}
}
*
*
*
*
*
*
*