Это беспокоило меня уже около суток. Я прочитал различные статьи и попробовал образцы и предложения, чтобы решить это. Я нашел решение, но не думаю, что оно «правильное». Я ищу совет о том, чего мне не хватает, и правильно все делаю.
Я экспериментирую с различными типами методов аутентификации в простом ASP. NET MVC приложении с использованием простого в база данных памяти. Одним из таких методов является аутентификация через OWIN с использованием простой формы имени пользователя / пароля. Все отлично работает, когда я проверяю это с почтальоном. С почтальоном я могу напрямую позвонить на конечную точку /auth
и получить токен на предъявителя. Содержание тела, которое я использую, составляет grant_type=password&username=administrator&roleName=Admin&permissionLevel=2
. roleName
и permissionLevel
- это функциональность, определяемая приложением c. Когда я добавляю заголовок Authorization с выданным токеном Bearer в запрос GET с помощью Postman и вызываю метод действия, украшенный атрибутом [Authorize]
, все прекрасно работает, и я успешно аутентифицируюсь с заголовком Authorization, содержащим токен Bearer.
Проблема возникает, когда я пытаюсь сделать то же самое из моего метода действия public async Task<ActionResult> OAuth(AppModels.User logonUser)
, который я использую для поиска пользователя в базе данных и выполнения перенаправления / вызова того же представления оттуда после вызова /auth
конечная точка.
Я могу вызвать конечную точку токена, и переопределения ValidateClientAuthentication
и GrantResourceOwnerCredentials
вызываются, как и ожидалось, создается объект IIdentity
и возвращается токен.
Но когда я пытаюсь выполнить перенаправление на другое действие или вызвать мой View в конце этого метода действия, ClaimsPrincipal
и содержащиеся в нем утверждения, похоже, теряются, и я получаю HTTP 401 неавторизованным. Это потому, что мой запрос не содержит правильный заголовок запроса авторизации. Быстрое исправление, которое я обнаружил, было создание ClaimsPrincipal
в переопределении GrantResourceOwnerCredentials
, сохраните его в свойство stati c в Startup.cs и добавьте этот принципал в конвейер позже, используя метод Invoke
. Тогда и только тогда я смогу заставить аутентификацию работать.
Внедрение заголовка запроса авторизации не работает в методе Invoke
, так как я думаю, что это нужно делать в коде клиента. Заголовок запроса должен быть уже при вызове метода Invoke
. Но я не хочу делать это в коде клиента javascript / angular / some-framework. Я также не хочу использовать куки, я хочу использовать чистый заголовок, и почтальон показывает, что это возможно.
Поэтому мне нужно вводить заголовок авторизации, содержащий токен носителя, при каждом запросе, используя ASP. NET MVC только, но я не знаю, как. Я подозреваю, что у меня здесь ситуация с курицей и яйцом, и единственный способ добавить заголовок запроса - добавить клиентский код javascript на мой взгляд, и сделайте это оттуда. Различные примеры, которые я нашел в Интернете, не помогают мне, поскольку все они связаны с использованием Postman или аналогичного инструмента, а не с реальной ситуацией, называющей UseOAuthAuthorizationServer
и UseOAuthBearerAuthentication
. Пожалуйста, совет.
Вот мой код OWIN в Startup.cs
классе:
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = false,
TokenEndpointPath = new PathString("/auth"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
Provider = new AuthorizationServerProvider(),
AuthenticationMode = AuthenticationMode.Active
};
// Token Generation
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions { Provider = new OAuthBearerAuthenticationProvider() });
app.UseOAuthAuthorizationServer(OAuthServerOptions);
//my dirty fix
app.Use<PrincipalInjectionMiddleware>();
HttpConfiguration configuration = new HttpConfiguration();
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(configuration);
}
Это код OAuthAuthorizationServerProvider
класса
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
base.ValidateClientAuthentication(context);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
FormCollection dataSet = context.OwinContext.Environment["Microsoft.Owin.Form#collection"] as FormCollection;
var roleName = dataSet["roleName"];
var permissionLevel = dataSet["permissionLevel"];
var userName = dataSet["userName"];
//TODO:Configure CORS
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
//grant claims here
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
identity.AddClaim(new Claim(ClaimTypes.Role, roleName));
identity.AddClaim(new Claim(ExtendedClaimTypes.PermissionLevel, permissionLevel));
ClaimsPrincipal principal = new ClaimsPrincipal(identity);
//my dirty fix
Startup.issuedPrincipal = principal;
context.OwinContext.Authentication.SignIn(new AuthenticationProperties { IsPersistent = true, AllowRefresh = true }, identity);
context.Validated(identity);
base.GrantResourceOwnerCredentials(context);
}
Это это код для PrincipalInjectionMiddleware
public class PrincipalInjectionMiddleware : OwinMiddleware
{
public PrincipalInjectionMiddleware(OwinMiddleware next) : base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
var principal = Startup.issuedPrincipal;
if(principal != null)
{
context.Authentication.User = Startup.issuedPrincipal;
context.Request.User = Startup.issuedPrincipal;
}
await Next.Invoke(context);
}
}
Вот мой код метода действия после того, как я отправил форму с именем пользователя / паролем:
[HttpPost]
public async Task<ActionResult> OAuth(AppModels.User logonUser)
{
string tokenAuthPath = "mvcsandbox/auth";
ViewData["StatusMessage"] = String.Empty;
using (var dbContext = new SandboxContext())
{
var dbUser = dbContext.Users.Include("UserRole").SingleOrDefault(user => user.Name == logonUser.Name && user.Password == logonUser.Password);
string bodyContent = String.Format("grant_type=password&userName={0}&roleName={1}&permissionLevel={2}&password={3}", dbUser.Name, dbUser.UserRole.Name, ((int)dbUser.UserRole.PermissionLevel).ToString(), dbUser.Password);
if (dbUser != null)
{
await Task.Run(() =>
{
HttpResponseMessage output = _serviceClient.GetServiceClient().PostAsync(tokenAuthPath, new StringContent(bodyContent)).Result;
/*HttpContent content = output.Content;
string formattedContent = content.ReadAsStringAsync().Result;
dynamic contentObject = System.Web.Helpers.Json.Decode(formattedContent);*/
});
return RedirectToAction("Main");
}
else
{
ViewData["StatusMessage"] = "Invalid username/password, login failed.";
return View();
}
}
}