正确登录WebApi和OAuth的过程

时间:2015-10-06 11:18:59

标签: c# asp.net-web-api oauth-2.0

我最近开始深入研究WebApi作为从中开始新的C#Web项目的基础。我已经看过VS2013 Update 4提供的基本模板,特别是MVC(尝试将方法移植到WebApi)以及基本的WebApi模板。

通过我设置这个框架的努力(基本上应该提供身份验证和授权规则),我已经习惯了以下来源作为指导:

使用这些来源,我将基本的WebApi框架拼凑在一起,该框架使用OAuth和JWT作为承载令牌提供程序。我还用一个使用Telerik ORM而不是Entity Framework的库替换了原生的AspNet Identity库(因为我们需要映射到使用元数据进行结构定义的自定义表,但是现在这并不重要。)

但是,以上所有来源都没有描述如何使用WebApi登录,而我所做的其他搜索也没有产生结果。

使用使用AspNet Identity的基本MVC模板(使用个人用户帐户和Cookie身份验证),它包含一个非常强大的登录过程,可以记录用户,可以锁定用户,或者可以检查帐户验证是否是必需的,这都是由登录管理员提供的。

基本的WebApi模板(具有个人用户帐户和Cookie身份验证)使用AuthAuthorizationServerOptions,该设置用于检查是否存在具有提供的凭据的用户,生成用户身份以获取ClaimsIdenity,生成身份验证票证,验证票证,然后调用context.Request.Context.Authntication.SignIn(param type ClaimsIdentity)。这个函数做了什么,我不确定,因为它似乎没有像使用SigninManager的MVC模板那样应用它。

我想知道的是,使用WebApi记录某个人的正确程序是什么?承载令牌是否仅仅是登录过程中的授权,或者是否应该完成登录(使用IAuthenticationManager或SigninManager),并且基于登录的结果,如果成功,则只应该使用令牌请问?

下面我提供了我的Startup.cs版本和OAuthProvider。

Startup.cs:

[assembly: OwinStartup(typeof(TelerikAndWebAPI.Startup))]
namespace TelerikAndWebAPI
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration config = new HttpConfiguration();

            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
            app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);

            ConfigureOAuthTokenGeneration(app);
            ConfigureOAuthTokenConsumption(app);

            WebApiConfig.Register(config);
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            app.UseWebApi(config);
        }

        //Uses JWT bearer tokens
        public void ConfigureOAuthTokenGeneration(IAppBuilder app)
        {
            bool debug = false;
            if (System.Diagnostics.Debugger.IsAttached) {
                debug = true;
            }
            else{
                debug = false;
            }

            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = debug,
                TokenEndpointPath = new PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = new CustomOAuthProvider(),
                AccessTokenFormat = new CustomJwtFormat(HttpContext.Current.Request.Url.AbsoluteUri.Replace(HttpContext.Current.Request.Url.PathAndQuery, "/"))
            };

            app.UseOAuthAuthorizationServer(OAuthServerOptions);
        }

        //Consume the JWT bearer tokens
        private void ConfigureOAuthTokenConsumption(IAppBuilder app)
        {

            var issuer = HttpContext.Current.Request.Url.AbsoluteUri.Replace(HttpContext.Current.Request.Url.PathAndQuery, "/");
            string audienceId = JwtCodeProvider.audienceID;
            byte[] audienceSecret = TextEncodings.Base64Url.Decode(JwtCodeProvider.audienceSecret);
            // Api controllers with an [Authorize] attribute will be validated with JWT
            app.UseJwtBearerAuthentication(
                new JwtBearerAuthenticationOptions
                {
                    AuthenticationMode = AuthenticationMode.Active,
                    AllowedAudiences = new[] { audienceId },
                    IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
                    {
                        new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
                    }
                });
        }
    }
}

OAuthProvder:

    public class CustomOAuthProvider : OAuthAuthorizationServerProvider
    {
        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated();
            return Task.FromResult<object>(null);
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var allowedOrigin = "*";

            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });

            using (AccountController accounts = new AccountController())
            {
                //Should I plug in a signin manager or authentication manager here and then based on whether the
                //sign in is successful, then do the bearer token generation?
                IdentityUser user = await accounts.FindUser(context.UserName, context.Password);

                if (user == null)
                {
                    context.SetError("invalid_grant", "The user name or password is incorrect.");
                    return;
                }

                ClaimsIdentity oAuthIdentity = await accounts.GenerateUserIdentity(user, "JWT");

                var ticket = new AuthenticationTicket(oAuthIdentity, null);

                context.Validated(ticket);
            }          

        }
    }

为了清楚起见,ClaimsIdentity oAuthIdentity = await accounts.GenerateUserIdentity(user, "JWT");的来源:

    public async Task<ClaimsIdentity> GenerateUserIdentity(IdentityUser user,string authenticationType)
    {
        ClaimsIdentity oAuthIdentity = await UserManager.CreateIdentityAsync(user, authenticationType);

        return oAuthIdentity;
    }

我在OAuthRpovider类中添加了注释,我认为我应该插入一个SignInManager方法,但是,我不确定。如果有人能说明最佳做法是什么,我将不胜感激。

就像免责声明一样,我班级中的很多代码都与我提供的源代码一样。一旦我完成了概念证明,我将改变这一点。

1 个答案:

答案 0 :(得分:3)

VS 2013模板在MVC应用程序的身份验证(在浏览器中呈现并使用传统的身份验证cookie的应用程序)和依赖于OAuth 2.0承载令牌的Web Api身份验证之间混合的问题,无论令牌格式如何(JWT或默认值)格式)。

在REST API中,世界身份验证应该保持简单,因为您在消费者(移动应用程序,JS,服务器端服务,MVC等)上拥有广泛的范围,并且它应该是无状态的(对每个请求进行身份验证)所以为此,您需要在开头提供OAuth访问令牌,方法是提供在服务器上验证它们的用户名/密码,然后发出此令牌并将其返回给使用者。此令牌应具有到期日期/时间(几小时,几天,几个月)。

获得访问令牌后,您需要使用承载方案将其与Authorization标头中的每个请求一起发送。然后服务器将负责验证它并允许/拒绝呼叫。 JWT访问令牌是自包含令牌,因此您可以在其中放置声明(有关登录用户的信息或代表用户执行的客户端应用程序),并且您可以在每次请求时在服务器上再次读取这些声明而不会触及DB。

现在,在您的情况下不需要SignIn Manager,如果您想创建一个将在MVC应用程序中使用的安全身份验证cookie,它将非常有用。

如果您的应用程序包含MVC和Web API,那么您需要SignIn Manager。

我希望我的回答能澄清您的疑虑。