#asp.net-core #authentication #asp.net-core-mvc #.net-5 #challenge-response
Вопрос:
У меня есть проект в .NET5 MVC, в котором реализована аутентификация Twitch с использованием AspNet.Безопасность.OAuth.Twitch. Я все настроил, и все работает нормально, но я хочу добавить возможность связать дополнительную учетную запись с другими поставщиками, такими как Twitter. Я попытался добавить аутентификацию в Twitter с помощью Microsoft.AspNetCore.Идентификация.Twitter. Также все настроил.
Но когда я вхожу в систему с помощью Twitter, моя текущая сессия теряется, и все претензии Twitch были удалены и заменены претензиями Twitter. Я полагаю, что это ожидаемое поведение, но я не знаю, могу ли я сохранить эти утверждения или восстановить только утверждения Twitter без сохранения в удостоверении пользователя (например, хранить в базе данных). Моя главная цель-использовать аутентификацию Twitch в качестве единственного способа входа в приложение, но при этом иметь возможность связывать учетные записи от других поставщиков.
У меня в Startup.cs
обоих моих провайдерах добавлены (и в конечном итоге, возможно, когда-нибудь в будущем добавятся другие)
public void ConfigureServices(IServiceCollection services)
{
// more stuff ...
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddTwitch(options =>
{
options.ClientId = Configuration["Twitch-ClientId"];
options.ClientSecret = Configuration["Twitch-ClientSecret"];
})
.AddTwitter(options =>
{
options.ConsumerKey = Configuration["Twitter-ConsumerKey"];
options.ConsumerSecret = Configuration["Twitter-ConsumerSecret"];
});
}
По-моему AuthController.cs
, у меня есть соответствующие методы с Вызовом.
// Default Login using Twitch
[HttpGet("~/signin")]
public IActionResult Login() => RedirectToAction("Login", "Auth", new { provider = "Twitch" });
[HttpPost("~/signin")]
public IActionResult Login([FromForm] string provider)
{
string redirect_uri = Url.Action("Index", "Home");
return Challenge(new AuthenticationProperties() { RedirectUri = redirect_uri }, provider);
}
Я не знаю Challenge
, можно ли изменить или настроить, чтобы разрешить такое поведение. Я не вижу в классе AuthenticationProperties никакого свойства, которое можно было бы использовать. Сначала я попытался создать другой контроллер/действие для дополнительных поставщиков, но результаты были теми же.
Любая помощь будет оценена по достоинству.
Комментарии:
1. «когда я вхожу в систему с помощью Twitter, моя текущая сессия теряется, и все претензии Twitch были удалены и заменены претензиями Twitter». Исчезает ли файл cookie Twitch
__TwitchState
?2. @abdusco, то же самое происходит и с файлами cookie…
3. Я написал сообщение в блоге об этой проблеме: abdus.dev/сообщения/aspnetcore-несколько сеансов
Ответ №1:
Пока файлы cookie сеанса пользователя действительны, вы можете аутентифицировать его с помощью нескольких схем аутентификации и получить доступ к этим утверждениям в любое время.
Но когда я вхожу в систему с помощью Twitter, моя текущая сессия теряется, и все претензии Twitch были удалены и заменены претензиями Twitter.
Это происходит потому, что вы пытаетесь использовать Cookie
схему для хранения файлов cookie сеанса как для Twitter, так и для Twitch. Когда вы входите в систему с одним из них, он перезаписывает другой.
Чтобы решить эту проблему, вам необходимо добавить отдельные файлы cookie для каждого отдельного варианта входа в систему.
services.AddAuthentication()
.AddCookie("GoogleSession")
.AddCookie("GithubSession")
.AddGoogle(
options => {
// set the app credentials
Configuration.GetSection("Google").Bind(options);
// save session to this cookie
options.SignInScheme = "GoogleSession";
})
.AddGitHub(
options => {
// set the app credentials
Configuration.GetSection("Github").Bind(options);
// save session to this cookie
options.SignInScheme = "GithubSession";
});
Затем бросьте вызов, чтобы заставить пользователя войти в систему:
[AllowAnonymous]
[HttpGet("login-google")]
public ActionResult LoginGoogle()
{
return Challenge(
new AuthenticationProperties
{
RedirectUri = Url.Action("WhoAmI"),
}, GoogleDefaults.AuthenticationScheme
);
}
[AllowAnonymous]
[HttpGet("login-github")]
public ActionResult LoginGithub()
{
return Challenge(
new AuthenticationProperties
{
RedirectUri = Url.Action("WhoAmI"),
}, GitHubAuthenticationDefaults.AuthenticationScheme
);
}
Затем в любое время вы можете аутентифицировать пользователя, чтобы прочитать и проанализировать файлы cookie для доступа к утверждениям:
[AllowAnonymous]
[HttpGet("me")]
public async Task<ActionResult> WhoAmI()
{
var googleResult = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme);
if (googleResult.Succeeded)
{
var googlePrincipal = googleResult.Principal;
// ... use google claims
User.AddIdentity((ClaimsIdentity)googlePrincipal.Identity);
}
var githubResult = await HttpContext.AuthenticateAsync(GitHubAuthenticationDefaults.AuthenticationScheme);
if (githubResult.Succeeded)
{
var githubPrincipal = githubResult.Principal;
// ... use google claims
User.AddIdentity((ClaimsIdentity)githubPrincipal.Identity);
}
return Ok(
User.Identities.Select(
id => new
{
id.AuthenticationType,
Claims = id.Claims.Select(c => new { c.Type, c.Value })
}
)
.ToList()
);
Теперь , когда я посещаю /me
, я получаю список всех претензий со всей сессии:
[
{
"authenticationType": null,
"claims": []
},
{
"authenticationType": "Google",
"claims": [
{
"type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
"value": "123131231231312123123123"
},
{
"type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"value": "My Fullname"
},
{
"type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
"value": "MyName"
},
{
"type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
"value": "MyLastname"
},
{
"type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"value": "my@gmail.com"
}
]
},
{
"authenticationType": "GitHub",
"claims": [
{
"type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
"value": "1313123123"
},
{
"type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"value": "abdusco"
},
{
"type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"value": "my@email.com"
},
{
"type": "urn:github:name",
"value": "my name"
},
{
"type": "urn:github:url",
"value": "https://api.github.com/users/abdusco"
}
]
}
]
Немного утомительно вручную аутентифицировать пользователя с помощью нескольких схем аутентификации. Мы можем позволить ASP.NET Ядро делает это за нас.
Определите политику авторизации, которая принимает несколько схем проверки подлинности.
services.AddAuthorization(
options => options.DefaultPolicy = new AuthorizationPolicyBuilder(
GoogleDefaults.AuthenticationScheme,
GitHubAuthenticationDefaults.AuthenticationScheme
).RequireAuthenticatedUser()
.Build()
);
Теперь, когда вы оформляете действие [Authorize]
(и указываете имя политики, если это необходимо), HttpContext.User
оно будет содержать как удостоверения, так и утверждения из всех сеансов.
[Authorize]
[HttpGet("me")]
public async Task<ActionResult> WhoAmI()
{
return Ok(
// user has claims from all sessions
User.Identities.Select(
id => new
{
id.AuthenticationType,
Claims = id.Claims.Select(c => new { c.Type, c.Value })
}
)
.ToList()
);
}
Это имеет тот же вывод, что и раньше, но без шаблона.
Комментарии:
1. Спасибо @abdusco. Я прочитал ваш пост в блоге и теперь понимаю, как это работает. Я создал два файла cookie, один для основного сеанса, а другой для всех дополнительных провайдеров, потому что я хочу, чтобы претензии сохранялись в базе данных только один раз, а затем сохранялись в неизменном виде.