【问题标题】:Owin Bearer Token Authentication + Authorize controllerOwin Bearer Token 认证+授权控制器
【发布时间】:2014-09-24 03:51:15
【问题描述】:

我正在尝试使用 Bearer 令牌和 owin 进行身份验证。

我可以使用授权类型 password 并覆盖 AuthorizationServerProvider.cs 中的 GrantResourceOwnerCredentials 来发出令牌。

但我无法使用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();
    }
}

如果我在 MyAuthenticationFilter.cs 中调试 AuthenticateAsync,我会在请求中看到标头:

Authorization: Bearer AQAAANCMnd8BFdERjHoAwE_Cl...

但身份声明为空,context.Principal.Identity.IsAuthenticated 为假。

有什么想法吗?

【问题讨论】:

  • 微软在发布如此丑陋的复杂性时犯了一个错误......我已经堆叠了大约 2 天试图找到相同问题的解决方案......
  • 我也一直在努力寻找解决方案。如果你解决了这个问题,请告诉我。
  • 我们三个人。此 OWIN 实现比 WCF 的第一个版本差。我的天哪——他们能做到这一点有多难??

标签: c# authentication token owin


【解决方案1】:

我不确定这是否有帮助,但我在使用依赖注入时遇到了 IsAuthenticated 返回错误的问题(请参阅 SO 问题 here),它看起来是因为在注入点它还没有被设置Owin 管道。

我通过延迟注入 Principal 来克服它。无论哪种方式,我都组装了一个非常基本的应用程序(在上面链接到)来演示问题,但它可能会对您有所帮助,因为它显示了在属性中设置的 Principal 和承载身份验证的使用。

【讨论】:

    【解决方案2】:

    我一直在寻找相同的解决方案,我花了一周左右的时间就放弃了它。今天又开始搜索,找到了你的问题,希望能找到答案。

    所以我一整天都在尝试所有可能的解决方案,相互合并建议,我找到了一些解决方案,但它们是很长的解决方法,长话短说,这就是我发现的。

    首先,如果您需要使用自定义第三方身份提供者令牌对网站进行身份验证,您需要让它们都使用相同的 machineKey,或者您需要将它们都放在同一台服务器上。

    您需要将 ma​​chineKey 添加到 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>
    

    这是generate a new 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;
    
            …
        }
    }
    

    希望这对你有用。

    【讨论】:

    • 这正是我需要的代码工作正常。太感谢了。当我不那么忙时,我会写一篇文章来解释它是如何工作的。
    【解决方案3】:

    嗯,我已经为此工作了一段时间,我终于弄清楚出了什么问题,现在它正在工作。

    您在 GrantResourceOwnerCredentials 方法上的 Cors 启用代码似乎以某种方式推翻了参数中的标头。因此,通过将第一行放在当前第三行的正下方,您的问题将得到解决:

        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[] { "*" });
    

    到目前为止,我还没有深入了解为什么会这样,但我相信通过在获取 userManager 之前添加新的标头条目会以某种方式破坏客户端上的 post 方法发送的数据,在我的案例,一个像这样的角度资源:

        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】:

      自从这篇文章发布一年以来,我也遇到了同样的问题。

      如您所见,我的不记名令牌在请求标头中被识别,但我的身份仍未通过身份验证。

      要解决此问题,简短的回答是确保在配置 WebApi 中间件 (HttpConfiguration) 之前配置 OAuth 中间件。

      【讨论】:

      • 没关系,我想通了。我不得不深入研究源代码,这是一个冗长的解释。如果有人关心它,我会在这里编辑我的帖子。但简短的回答是,只需在配置 webapi 中间件 (HttpConfiguration) 之前配置 oauth 中间件。
      • @adOran 我将不胜感激有关此的更多信息,我可以看到我的中间件在我的 WebApi 配置之后启动。您更改此顺序的方法是什么?
      • @TheSenator 在你自己的启动中,你需要做的就是在 web api 之前定义你的授权提供者。所以基本上, ((IAppBuilder)app).UseOauthAuthorizationServer; ((IAppBuilder)app).UseWebApi()。之所以在这里顺序很重要,是因为 AuthorizationAttribute 依赖于预先定义的 AuthenticationManager。
      • 我已将我的 api 抽象为类,因此您最终会得到驱动是否需要身份验证的类。 [Authorize] 属性是绝对要求吗?我已尝试尽可能不使用它!
      • 我刚刚给api本身添加了authorize属性,现在框架直接拒绝了我的Authorization头,所以我的jwt一定是格式不正确,放错了地方或者混了一些其他认证机制。不知道我在这里做错了什么!
      猜你喜欢
      • 2017-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-02
      • 2019-01-30
      • 1970-01-01
      • 2015-12-25
      相关资源
      最近更新 更多