Аутентификация с помощью OAuth 2.0 в ASP.NET Core 2.0
Опубликовано: 4 января 2018 г.
Почти 2 года назад я написал сообщение в блоге о с использованием универсального поставщика OAuth в ASP.NET Core , С тех пор многое изменилось, поэтому я подумал, что сейчас самое время вернуться к этому.
Для этого поста в блоге мы создадим простой веб-сайт, который позволит пользователям входить в систему со своими учетными данными GitHub. Я также продемонстрирую, как мы можем хранить токен доступа, полученный от GitHub, для выполнения вызовов GitHub API с использованием Octokit ,
Поток OAuth
Прежде чем мы начнем, я хотел бы быстро пройти через процесс OAuth 2, чтобы вы могли понять, как все складывается вместе.
Что касается терминологии, я буду ссылаться на потребителей и поставщиков услуг. Потребитель - это приложение, которое будет запрашивать токен OAuth, например, наше приложение ASP.NET Core. Поставщик услуг - это приложение или служба, которая авторизует пользователя и выдает токен.
В этом посте я покажу, как настроить аутентификацию с GitHub, поэтому ради этого поста в блоге подумайте о GitHub как о сервис-провайдере.
Прежде чем что-либо сделать, вам необходимо зарегистрировать приложение у поставщика услуг. Обычно вам нужно указать имя приложения и URI перенаправления . Как только приложение зарегистрировано, поставщик услуг предоставит идентификатор клиента и секрет клиента, которые используются в процессе аутентификации и запроса токена.
Что касается фактического потока OAuth 2, он выглядит следующим образом:
- Потребитель делает запрос конечной точке авторизации поставщика услуг для авторизации пользователя.
- Поставщик услуг аутентифицирует пользователя и запрашивает, разрешать ли Потребителю доступ к его информации.
- Если пользователь авторизует Потребителя, Поставщик услуг перенаправляет обратно на URI перенаправления на веб-сайте Потребителя с временным кодом доступа.
- Потребитель вызывает конечную точку токена на веб-сайте поставщика услуг для обмена кодом на более постоянный токен доступа.
- Поставщик услуг предоставляет токен доступа, который может использоваться для аутентификации последующих запросов к защищенным ресурсам.
Теперь есть разные мелкие нюансы между разными поставщиками услуг, но описанный выше процесс охватывает основы и справедлив для большинства поставщиков услуг.
В приведенном выше разделе я выделил важные части, необходимые для настройки универсального поставщика OAuth, выделенным жирным шрифтом . Это: 1. Идентификатор клиента 2. Секрет клиента 3. URI перенаправления 4. Конечная точка авторизации 5. Конечная точка токена
Идентификатор и секретный ключ клиента получаются при регистрации вашего приложения на веб-сайте поставщика услуг. Некоторые поставщики услуг могут называть их как-то иначе, например, например, Facebook, будут называть их идентификатором приложения и секретом приложения. Конечные точки авторизации и токена обычно можно получить из документации разработчика поставщика услуг.
URI перенаправления - это то, что вы можете указать при настройке аутентификации OAuth на приемнике. Это должно совпадать с тем, что вы указали при создании приложения в сервис-провайдере. Обычно это будет называться либо URL-адресом перенаправления, либо URL- адресом обратного вызова, либо каким-либо другим вариантом.
Используя наш пример GitHub, эту информацию можно найти в разделе OAuth документации API по адресу https://developer.github.com/v3/oauth/
Вы увидите, что они перечисляют конечную точку авторизации как
https://github.com/login/oauth/authorizeи конечная точка токена как
https://github.com/login/oauth/access_tokenКонечная точка авторизации обычно имеет путь / авторизация, / аутентификация, / аутентификация или что-то подобное. Конечная точка Token обычно имеет путь / access_token или / token, так что следите за ними.
Для начала вам нужно зарегистрировать приложение, перейдя в Настройки разработчика GitHub :
Нажмите на кнопку, чтобы Зарегистрировать новое приложение , и заполните информацию для своего приложения. Укажите http: // localhost: 5000 / signin-github в качестве значения поля URL обратного вызова авторизации :
После этого вы можете нажать кнопку Зарегистрировать приложение . Обратите внимание на значения для Client ID и Client Secret , так как они понадобятся вам в ближайшее время при регистрации промежуточного программного обеспечения OAuth.
В Visual Studio выберите «Файл»> «Новый проект» и выберите шаблон основного веб-приложения ASP.NET :
В следующем диалоговом окне выберите шаблон веб-приложения , который создаст для вас приложение Razor Pages. Также убедитесь, что для параметра « Аутентификация» установлено значение « Нет аутентификации» :
Если вы хотите создать приложение MVC, тогда выберите шаблон веб-приложения (Model-View-Controller) . Вы все еще должны быть в состоянии следовать, поскольку важные вещи выполняются в файле запуска, который одинаков для Razor Pages и приложений MVC.
После создания проекта вы можете перейти к его свойствам и на странице « Отладка» убедиться, что для URL-адреса приложения установлено значение http: // localhost: 5000 /. Кроме того, вам нужно изменить регистрацию приложения в GitHub, чтобы использовать тот же порт, что и ваше приложение.
Если вы используете утилиты командной строки, вы можете запустить следующую команду, чтобы создать тот же проект:
новая бритва dotnet -n AspNetCoreGitHubAuthВам нужно будет зарегистрировать сервисы аутентификации Cookie и OAuth, а также добавить промежуточное ПО аутентификации. Прежде всего, обновите метод ConfigureServices вашего класса запуска, чтобы зарегистрировать необходимые службы аутентификации:
public void ConfigureServices (IServiceCollection services) {services.AddMvc (); services. .ClientId = Конфигурация ["GitHub: ClientId"]; options.ClientSecret = Конфигурация ["GitHub: ClientSecret"]; options.CallbackPath = new PathString ("/ signin-github"); options.AuthorizationEndpoint = "https: // github .com / login / oauth / authorize "; options.TokenEndpoint =" https://github.com/login/oauth/access_token "; options.UserInformationEndpoint =" https://api.github.com/user "; параметры. ClaimActions.MapJsonKey (ClaimTypes.NameIdentifier, "id"); options.ClaimActions.MapJsonKey (ClaimTypes.Name, "name"); options.ClaimActions.MapJsonKey ("urn: github: login", "login"); options.ClaimActions .MapJsonKey ("urn: github: url", "html_url"); options.ClaimActions.MapJsonKey ("urn: github: ava") tar "," avatar_url "); options.Events = new OAuthEvents {OnCreatingTicket = async context => {var request = new HttpRequestMessage (HttpMethod.Get, context.Options.UserInformationEndpoint); request.Headers.Accept.Add (new MediaTypeWithQualityHeaderValue ("application / json")); request.Headers.Authorization = new AuthenticationHeaderValue ("Носитель", context.AccessToken); var response = await context.Backchannel.SendAsync (запрос, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted); response.EnsureSuccessStatusCode (); var user = JObject.Parse (ожидайте response.Content.ReadAsStringAsync ()); context.RunClaimActions (пользователей); }}; }); }Вызов AddAuthentication регистрирует службы аутентификации. Он указывает DefaultAuthenticateScheme и DefaultSignInScheme в качестве файлов cookie, а DefaultChallengeScheme в качестве GitHub . Это означает, что когда ASP.NET проверяет, аутентифицирован ли пользователь, он использует обработчик аутентификации cookie.
Когда мы вызываем ChallengeAsync () для входа пользователя в систему, схема аутентификации GitHub (другими словами, обработчик аутентификации OAuth) будет подвергнута сомнению. Наконец, после аутентификации пользователя его аутентификационная информация будет сохранена в файле cookie, поскольку это DefaultSignInScheme.
Если это не имеет смысла для вас, я настоятельно рекомендую вам прочитать Демистифицирована система аутентификации и авторизации ASP.NET Core 2.0 что объясняет все это гораздо более подробно.
Затем мы регистрируем обработчик аутентификации cookie с помощью вызова AddCookie ().
Затем мы регистрируем обработчик аутентификации OAuth, вызывая метод AddOAuth () и устанавливая параметр authenticationScheme как GitHub (то же, что мы указали для DefaultChallengeScheme ранее).
Для обработчика аутентификации OAuth необходимо указать ClientId, ClientSecret и CallbackPath. CallbackPath - это путь, по которому провайдер идентификации будет перезванивать после аутентификации пользователя (т. Е. URL обратного вызова, который мы указали при регистрации нашего приложения в GitHub).
Мы также указываем AuthorizationEndpoint, TokenEndpoint и UserInformationEndpoint. Последний не вызывается самим обработчиком аутентификации OAuth, но вместо этого нам нужно вызывать, чтобы получить больше информации о пользователе (имя, адрес электронной почты и т. Д.)
Это то, что происходит в событии OnCreatingTicket в приведенном выше коде. Мы вызываем UserInformationEndpoint и получаем ответ JSON от GitHub. Этот ответ выглядит следующим образом:
{"login": "octocat", "id": 1, "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https : //api.github.com/users/octocat "," html_url ":" https://github.com/octocat "," follow__url ":" https://api.github.com/users/octocat/followers "," follow_url ":" https://api.github.com/users/octocat/following enj/other_user} "," gists_url ":" https://api.github.com/users/octocat/gistsndom/ gist_id} "," starred_url ":" https://api.github.com/users/octocat/starred‹/owner automotive]/repo} "," subscription_url ":" https://api.github.com/users / octocat / subscription "," organization_url ":" https://api.github.com/users/octocat/orgs "," repos_url ":" https://api.github.com/users/octocat/repos ", "events_url": "https://api.github.com/users/octocat/events‹/privacy}", "receive_events_url": "https://api.github.com/users/octocat/received_events", "type ":" Пользователь "," site_admin ": false," name ":" monalisa octocat "," company ":" GitHub "," blog ":" https: //github.c om / blog "," location ":" San Francisco "," email ":" [email protected] "," hireable ": false," bio ":" Там когда-то было ... "," public_repos ": 2 , "public_gists": 1, "последователи": 20, "следующие": 0, "create_at": "2008-01-14T04: 33: 35Z", "updated_at": "2008-01-14T04: 33: 35Z" }В нашем случае мы хотим использовать поле id для утверждения NameIdentifier, имени для Name, а также некоторых других утверждений, содержащих логин пользователя GitHub и т. Д. Мы информируем об этом обработчик аутентификации OAuth, сначала указав следующие ClaimActions в регистрации обработчика аутентификации. :
options.ClaimActions.MapJsonKey (ClaimTypes.NameIdentifier, "id"); options.ClaimActions.MapJsonKey (ClaimTypes.Name, "name"); options.ClaimActions.MapJsonKey («urn: github: login», «login»); options.ClaimActions.MapJsonKey ("urn: github: url", "html_url"); options.ClaimActions.MapJsonKey ("urn: github: avatar", "avatar_url");И затем впоследствии вызывая RunClaimActions, передавая пользовательский объект, полученный от GitHub. Это обеспечит создание правильных претензий в нашем ClaimsIdentity.
Другая часть, которую нам нужно сделать, это зарегистрировать промежуточное ПО аутентификации. Вы можете сделать это, вызвав UseAuthentication () в вашем методе Configure:
public void Configure (приложение IApplicationBuilder, env IHostingEnvironment) {if (env.IsDevelopment ()) {app.UseDeveloperExceptionPage (); app.UseBrowserLink (); } else {app.UseExceptionHandler ("/ Error"); } app.UseStaticFiles (); app.UseAuthentication (); app.UseMvc (rout => {rout.MapRoute (имя: "default", шаблон: "{controller} / {action = Index} / {id?}");}); }Вы заметили, что конфигурация аутентификации OAuth ссылалась на параметры конфигурации GitHub: ClientId и GitHub: ClientSecret. Вам нужно будет обновить файл appsettings.json, чтобы добавить эти настройки. Укажите значения для вашего собственного приложения GitHub, которое вы зарегистрировали ранее.
{"Logging": {"IncludeScopes": false, "LogLevel": {"Default": "Warning"}}, "GitHub": {"ClientId": "идентификатор вашего клиента", "ClientSecret": "секрет вашего клиента "}}Я храню эти значения непосредственно в appsettings.json только для демонстрационных целей, но это не очень хорошая практика. Пожалуйста, обратитесь к Безопасное хранение секретов приложений при разработке в ASP.NET Core для некоторых лучших способов справиться с этим.
Нам нужен способ вызвать обработчик аутентификации. Для этого я создам обычный MVC Controller с именем AccountController и укажу действие Login, которое вернет ChallengeResult и, следовательно, вызовет обработчик аутентификации OAuth (потому что мы указали GitHub в качестве DefaultChallengeScheme):
[Route ("[controller] / [action]")] открытый класс AccountController: Controller {[HttpGet] public IActionResult Login (string returnUrl = "/") {return Challenge (new AuthenticationProperties () {RedirectUri = returnUrl}); }}Затем мы можем добавить большую кнопку на домашней странице, которая позволяет пользователю входить в систему со своими учетными данными GitHub. Эта кнопка просто вызывает действие Login для класса Controller. Обновите HTML-код для вашего файла /Pages/Index.cshtml следующим образом:
@page @model IndexModel @ {ViewData ["Title"] = "Домашняя страница"; } <div class = "row"> <div class = "col-md-12"> <a asp-action = "Login" asp-controller = "Account" class = "btn btn-default"> Войдите в систему с помощью GitHub </ a> </ div> </ div>Еще одна вещь, которую мы можем сделать, это отобразить информацию о пользователе. Для этого мы можем обновить код для класса IndexModel в /Pages/Index.cshtml.cs следующим образом:
открытый класс IndexModel: PageModel {открытая строка GitHubAvatar {get; задавать ; } публичная строка GitHubLogin {get; задавать ; } публичная строка GitHubName {get; задавать ; } публичная строка GitHubUrl {get; задавать ; } public void OnGet () {if (User.Identity.IsAuthenticated) {GitHubName = User.FindFirst (c => c.Type == ClaimTypes.Name) ?. Value; GitHubLogin = User.FindFirst (c => c.Type == "urn: github: login") ?. Значение; GitHubUrl = User.FindFirst (c => c.Type == "urn: github: url") ?. Value; GitHubAvatar = User.FindFirst (c => c.Type == "urn: github: avatar") ?. Значение; }}}Обратите внимание, что мы извлекаем утверждения, сохраненные для пользователя, и сохраняем значения этих утверждений в свойствах нашего класса IndexModel. Это позволит использовать эти значения на странице Razor.
Вы можете обновить HTML-код для страницы Razor (расположенный в /Pages/Index.cshtml) следующим образом:
@page @model IndexModel @ {ViewData ["Title"] = "Домашняя страница"; } <div class = "row"> <div class = "col-md-12"> @if (! User.Identity.IsAuthenticated) {<a asp-action = "Вход в систему" asp-controller = "Account" class = "btn btn-default"> Войдите в систему с помощью GitHub </ a>} else {<div class = "row"> <div class = "col-md-2"> <img src = "@ Model.GitHubAvatar" alt = "" class = "img-thumbnail" /> </ div> <div class = "col-md-10"> <h4> @ Model.GitHubName </ h4> <p> <i class = "fab fa-github "> </ i> <a href =" @ Model.GitHubUrl "> @ Model.GitHubLogin </ a> </ p> </ div> </ div>} </ div> </ div>Пробовать
Давайте попробуем это, запустив приложение. Вам будет представлена домашняя страница, на которой отображается кнопка «Вход»:
Нажатие на кнопку перенаправит вас на GitHub, где вас спросят, хотите ли вы Авторизовать приложение для доступа к вашей информации:
Если вы нажмете кнопку « Авторизовать» , вы будете перенаправлены назад к вашему приложению, где вы сможете увидеть информацию профиля пользователя:
Получение репозиториев пользователя
Допустим, мы хотим также отобразить список хранилищ для пользователя на странице. Для этого нам нужно позвонить / пользователь / хранилищу конечная точка GitHub API, передающая access_token, который мы получили от GitHub.
Давайте установим Octokit , которая является оболочкой C # вокруг GitHub API:
Нам нужно будет сохранить токены, полученные от GitHub. Мы можем сделать это, установив свойство SaveTokens при настройке обработчика аутентификации OAuth:
services. .ClientId = Конфигурация ["GitHub: ClientId"]; options.ClientSecret = Конфигурация ["GitHub: ClientSecret"]; options.CallbackPath = new PathString ("/ signin-github"); options.AuthorizationEndpoint = "https: // github .com / login / oauth / authorize "; options.TokenEndpoint =" https://github.com/login/oauth/access_token "; options.UserInformationEndpoint =" https://api.github.com/user "; параметры. SaveTokens = true; // какой-то код опущен для краткостиЗатем в классе IndexModel мы можем получить токен доступа, вызвав метод GetTokenAsync (), а затем использовать его для инициализации класса GitHubClient и получения репозиториев для текущего пользователя. Мы храним репозитории в новом свойстве Repositories, чтобы использовать его на странице Razor.
Также обратите внимание, что, поскольку мы сейчас используем асинхронные методы, я изменил метод OnGet () на OnGetAsync ().
открытый класс IndexModel: PageModel {открытая строка GitHubAvatar {get; задавать ; } публичная строка GitHubLogin {get; задавать ; } публичная строка GitHubName {get; задавать ; } публичная строка GitHubUrl {get; задавать ; } public IReadOnlyList <Repository> Repositories {get; задавать ; } public async Task OnGetAsync () {if (User.Identity.IsAuthenticated) {GitHubName = User.FindFirst (c => c.Type == ClaimTypes.Name) ?. Value; GitHubLogin = User.FindFirst (c => c.Type == "urn: github: login") ?. Значение; GitHubUrl = User.FindFirst (c => c.Type == "urn: github: url") ?. Value; GitHubAvatar = User.FindFirst (c => c.Type == "urn: github: avatar") ?. Значение; string accessToken = await HttpContext.GetTokenAsync ("access_token"); var github = new GitHubClient (новый ProductHeaderValue («AspNetCoreGitHubAuth»), новый InMemoryCredentialStore (новые учетные данные (accessToken))); Репозитории = ожидайте github.Repository.GetAllForCurrent (); }}}Также обновите страницу Razor для отображения списка репозиториев:
@page @model IndexModel @ {ViewData ["Title"] = "Домашняя страница"; } <div class = "row"> <div class = "col-md-12"> @if (! User.Identity.IsAuthenticated) {<a asp-action = "Вход в систему" asp-controller = "Account" class = "btn btn-default"> Войдите в систему с помощью GitHub </ a>} else {<div class = "row"> <div class = "col-md-2"> <img src = "@ Model.GitHubAvatar" alt = "" class = "img-thumbnail" /> </ div> <div class = "col-md-10"> <h4> @ Model.GitHubName </ h4> <p> <i class = "fab fa-github "> </ i> <a href =" @ Model.GitHubUrl "> @ Model.GitHubLogin </ a> </ p> <h4> Хранилища </ h4> <ul> @foreach (var repo в Model.Repositories) {<li> <a href = "@ repo.HtmlUrl"> @ repo.FullName </ a> </ li>} </ ul> </ div> </ div>} </ div> </ div>Теперь, когда мы запустим приложение и снова войдем в систему, вы увидите, что отображаются репозитории пользователя:
Исходный код
Исходный код для этого образца доступен на GitHub по адресу https://github.com/jerriepelser-blog/AspnetCoreGitHubAuth
Если вы нашли ценность в этом сообщении в блоге и хотите вернуть услугу, вы можете Купи мне кофе
PS: я публикую еженедельную рассылку для разработчиков ASP.NET под названием ASP.NET Weekly . Если вы хотите получать электронную почту каждую пятницу со всеми лучшими сообщениями в блогах, связанных с ASP.NET за предыдущую неделю, пожалуйста, Подписаться !
Имя: "default", шаблон: "{controller} / {action = Index} / {id?FindFirst (c => c.Type == ClaimTypes.Name) ?
FindFirst (c => c.Type == "urn: github: login") ?
FindFirst (c => c.Type == "urn: github: url") ?
FindFirst (c => c.Type == "urn: github: avatar") ?
FindFirst (c => c.Type == ClaimTypes.Name) ?
FindFirst (c => c.Type == "urn: github: login") ?
FindFirst (c => c.Type == "urn: github: url") ?
FindFirst (c => c.Type == "urn: github: avatar") ?