我有一个.Net Core 2.2 Web应用程序MVC,其中添加了API控制器和SignalR集线器。另一方面,我有一个移动应用程序,它调用中心方法。从应用程序调用集线器之前,我正在通过API调用对用户进行身份验证-返回JWT令牌-并将此令牌用于将来的请求,这样我就可以在集线器方法中使用Context.User.Identity.Name
:
public static async Task<string> GetValidToken(string userName, string password)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(_API_BASE_URI);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
LoginViewModel loginVM = new LoginViewModel() { Email = userName, Password = password, RememberMe = false };
var formContent = Newtonsoft.Json.JsonConvert.SerializeObject(loginVM);
var content = new StringContent(formContent, Encoding.UTF8, "application/json");
HttpResponseMessage responseMessage;
try
{
responseMessage = await client.PostAsync("/api/user/authenticate", content);
var responseJson = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); ;
var jObject = JObject.Parse(responseJson);
_TOKEN = jObject.GetValue("token").ToString();
return _TOKEN;
}catch
[...]
然后使用令牌:
_connection = new HubConnectionBuilder().WithUrl(ApiCommunication._API_BASE_URI + "/network", options =>
{
options.AccessTokenProvider = () => Task.FromResult(token);
}).Build();
到目前为止,一切都很好。它在我的移动应用程序上按预期工作。但是为了使其正常工作,我必须在服务器端(Startup.cs)上设置以下代码:
services.AddAuthentication(options =>
{
options .DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options .DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
...
这使我无法再使用cookie身份验证,因此mvc Web应用程序无法正常运行,因为它无法在请求中获取当前已身份验证的用户。
删除行:
options .DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options .DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
使网络应用正常运行,但不再使移动应用正常运行(由于Context.User.Identity.Name
等于null,集线器调用失败)。
我一直在寻找有关如何处理不同方案(在我的情况下为cookie + jwt)的方法,根据我的理解,这是设计使然无法实现的。
是否有可能使用双重方案的解决方法,或者我丢失了某些东西?
我想也许我应该托管两个单独的项目,并使用一个Cookie身份验证,另一个使用JWT?
谢谢。
答案 0 :(得分:3)
有多种方法可以解决您遇到的问题,但首先让我们看一下为什么它当前不起作用。
DefaultAuthenticateScheme
的含义当您为DefaultAuthenticateScheme
的{{1}}属性设置一个值时,您指示认证中间件尝试针对该特定方案对每个HTTP请求进行认证。我假设您使用ASP.NET Identity进行基于cookie的身份验证,并且在调用AuthenticationOptions
时,它将cookie身份验证方案注册为默认身份验证方案;您可以在source code on GitHub中看到它。
但是,这并不意味着您不能在应用程序中使用任何其他身份验证方案。
如果要使用Cookie或JWT进行身份验证的客户端可以访问应用程序的所有受保护端点,则一种选择是使用授权系统默认策略。当您使用AddIdentity
类的“空”实例时,将使用该特殊策略-作为装饰控制器/动作的属性,或在应用程序级别全局使用AuthorizeAttribute
。
默认策略设置为仅要求经过身份验证的用户,但未定义需要“尝试”哪些身份验证方案以对请求进行身份验证。结果是它依赖于已经执行过的身份验证过程。它说明了您在两种方案中一次只能起作用的情况下遇到的行为。
我们可以通过一些代码来更改默认策略:
new AuthorizeFilter(new AuthorizeAttribute())
如果您遇到这样的情况,即您要求某些终结点只能由经过cookie验证的客户端访问,而另一些需要通过JWT进行访问的客户端,则可以利用授权策略。
它们的工作原理与默认策略完全相同,希望您能在适用的端点基础上进行选择。您可以像这样添加策略:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("<your-cookie-authentication-scheme", "your-jwt-authentication-scheme")
.Build();
})
然后可以通过用services.AddAuthorization(options =>
{
options.AddPolicy("Cookies", new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("<your-cookie-authentication-scheme")
.Build());
options.AddPolicy("JWT", new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("<your-jwt-authentication-scheme")
.Build());
})
装饰它们来在适当的端点中引用这些策略。附带说明一下,如果您的策略之间唯一的区别是身份验证方案,则无需创建策略即可获得相同的结果,并且可以在[Authorize(Policy = "<policy-name>")]
属性中使用{{1 }}属性。
当您有更复杂的规则时,例如特定声明需要此特定值的政策非常有用。
希望这对您有所帮助,让我知道您的情况!