Owin Bearer Token Authentication + Авторизованный контроллер
Я пытаюсь сделать аутентификацию с токенами-носителями и owin.
Я могу выдавать токен с использованием типа гранта password
и переопределять GrantResourceOwnerCredentials
в AuthorizationServerProvider.cs.
Но я не могу связаться с методом контроллера с атрибутом Authorize
.
Здесь мой код:
Startup.cs
public class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
// normal
public Startup() : this(false) { }
// testing
public Startup(bool isDev)
{
// add settings
Settings.Configure(isDev);
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new AuthorizationServerProvider()
};
}
public void Configuration(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.CreatePerOwinContext<LoanManager>(BaseManager.Create);
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
// token generation
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AuthenticationType = "Bearer",
AuthenticationMode = AuthenticationMode.Active
});
}
}
AuthorizationServerProvider.cs
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
// enable CORS for all hosts, headers and methods
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
config.Routes.MapHttpRoute(
name: "optional params",
routeTemplate: "api/{controller}"
);
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// stop cookie auth
config.SuppressDefaultHostAuthentication();
// add token bearer auth
config.Filters.Add(new MyAuthenticationFilter());
//config.Filters.Add(new HostAuthenticationFilter(Startup.OAuthOptions.AuthenticationType));
config.Filters.Add(new ValidateModelAttribute());
if (Settings.IsDev == false)
{
config.Filters.Add(new AuthorizeAttribute());
}
// make properties on model camelCased
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
}
MyAuthenticationFilter.cs Пользовательский фильтр, используемый для целей отладки
public class MyAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter
{
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
if (context.Principal != null && context.Principal.Identity.IsAuthenticated)
{
}
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
}
Если я отлаживаю AuthenticateAsync
в MyAuthenticationFilter.cs, я вижу заголовок в запросе:
Authorization: Bearer AQAAANCMnd8BFdERjHoAwE_Cl...
Но претензии Identity пусты, а context.Principal.Identity.IsAuthenticated
- false.
Любые идеи?
Ответы
Ответ 1
Я искал одно и то же решение, я потратил неделю или около того на это, и я оставил его. Сегодня я начал искать снова, я нашел ваши вопросы, и я надеялся найти ответ.
Таким образом, я потратил целый день, ничего не делая, кроме попыток всех возможных решений, слияния предложений друг с другом, я нашел какое-то решение, но они были длинными обходными решениями, чтобы сделать длинную историю короче, вот что я нашел.
Прежде всего, если вам нужно пройти аутентификацию веб-сайта с помощью специального маркера поставщика удостоверений сторонних поставщиков, вам необходимо, чтобы они оба использовали один и тот же файл machineKey, или вам нужно иметь их на одном сервере.
Вам нужно добавить machineKey в раздел system.web
следующим образом:
Web.Config
<system.web>
<authentication mode="None" />
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
<machineKey validationKey="*****" decryptionKey="***" validation="SHA1" decryption="AES" />
</system.web>
Вот ссылка на создать новый файл machineKey:
Теперь вам нужно перейти в файл Startup.Auth.cs, где вы можете найти частичный класс Startup.cs, вам нужно определить OAuthBearerOptions
Startup.Auth.cs
public partial class Startup
{
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
...
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
...
}
}
Замените действие входа в AccountController следующим образом:
AccountController.cs
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
/*This will depend totally on how you will get access to the identity provider and get your token, this is just a sample of how it would be done*/
/*Get Access Token Start*/
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://youridentityproviderbaseurl");
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("UserName", model.Email));
postData.Add(new KeyValuePair<string, string>("Password", model.Password));
HttpContent content = new FormUrlEncodedContent(postData);
HttpResponseMessage response = await httpClient.PostAsync("yourloginapi", content);
response.EnsureSuccessStatusCode();
string AccessToken = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync());
/*Get Access Token End*/
If(!string.IsNullOrEmpty(AccessToken))
{
var ticket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(AccessToken);
var id = new ClaimsIdentity(ticket.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, id);
return RedirectToLocal(returnUrl);
}
ModelState.AddModelError("Error", "Invalid Authentication");
return View();
}
Последнее, что вам нужно сделать, это поместить эту строку кода в файл Global.asax.cs, чтобы избежать исключений Anti Forgery:
Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
…
}
}
Надеюсь, это сработает для вас.
Ответ 2
Через год, когда это было опубликовано, я тоже испытал ту же проблему.
![введите описание изображения здесь]()
Как вы можете видеть, мой токен-носитель распознается в заголовках запроса, но моя личность еще не аутентифицирована.
Чтобы исправить, короткий ответ заключается в том, чтобы настроить ваше промежуточное ПО OAuth до того, как вы настроите промежуточное ПО WebApi (HttpConfiguration).
Ответ 3
Хорошо, я работал над этим некоторое время, и я наконец понял, что не так, и теперь он работает.
Похоже, что ваш код включения Cors в методе GrantResourceOwnerCredentials каким-то образом перекрывает заголовок из параметра. Таким образом, поставив свою первую строку прямо ниже вашей текущей третьей, у вас будет проблема:
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
До сих пор я не углублялся, чтобы понять, почему это так, но я считаю, что добавив новую запись заголовка, прежде чем пользовательский интерфейс каким-то образом искажает данные, отправляемые методом post на клиенте, в моем case, ресурс angular, который будет выглядеть следующим образом:
function userAccount($resource, appSettings) {
return {
registration: $resource(appSettings.serverPath + "/api/Account/Register", null,
{
'registerUser' : { method : 'POST'}
}
),
login : $resource(appSettings.serverPath + "/Token", null,
{
'loginUser': {
method: 'POST',
headers: {
'Content-Type' : 'application/x-www-form-urlencoded'
},
transformRequest: function (data, headersGetter) {
var str = [];
for (var d in data) {
str.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
}
return str.join("&");
}
}
}
)
}
}
Ответ 4
Я не уверен, что это помогает, но у меня возникла проблема с возвратом IsAuthenticated во время использования инъекции зависимостей (см. вопрос SO здесь), и это выглядело так, потому что в точке впрыска он не был установлен трубопроводом Овина.
Я преодолел это ленивым введением Принципала. В любом случае я собрал действительно базовое приложение (которое связано с выше), чтобы продемонстрировать проблему, но это может помочь вам, поскольку оно показывает, что Принципал установлен в атрибуте и использует аутентификацию на предъявителя.