Подтвердить что ты не робот

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.

Любые идеи?

4b9b3361

Ответ 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 здесь), и это выглядело так, потому что в точке впрыска он не был установлен трубопроводом Овина.

Я преодолел это ленивым введением Принципала. В любом случае я собрал действительно базовое приложение (которое связано с выше), чтобы продемонстрировать проблему, но это может помочь вам, поскольку оно показывает, что Принципал установлен в атрибуте и использует аутентификацию на предъявителя.