我需要在 Net Core 6 应用程序的服务层中访问
ClaimsPrincipal
。
我总是可以只是
builder.Services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
在Startup.cs
并走我快乐的路,但这是一个禁忌。使得测试变得困难,更重要的是,这是“泄漏”抽象的一个很好的例子。
所以,现在我拥有的是以下内容
public class ClaimsProvider : IClaimsProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ClaimsProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public ClaimsPrincipal? GetClaimsPrincipal()
{
return _httpContextAccessor.HttpContext?.User;
}
}
public interface IClaimsProvider
{
ClaimsPrincipal? GetClaimsPrincipal();
}
在我的
Startup.cs
AddScoped()
中,它接受 IHttpContextAccessor 并返回 IClaimsProvider。然后我简单地针对 IClaimsProvider 构建所有服务builder.Services.AddScoped<IClaimsProvider>(provider =>
{
var httpContextAccessor = provider.GetRequiredService<IHttpContextAccessor>();
return new ClaimsProvider(httpContextAccessor);
});
以及我将其作为依赖项注入的服务的常用路径
private readonly IClaimsProvider _claimsProvider;
public SomeService(
IWebHostEnvironment hostingEnvironment,
IMapper mapper, IClaimsProvider claimsProvider, ...)
{
_hostingEnvironment = hostingEnvironment ??
throw new ArgumentNullException(nameof(hostingEnvironment));
_mapper = mapper ??
throw new ArgumentNullException(nameof(mapper));
_claimsProvider = claimsProvider;
}
public void SomeMethod()
{
var u = _claimsProvider.GetClaimsPrincipal();
foreach (var claim in u.Claims)
{
Console.WriteLine($"{claim.Type} : {claim.Value}");
}
}
我的问题是上面的方法可以吗?可能还有其他方法比上面显示的方法更好吗?
IHttpContextAsccessor
),我建议使用
适配器模式。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddHttpContextAccessor();
services.AddScoped<IClaimsProvider, HttpContextClaimsProvider>();
}
public IClaimsProvider
{
public ClaimsPrinciple ClaimsPrinciple { get; }
}
// Adapter
public HttpContextClaimsProvider : IClaimsProvider
{
public HttpContextClaimsProvider(IHttpContextAccessor httpContext)
{
ClaimsProvider = httpContext?.User?.Principle as ClaimsPrinciple;
}
public ClaimsPrinciple ClaimsPrinciple { get; private set; }
}
public class YourService : IYourService
{
private readonly IClaimsProvider _claimsProvider;
public YourService(IClaimsProvider claimsProvider)
{
_claimsProvider= claimsProvider;
}
}
FooRequest
。这是一个 POCO 对象,其中的属性是使用相应的属性从模型绑定器填充的:
public class FooRequest : RequestBase
{
[FromRoute]
public int Id { get; set; }
[FromQuery]
public DateTime? Start { get; set; }
[FromBody]
public SomeComplexObject Configuration { get; set; }
}
此外,我们使用后缀
WithUser
创建了一个派生类,该类具有 ClaimsPrincipal 作为附加属性:
public class FooRequestWithUser : FooRequest, IRequest<FooResponse>
{
public ClaimsPrincipal User { get; set; }
}
在下一步中,我们创建了一个帮助程序类,它提供了一个可以接收请求实例、声明主体和类型 T 的帮助程序方法:
public class RequestBase
{
public T Of<T>(ClaimsPrincipal user) where T: class, new()
{
// Check if T has base of own type
// Create instance and iterate all props to get value
// from this and and set value in instance.
// Additionally use reflection to set user property.
}
}
当我们的普通请求类派生自此类时,我们可以在控制器中调用它,并创建一个包含用户作为附加属性的模型,并使用 MediatR 将其转发到我们的服务中:
public IActionResult DoFoo(FooRequest request)
{
var requestWithUser = request.Of<FooRequestWithUser>(User);
var result = mediator.Send(requestWithUser);
return Ok(result);
}
通过这种方法,声明主体绑定到服务所使用的请求,而不是它必须额外接收的东西。它还清楚地表明,该请求必须以某种方式进行身份验证,并且服务应该检查某些潜在的权限或类似的权限。
另一种方法是使用 ASP.NET Core 中内置的依赖注入直接将 ClaimsPrincipal 注入到服务中,而不需要单独的 IClaimsProvider 接口。
您可以通过在 Startup 类的 ConfigureServices 方法中将 ClaimsPrincipal 注册为服务来完成此操作。
IHttpContextAccessor
的依赖,那么您的
IClaimsProvider
抽象是有意义的。如果你的问题是如何在服务层可靠地获取ClaimsPrincipal
,那么我的建议是:不要使用依赖注入,而是直接在方法参数中“注入”
ClaimsPrincipal
,如下所示:public class SomeService
{
public void DoSomething(ClaimsPrincipal user) { }
}
.NET 没有提供获取环境
ClaimPrincipal
的统一规范或约定。虽然
IHttpContextAccessor
在绝大多数情况下都是有效的,但在某些情况下它可能根本不是 http 请求,而且肯定不存在 HttpContext
。例如,在 SignalR 连接中,不保证 IHttpContextAccessor
在所有情况下都能工作。因为在某些情况下ClaimPrincipal
根本无法使用依赖注入框架获得,所以最可靠、最简单的方法是直接在参数中接收这个变量。