.Net Core微服务化ABP之六——处理Authentication
上篇中我们已经可以实现sso,并且为各个服务集成sso认证。本篇处理权限系统的角色问题,权限系统分两层,第一层为整体系统角色权限,区分app用户、后台用户、网站用户的接口权限,第二层为业务系统权限,对业务子系统各个岗位的人划分不同权限。
第一层的角色固化在代码中,如商家app用户,师傅app用户,订单系统用户,接单系统用户等,第二层角色可自定义,和现有系统的角色概念一致。权限系统需要读取其他各个子系统的权限列表(标记在控制器、action上),并在系统权限中定义第一层权限和第二层权限。然后自定义一个Authorize方法,在里面实现第一层、第二层权限的authentication认证。想要实现权限鉴定,首先需要在用户Claims中取得除用户id外必要的用户信息,所以先实现自定义Claims。
1.自定义Claims
2.MVC项目sso跳转
3.Authentication验证
自定义Claims
打开Authorize项目的Startup,可以看到AddAbpIdentityServer,查看源码,我们模仿abp的方式自己实现一个认证。考虑到需要读取数据库,所以卸载WebCore项目
先实现AddProtonIdentityServer扩展方法
using System;
using System.IdentityModel.Tokens.Jwt;
using Abp.Authorization.Users;
using Abp.IdentityServer4;
using Abp.Runtime.Security;
using IdentityModel;
using IdentityServer4.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; namespace Authorize.Validation
{
public static class IdentityServerBuilderExtensions
{ /// <summary>
/// 一个简单的扩展,用于注册IdentityServer
/// </summary>
/// <typeparam name="TUser"></typeparam>
/// <param name="builder"></param>
/// <param name="optionsAction"></param>
/// <returns></returns>
public static IIdentityServerBuilder AddProtonIdentityServer<TUser>(this IIdentityServerBuilder builder, Action<AbpIdentityServerOptions> optionsAction = null)
where TUser : AbpUser<TUser>
{
var options = new AbpIdentityServerOptions();
optionsAction?.Invoke(options); builder.AddAspNetIdentity<TUser>(); builder.AddProfileService<AbpProfileService<TUser>>();
builder.AddResourceOwnerValidator<ProtonResourceOwnerPasswordValidator<TUser>>(); builder.Services.Replace(ServiceDescriptor.Transient<IClaimsService, ProtonClaimService>()); if (options.UpdateAbpClaimTypes)
{
AbpClaimTypes.UserId = JwtClaimTypes.Subject;
AbpClaimTypes.UserName = JwtClaimTypes.Name;
AbpClaimTypes.Role = JwtClaimTypes.Role;
} if (options.UpdateJwtSecurityTokenHandlerDefaultInboundClaimTypeMap)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.UserId] = AbpClaimTypes.UserId;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.UserName] = AbpClaimTypes.UserName;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.Role] = AbpClaimTypes.Role;
} return builder;
}
}
}
还需要自定义ProtonResourceOwnerPasswordValidator和ProtonClaimService并且替换具体实现,这里的ProtonResourceOwnerPasswordValidator没有修改abp默认的用户表。
using Abp.Authorization.Users;
using Abp.Domain.Uow;
using Abp.Json;
using Abp.Runtime.Security;
using IdentityModel;
using IdentityServer4.AspNetIdentity;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; namespace Authorize.Validation
{
/// <summary>
/// 自定义 Resource owner password 验证器
/// </summary>
/// <typeparam name="TUser"></typeparam>
public class ProtonResourceOwnerPasswordValidator<TUser> : ResourceOwnerPasswordValidator<TUser> where TUser : AbpUser<TUser>
{
/// <summary>
/// 使用真实数据库验证用户
/// </summary>
protected UserManager<TUser> UserManager { get; } protected SignInManager<TUser> SignInManager { get; } protected ILogger<ResourceOwnerPasswordValidator<TUser>> Logger { get; } public ProtonResourceOwnerPasswordValidator(
UserManager<TUser> userManager,
SignInManager<TUser> signInManager,
IEventService eventService,
ILogger<ResourceOwnerPasswordValidator<TUser>> logger)
: base(
userManager,
signInManager,
eventService,
logger)
{
UserManager = userManager;
SignInManager = signInManager;
Logger = logger;
} [UnitOfWork]
public override async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var user = await UserManager.FindByNameAsync(context.UserName);
if (user != null)
{
var result = await SignInManager.CheckPasswordSignInAsync(user, context.Password, true);
if (result.Succeeded)
{
Logger.LogInformation("Credentials validated for username: {username}", context.UserName); //验证通过返回结果
//subjectId 为用户唯一标识 一般为用户id
//authenticationMethod 描述自定义授权类型的认证方法
//authTime 授权时间
//claims 需要返回的用户身份信息单元 此处应该根据我们从数据库读取到的用户信息 添加Claims 如果是从数据库中读取角色信息,那么我们应该在此处添加 此处只返回必要的Claim
var sub = await UserManager.GetUserIdAsync(user);
var claims = GetAdditionalClaimsOrNull(user);
context.Result = new GrantValidationResult(
sub,
OidcConstants.AuthenticationMethods.Password,
claims); //Logger.LogInformation($"claims:{claims.Select(d => d.Type + ":" + d.Value).ToJsonString()}");
return;
}
else if (result.IsLockedOut)
{
Logger.LogInformation("Authentication failed for username: {username}, reason: locked out", context.UserName);
}
else if (result.IsNotAllowed)
{
Logger.LogInformation("Authentication failed for username: {username}, reason: not allowed", context.UserName);
}
else
{
Logger.LogInformation("Authentication failed for username: {username}, reason: invalid credentials", context.UserName);
}
}
else
{
Logger.LogInformation("No user found matching username: {username}", context.UserName);
} context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
} protected virtual IEnumerable<Claim> GetAdditionalClaimsOrNull(TUser user)
{
var additionalClaims = new List<Claim>();
if (user.TenantId.HasValue)
{
additionalClaims.Add(new Claim(AbpClaimTypes.TenantId, user.TenantId?.ToString()));
} /*
* 系统角色
* 如超级管理员,xx子系统管理员,xx子系统普通用户,xxApp普通用户等,
* 角色列表被固化在代码中,sso系统中每个账户可拥有一个或多个系统角色,每个接口上有标记Authorize[Role="abc"],不属于角色列表的用户无法调用接口。
* 注意和业务角色的区分,两者是完全不同的概念。业务角色是指以往所说的同类权限的用户组集合。
*/
additionalClaims.Add(new Claim(ProtonClaimTypes.SystemRole, "systemrole")); //从用户表取 /*
* 直营商id
* 类似于tenantid,但全部放在业务系统处理相关逻辑。可能会换成tenantid
*/
additionalClaims.Add(new Claim(ProtonClaimTypes.PartnerId, "")); return additionalClaims;
}
} /// <summary>
/// Proton自定义ClaimTypes
/// </summary>
public static class ProtonClaimTypes
{
public const string PartnerId = "partner_id"; public const string SystemRole = "system_role";
}
}
注意在GetAdditionalClaimsOrNull方法添加需要在api服务获取的Claim属性。ProtonClaimService代码如下:
using Abp.Json;
using Abp.Runtime.Security;
using IdentityModel;
using IdentityServer4.Services;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims; namespace Authorize.Validation
{
public class ProtonClaimService : DefaultClaimsService
{
public ProtonClaimService(IProfileService profile, ILogger<DefaultClaimsService> logger)
: base(profile, logger)
{
} protected override IEnumerable<Claim> GetOptionalClaims(ClaimsPrincipal subject)
{
var claims = base.GetOptionalClaims(subject); var tenantClaim = subject.FindFirst(AbpClaimTypes.TenantId);
if (tenantClaim != null)
{
claims = claims.Union(new[] { tenantClaim });
} var sysRoleClaim = subject.FindFirst(ProtonClaimTypes.SystemRole);
if (sysRoleClaim != null)
{
claims = claims.Union(new[] { sysRoleClaim });
}
var partnerIdClaim = subject.FindFirst(ProtonClaimTypes.PartnerId);
if (partnerIdClaim != null)
{
claims = claims.Union(new[] { partnerIdClaim });
} Logger.LogInformation(claims.Select(d => d.Type + ":" + d.Value).ToJsonString());
return claims;
}
}
}
同样需要把必要的Claims都加上,否则api服务取不到。如果是自带的claims,想在api服务中取得,那么修改IdentityServerConfig.GetApiResources方法
/// <summary>
/// 允许使用认证服务的api列表
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("serviceorder", "Default (all) API",new List<string>(){JwtClaimTypes.Role}),
new ApiResource("servicepartner", "Default (all) API1"),
};
}
修改一下订单服务的Default控制器以适应测试数据
// GET: api/Default
[HttpGet]
[Authorize(Roles = "Admin")]
public String Get()
{
return $"claims:{HttpContext.User.Claims.Select(d => d.Type+":"+d.Value).ToJsonString()}";
//return new string[] { $"{serviceName}: {DateTime.Now.ToString()} {Environment.MachineName} " +
// $"OS: {Environment.OSVersion.VersionString}" };
} // GET: api/Default/5
[Authorize(Roles = "SuperAdmin")]
[HttpGet("{id}", Name = "Get")]
public string Get(int id)
{
return $"claims:{HttpContext.User.Claims.Select(d => d.Type + ":" + d.Value).ToJsonString()}";
return serviceName + ".value." + id;
}
设想的结果是,Admin角色可以访问/api/default接口,并且返回Claims数据集合,但无法访问/api/default/1接口,我们测试一下
从authorize服务取得token,然后用这个token请求/api/default
正常返回claims信息,继续请求/api/default/1
提示403 forbidden,测试通过
MVC项目sso跳转
authorize服务本身的api接口,未授权401时,也会跳转到Account/Login,就用这个来做测试。sso登录需要统一的登录页,
IdentityServer4官方提供了一个QuickstartUI组件,包含了登录、授权、查看权限等基本功能,可以基于此建立第一个版本
https://github.com/IdentityServer/IdentityServer4.Quickstart.UI
下载来代码后,我们需要以下3个文件夹,复制到Authorize服务Host项目下
修改AuthConfigurer
services.AddAuthentication().AddIdentityServerAuthentication(configuration["Authentication:JwtBearer:DefaultScheme"], options =>
{
options.Authority = $"http://{configuration["Service:IP"]}:{configuration["Service:Port"]}/";
options.RequireHttpsMetadata = false;
options.ApiName = "serviceauthorize";
});
启用CORS,支持跨域,这里直接用abp的代码,未做改变
// Configure CORS for angular2 UI
services.AddCors(
options => options.AddPolicy(
_defaultCorsPolicyName,
builder => builder
.WithOrigins(
// App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
_appConfiguration["App:CorsOrigins"]
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(o => o.RemovePostFix("/"))
.ToArray()
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
)
);
app.UseCors(_defaultCorsPolicyName); // Enable CORS!
修改appsettings.json,增加以下节点
"App": {
"CorsOrigins": "http://localhost:21021,http://localhost:8080,http://localhost:8081,http://localhost:3000"
},
去掉项目本身自带的HomeController(和demo的homecontroller冲突了),然后修改demo带来的每个控制器基类为AuthorizeControllerBase,比如:
/// <summary>
/// This sample controller allows a user to revoke grants given to clients
/// </summary>
[SecurityHeaders]
[Authorize]
public class GrantsController : AuthorizeControllerBase
{
private readonly IIdentityServerInteractionService _interaction;
private readonly IClientStore _clients;
private readonly IResourceStore _resources;
private readonly IEventService _events; public GrantsController(IIdentityServerInteractionService interaction,
IClientStore clients,
IResourceStore resources,
IEventService events)
{
_interaction = interaction;
_clients = clients;
_resources = resources;
_events = events;
} //......
}
最后,添加一个defaultcontroller用于测试,给Get方法添加Authorize标记
[DontWrapResult]
[Route("api/[controller]")]
public class DefaultController : AuthorizeControllerBase
{ private string serviceName = string.Empty;
public IConfiguration Configuration { get; } public DefaultController(IConfiguration configuration)
{
Configuration = configuration;
serviceName = Configuration["Service:Name"];
} // GET: api/Default
[HttpGet]
[Authorize]
public IEnumerable<string> Get()
{
return new string[] { $"{serviceName}: {DateTime.Now.ToString()} {Environment.MachineName} " +
$"OS: {Environment.OSVersion.VersionString}" };
} // other methods
}
运行项目,可以顺利打开http://localhost:21021/Account/Login
访问/api/default,会跳转到login,输入用户名密码登录后,可以访问到api/default这个受权限保护的接口,在实际mvc应用中,这个接口就是视图页
接下来处理新建一个mvc应用来实现上述功能。建立mvc应用是因为现有后端开发可以先行开发mpa应用,如果想开发spa,则需要单独招聘前端人员。mvc应用只提供视图,不提供任何其他接口,应用前端通过api网关直接向service请求数据,这是为了以后mpa转变为spa更方便。
权限问题,mtn系统可以管理整个系统的用户,用户的系统角色属性可能是商家app用户,师傅app用户,mtn管理员,mtn普通用户,order管理员,order普通用户等。一个用户可以有多个系统角色。mtn系统对每个系统角色都可以自定义权限范围,权限全集是所有页面权限(来自mpa应用或spa自定义的规则)和接口权限。mtn管理员在进入mtn系统后,可以添加mtn系统的业务角色,指定每个业务角色的权限(在全集内),还可以管理mtn系统的用户(从authorize服务取所有系统角色属于mtn管理员和mtn普通用户的所有用户),并为这些用户指定角色(可多个)。这部分功能放在基础服务实现。
从abp下载一个带login的模板,并用rename.ps1来重命名,注意CompanyName不要使用“Service”!,会导致rename时替换不应该替换的字符。去掉不相关的项目,只保留mvc
继承过程中遇到这个问题
IdentityServer4.Hosting.IdentityServerMiddleware[]
Unhandled exception: 系统找不到指定的文件。
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: 系统找不到指定的文件。
at System.Security.Cryptography.CngKey.Open(String keyName, CngProvider provider, CngKeyOpenOptions openOptions)
at System.Security.Cryptography.CngKey.Open(String keyName, CngProvider provider)
at Internal.Cryptography.Pal.CertificatePal.GetPrivateKey[T](Func` createCsp, Func` createCng)
at Internal.Cryptography.Pal.CertificatePal.GetRSAPrivateKey()
at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()
at Microsoft.IdentityModel.Tokens.X509SecurityKey.get_PrivateKey()
at Microsoft.IdentityModel.Tokens.X509SecurityKey.get_PrivateKeyStatus()
解决方案:应用程序池,高级设置,修改“加载用户配置文件”为“True”即可!
https://blog.csdn.net/mushui0633/article/details/78596615
mvc应用访问authorize限制的页面,自动跳转到权限中心登录页,但输入账号密码登录成功后,不跳转,有以下几种错误信息:
Showing login: User is not active
Client requested access token - but client is not configured to receive access tokens via browser(服务端clients属性没开)
Navigation property 'Claims' on entity of type 'User' cannot be loaded because the entity is not being tracked
Unable to obtain configuration from: '[PII is hidden]'
Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Executing ChallengeResult with authentication schemes ().
最后发现权限服务AddProtonIdentityServer方法中的AbpProfileService方法有问题,但是去掉后又导致role这个claim丢失,所以先写固定值,后面自己重写一套usermanager
增加一个ProtonProfileService
using Abp.Authorization.Users;
using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; namespace Authorize.Validation
{
public class ProtonProfileService<TUser> : IProfileService where TUser : AbpUser<TUser>
{
protected UserManager<TUser> UserManager { get; }
protected ILogger<ProtonProfileService<TUser>> Logger { get; }
protected readonly IUserClaimsPrincipalFactory<TUser> ClaimsFactory; public ProtonProfileService(
UserManager<TUser> userManager,
ILogger<ProtonProfileService<TUser>> logger,
IUserClaimsPrincipalFactory<TUser> claimsFactory)
{
UserManager = userManager;
Logger = logger;
ClaimsFactory = claimsFactory;
} public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{ //var sub = context.Subject?.GetSubjectId();
//if (sub == null) throw new Exception("No sub claim present"); //var user = await UserManager.FindByIdAsync(sub);
//if (user == null)
//{
// Logger?.LogWarning("No user found matching subject Id: {0}", sub);
//}
//else
//{
// var principal = await ClaimsFactory.CreateAsync(user);
// if (principal == null) throw new Exception("ClaimsFactory failed to create a principal"); // context.AddRequestedClaims(principal.Claims);
//}
var claims = context.Subject.Claims.ToList(); context.IssuedClaims = claims.ToList();
} public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
}
}
}
修改IdentityRegistrar中的Client
new Client
{
ClientId = "default_mvc_client",
ClientName="default_name1114324324",
AllowedGrantTypes = GrantTypes.Implicit,
RedirectUris = { $"{Configuration["Clients:MvcClient:RedirectUri"]}signin-oidc" },
PostLogoutRedirectUris = { $"{Configuration["Clients:MvcClient:RedirectUri"]}signout-callback-oidc" },
AllowedScopes =new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"serviceorder",
},
AllowAccessTokensViaBrowser = true
}
将IdentityServerBuilderExtensions中的AbpProfileService替换为ProtonProfileService
修改ProtonResourceOwnerPasswordValidator中的GetAdditionalClaimsOrNull,增加一个role,后期重写时会替换为真实的用户角色
protected virtual IEnumerable<Claim> GetAdditionalClaimsOrNull(TUser user)
{
var additionalClaims = new List<Claim>();
if (user.TenantId.HasValue)
{
additionalClaims.Add(new Claim(AbpClaimTypes.TenantId, user.TenantId?.ToString()));
} /*
* 系统角色
* 如超级管理员,xx子系统管理员,xx子系统普通用户,xxApp普通用户等,
* 角色列表被固化在代码中,sso系统中每个账户可拥有一个或多个系统角色,每个接口上有标记Authorize[Role="abc"],不属于角色列表的用户无法调用接口。
* 注意和业务角色的区分,两者是完全不同的概念。业务角色是指以往所说的同类权限的用户组集合。
*/
additionalClaims.Add(new Claim(ProtonClaimTypes.SystemRole, "systemrole")); //从用户表取 /*
* 业务角色
* 业务子系统的角色
*/
additionalClaims.Add(new Claim(JwtClaimTypes.Role, "Admin")); /*
* 直营商id
* 类似于tenantid,但全部放在业务系统处理相关逻辑。可能会换成tenantid
*/
additionalClaims.Add(new Claim(ProtonClaimTypes.PartnerId, "")); return additionalClaims;
}
修改appsettings.json
"Clients": {
"MvcClient": {
"RedirectUri": "http://192.168.8.157:5200/",
"LogoutRedirectUri": "http://192.168.8.157:5200/"
}
}
修改新增的mpa.maintenance项目的HomeController,给About这个action加上Authorize
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; namespace Mpa.Maintenance.Web.Controllers
{
public class HomeController : MaintenanceControllerBase
{
public ActionResult Index()
{
return View();
} [Authorize]
public ActionResult About()
{
return View();
}
}
}
修改其Startup.cs,其中Authority地址必须和认证服务地址一致,ClientId和权限服务配置的client信息一致。
using System;
using System.IdentityModel.Tokens.Jwt;
using Abp.AspNetCore;
using Abp.Castle.Logging.Log4Net;
//using Abp.EntityFrameworkCore;
//using Mpa.Maintenance.EntityFrameworkCore;
using Castle.Facilities.Logging;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; namespace Mpa.Maintenance.Web.Startup
{
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//Configure DbContext
//services.AddAbpDbContext<MaintenanceDbContext>(options =>
//{
// DbContextOptionsConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
//}); services.AddMvc(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies"; options.Authority = "http://192.168.8.157:5100";
options.RequireHttpsMetadata = false; options.ClientId = "default_mvc_client";
options.ResponseType = "id_token token"; // allow to return access token
options.SaveTokens = true;
}); //Configure Abp and Dependency Injection
return services.AddAbp<MaintenanceWebModule>(options =>
{
//Configure Log4Net logging
options.IocManager.IocContainer.AddFacility<LoggingFacility>(
f => f.UseAbpLog4Net().WithConfig("log4net.config")
);
});
} public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseAbp(); //Initializes ABP framework. if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseAuthentication(); app.UseStaticFiles(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
再次测试,把之前的两个测试都重复一遍,验证api客户端和mvc客户端都可以正常使用
Authentication验证
有了基础的权限以后,我们需要考虑一下权限系统如何实现,然后再处理如何在controller,api,mpa_controller中获取session信息。authorize特性可以基于用户角色来验证,但这个角色必须固化在action上面,只能处理固化的系统角色,并不符合当前业务需求。当前业务需求是基于系统角色、业务角色来限制权限,需要灵活可配置每个角色的权限。所以需要自定义authorizeattribute,并设置一些属性来标记权限值(字符串),同时在控制器上也需要自定义attribute,这样就获得一个action的权限值,如“controllerName.actionName”,可以经由api接口或其他途径提供给authorize服务,在数据库中建立与角色的对应关系。最后再自定义一个filter用于验证权限即可。session的实现,可以经由Httpcontext.user.claims取得并赋值。
首先实现session功能,参考https://www.jianshu.com/p/930c10287e2a方式二。
第一步是确保所需的claims已经正常添加到context,然后我们基于abpSession进行扩展。新建一个IProtonSesson,继承IAbpSession,并添加我们需要的属性.
using Abp.Runtime.Session; namespace Proton.Web.Session
{
public interface IProtonSession : IAbpSession
{
string PartnerId { get; } //int UserId { get; } string SystemPermissions { get; }
}
}
新建ProtonSession类,继承ClaimsAbpSession,IProtonSession,注意其中override重写了UserId字段
using Abp.Configuration.Startup;
using Abp.Json;
using Abp.MultiTenancy;
using Abp.Runtime;
using Abp.Runtime.Session;
using Castle.Core.Logging;
using IdentityModel;
using Microsoft.AspNetCore.Http;
using Proton.IdentityModel;
using System;
using System.Linq; namespace Proton.Web.Session
{
public class ProtonSession : ClaimsAbpSession, IProtonSession
{ private IHttpContextAccessor _accessor;
public ILogger Logger { get; set; } public ProtonSession(
IPrincipalAccessor principalAccessor,
IMultiTenancyConfig multiTenancy,
ITenantResolver tenantResolver,
IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider,
IHttpContextAccessor accessor)
: base(
principalAccessor,
multiTenancy,
tenantResolver,
sessionOverrideScopeProvider)
{
_accessor = accessor;
Logger = NullLogger.Instance;
} public string PartnerId => GetClaimValue(ProtonClaimTypes.PartnerId); public override long? UserId => Int32.Parse(GetClaimValue(JwtClaimTypes.Subject)); public string SystemPermissions => GetClaimValue(ProtonClaimTypes.SystemPermissions); private dynamic GetClaimValue(string claimType)
{
//var claimsPrincipal = PrincipalAccessor.Principal; //var claim = claimsPrincipal?.Claims.FirstOrDefault(c => c.Type == claimType);
var claim = _accessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == claimType);
Logger.InfoFormat(_accessor.HttpContext.User.Claims.Select(d => d.Type + ":" + d.Value).ToJsonString());
if (string.IsNullOrEmpty(claim?.Value))
{
return null;
} return claim.Value;
}
}
}
这里用到IHttpContextAccessor,参考https://www.cnblogs.com/liuxiaoji/p/6860122.html,需要在service提前注入实现。
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IProtonSession, ProtonSession>();
替换掉注入的AbpSession,在ControllerBase中处理
public new IProtonSession AbpSession { get; set; }
修改defaultcontroller来测试一下
public string Get()
{
var session = AbpSession;
var userId = AbpSession.UserId;
var partnerid = AbpSession.PartnerId; var r = $"claims:{HttpContext.User.Claims.Select(d => d.Type + ":" + d.Value).ToJsonString()}";
return $"session:{session.ToJsonString()};.\r\n"
+ r + "\r\n"
+ $"userId:{userId},partnerid:{partnerid}";
}
可以看到成功取得userid,partnerid,并可以看到claims中所有的值。
下面处理权限验证。首先定义ProtonAuthorizeAttribute
using Abp.Extensions;
using Microsoft.AspNetCore.Authorization;
using System; namespace Proton.Web.Authorize
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class ProtonAuthorizeAttribute : AuthorizeAttribute
{
/// <summary>
/// Action权限值名称,英文,同一控制器下唯一
/// </summary>
public string AuthName { get; set; } /// <summary>
/// 权限中文名,用于权限列表展示
/// </summary>
public string AuthNameDisplay { get; set; } /// <summary>
/// 构造函数
/// </summary>
/// <param name="authName">权限值</param>
/// <param name="displayName">权限值中文名称。</param>
public ProtonAuthorizeAttribute(string authName, string displayName = "")
{
AuthName = authName;
AuthNameDisplay = displayName.IsNullOrEmpty() ? authName : displayName; ;
}
}
}
定义ProtonControllerAttribute
using System; namespace Proton.Web.Authorize
{
public class ProtonControllerAttribute : Attribute
{
/// <summary>
/// ControllerID,Guid,唯一值,用于和数据库对比记录
/// </summary>
public string Id { get; set; } /// <summary>
/// Controller权限值名称
/// </summary>
public string ControllerName { get; set; } /// <summary>
/// Controller中文名称,用于权限列表显示
/// </summary>
public string ControllerNameDisplay { get; set; }
public ProtonControllerAttribute(string id, string controllerName, string controllerNameDisplay = "")
{
Id = id;
ControllerName = controllerName;
ControllerNameDisplay =
String.IsNullOrEmpty(controllerNameDisplay) ? controllerName : controllerNameDisplay;
}
}
}
其中预置的ProtonAuthNames如下:
using System.ComponentModel; namespace Proton.Web.Authorize
{
/// <summary>
/// 页面元素预置权限类型
/// 可以自定义,只允许添加通用类型,特殊类型直接用字符串
/// </summary>
public static class ProtonAuthNames
{
public const string List = "list";
public const string Detail = "look";
public const string Create = "create";
public const string Edit = "edit";
public const string Delete = "delete";
public const string Import = "import";
public const string Export = "export";
public const string Accept = "accept";
public const string Reject = "reject";
public const string CustomeOne = "custom1"; }
}
新建authorizefilter,定义AuthorizeFilter
using Abp.Collections.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Proton.IdentityModel;
using System.Collections.Generic;
using System.Linq; namespace Proton.Web.Authorize
{
/// <summary>
/// 权限验证拦截器
/// 当前只用于第一级系统角色验证,还需适应第二级业务角色验证
/// </summary>
public class ProtonAuthorizationFilter : RequireHttpsAttribute
{
public override void OnAuthorization(AuthorizationFilterContext context)
{
if (context.Filters.Any(item => item is IAllowAnonymousFilter))
{
return;
} if (!(context.ActionDescriptor is ControllerActionDescriptor))
{
return;
} //不存在claims信息,需要验证登录
if (!context.HttpContext.User.Claims.Any())
{
return;
} var attributeList = new List<object>();
attributeList.AddRange((context.ActionDescriptor as ControllerActionDescriptor).MethodInfo.GetCustomAttributes(true));
attributeList.AddRange((context.ActionDescriptor as ControllerActionDescriptor).MethodInfo.DeclaringType.GetCustomAttributes(true)); //获取当前服务id,控制器id,控制器名称,action名称
var actionAttributes = attributeList.OfType<ProtonAuthorizeAttribute>().ToList();
string authName = actionAttributes.FirstOrDefault()?.AuthName; var controllerAttributes = attributeList.OfType<ProtonControllerAttribute>().ToList();
string controllerName = controllerAttributes.FirstOrDefault()?.ControllerName;
var permissionStr = $"{controllerName}.{authName}"; //获取用户权限并判断
var claims = context.HttpContext.User.Claims;
var permissions = claims.FirstOrDefault(c => c.Type == ProtonClaimTypes.SystemPermissions)?.Value.Split(',');
permissions = new[] { "default_one.list" };
if (!permissions.IsNullOrEmpty() && permissions.Any(s => s.Equals(permissionStr)))
{
//授权通过
return;
} //未授权返回403
context.Result = new StatusCodeResult();
}
}
}
准备测试数据,修改DefaultController
[HttpGet]
[ProtonAuthorize(ProtonAuthNames.List, "列表")]
public string Get()
{
var session = AbpSession;
var userId = AbpSession.UserId;
var partnerid = AbpSession.PartnerId; var r = $"claims:{HttpContext.User.Claims.Select(d => d.Type + ":" + d.Value).ToJsonString()}";
return $"session:{session.ToJsonString()};.\r\n"
+ r + "\r\n"
+ $"userId:{userId},partnerid:{partnerid}";
} // GET: api/Default/5
[HttpGet("{id}", Name = "Get")]
[ProtonAuthorize(ProtonAuthNames.Accept, "提交")]
public string Get(int id)
{
return serviceName + ".value." + id;
}
其中控制器也要加上Attribute
[ProtonController("72701F1B-4DAF-4E4C-AB87-9DA36CEC9D02", "default_one", "默认页面")]
public class DefaultController : AuthorizeControllerBase
filter中定义了固化的permission="default_one.list"(claims没传回来,需要修改),所以理论上是/api/default和/api/default/1都需要登录,但前者有权限,可以读取内容,后者无权限,返回403.
测试结果表明,api访问可以正常取得所有claim,mpa应用和authorize服务不能,只能取到以下信息,这个问题后面在解决
参考文章:
https://www.cnblogs.com/stulzq/p/8726002.html
https://www.cnblogs.com/edisonchou/p/identityserver4_foundation_and_quickstart_01.html
https://www.jianshu.com/p/930c10287e2a
https://www.cnblogs.com/jaycewu/p/7791102.html
.Net Core微服务化ABP之六——处理Authentication的更多相关文章
- 基于.NET CORE微服务框架 -surging的介绍和简单示例 (开源)
一.前言 至今为止编程开发已经11个年头,从 VB6.0,ASP时代到ASP.NET再到MVC, 从中见证了.NET技术发展,从无畏无知的懵懂少年,到现在的中年大叔,从中的酸甜苦辣也只有本人自知.随着 ...
- .NET Core微服务之基于IdentityServer建立授权与验证服务(续)
Tip: 此篇已加入.NET Core微服务基础系列文章索引 上一篇我们基于IdentityServer4建立了一个AuthorizationServer,并且继承了QuickStartUI,能够成功 ...
- .NET Core微服务之基于Ocelot+IdentityServer实现统一验证与授权
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.案例结构总览 这里,假设我们有两个客户端(一个Web网站,一个移动App),他们要使用系统,需要通过API网关(这里API网关始终作为 ...
- 【NET CORE微服务一条龙应用】第三章 认证授权与动态权限配置
介绍 系列目录:[NET CORE微服务一条龙应用]开始篇与目录 在微服务的应用中,统一的认证授权是必不可少的组件,本文将介绍微服务中网关和子服务如何使用统一的权限认证 主要介绍内容为: 1.子服务如 ...
- .NET Core微服务之路:文章系列和内容索引汇总 (v0.52)
微服务架构,对于从事JAVA架构的童鞋来说,早已不是什么新鲜的事儿,他们有鼎鼎大名的Spring Cloud这样的全家桶框架支撑,包含微服务核心组件如 1. Eureka:实现服务注册与发现. 2. ...
- .NET CORE微服务实践
.NET CORE微服务实践 https://www.cnblogs.com/zengqinglei/p/9570343.html .NET CORE 实践部署架构图 实践源码:https://git ...
- 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发
<ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...
- .Net Core微服务入门全纪录(七)——IdentityServer4-授权认证
前言 上一篇[.Net Core微服务入门全纪录(六)--EventBus-事件总线]中使用CAP完成了一个简单的Eventbus,实现了服务之间的解耦和异步调用,并且做到数据的最终一致性.这一篇将使 ...
- .NET Core微服务系列基础文章索引(目录导航Final版)
一.为啥要总结和收集这个系列? 今年从原来的Team里面被抽出来加入了新的Team,开始做Java微服务的开发工作,接触了Spring Boot, Spring Cloud等技术栈,对微服务这种架构有 ...
随机推荐
- unity3d之简单动画
Unity3d中有两个关于动画的概念,Animation和Animator,看一下他们的创建和区别 1.创建一个物体后可以添加Animator和Animation组件如图所示 2.Animation和 ...
- [转+自]SSH工作原理
SSH工作原理 熟悉Linux的人肯定都知道SSH.SSH是一种用于安全访问远程服务器的网络协议.它将客户端与服务端之间的消息通过加密保护起来,这样就无法被窃取或篡改了.那么它安全性是如何实现的呢? ...
- 借助leetcode题目来了解BFS和DFS
广度优先和深度优先搜索 前言 看着这两个搜索的前提的是读者具备图这一数据结构的基本知识,这些可以直接百度一波就了解了.图也像树一样,遍历具有很多的学问在里面,下面我将借用leetcode的题目讲解一下 ...
- 在Thinkphp中微信公众号JsApi支付
由于网站使用的微信Native扫码支付,现在公众号需要接入功能,怎么办呢,看这官方文档,参考着demo进行写吧.直接进入正题 进入公众号(服务号)设置--->功能设置--->网页授权域名配 ...
- JavaScript中一种全新的数据类型-symbol
连续连载了几篇<ES6对xxx的扩展>,本节咱们换换口味,介绍一种全新的数据类型:Symbol,中文意思为:标志,记号.音标:[ˈsɪmbəl]. 数据类型 在介绍Symbol之前,我们简 ...
- [Asp.Net Core] Blazor Server Side 扩展用途 - 配合CEF来制作客户端浏览器软件
前言 大家用过微信PC端吧? 这是用浏览器做的. 用过Visual Studio Code吧? 也是用浏览器做的. 听说, 暴雪客户端也包含浏览器核心?? 在客户端启动一个浏览器, 并不是什么难事了. ...
- 监控CPU与GPU的工具
1.sensor:可以显示包括cpu在内的所有传感器的当前读数 使用sensors可以检测到cpu的温度,风扇的风速度,电压等. 2.Glances使用Python写的跨平台的curses的检测工具. ...
- Scala教程之:Option-Some-None
文章目录 Option和Some Option和None Option和模式匹配 在java 8中,为了避免NullPointerException,引入了Option,在Scala中也有同样的用法. ...
- c语言-----劫持自己02
在上一节 c语言-----劫持原理01 已经叙述了劫持原理,下边正式进入劫持实战 1. 需要实现的功能 在c语言中 system("notepad") 可以打开一个记事本 syst ...
- nginx日志、nginx日志切割、静态文件不记录日志和过期时间
2019独角兽企业重金招聘Python工程师标准>>> 12.10 Nginx访问日志 日志格式 vim /usr/local/nginx/conf/nginx.conf //搜索l ...