首页 文章

为Bearer授权添加额外的逻辑

提问于
浏览
16

我正在尝试实现OWIN承载令牌授权,并基于this article . 但是,有's one additional piece of information I need in bearer token that I don'知道如何实现 .

在我的应用程序中,我需要从持有者令牌中推断出用户信息(比如userid) . 这很重要,因为我不希望授权用户能够充当其他用户 . 这可行吗?它甚至是正确的方法吗?如果用户标识是guid,那么这很简单 . 在这种情况下,它是一个整数 . 授权用户可能只是通过猜测/暴力来冒充他人,这是不可接受的 .

看看这段代码:

public void ConfigureOAuth(IAppBuilder app)
{
    OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
        Provider = new SimpleAuthorizationServerProvider()
    };

    // Token Generation
    app.UseOAuthAuthorizationServer(OAuthServerOptions);
    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}

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

        using (AuthRepository _repo = new AuthRepository())
        {
            IdentityUser user = await _repo.FindUser(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);
    }
}

我认为可以覆盖授权/身份验证以适应我的需要吗?

3 回答

  • 18

    你的代码似乎缺少一些东西 .
    你没有验证你的客户 .

    您应该实现ValidateClientAuthentication并在那里检查您的客户端凭据 .

    这就是我做的:

    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
            string clientId = string.Empty;
            string clientSecret = string.Empty;
    
            if (!context.TryGetBasicCredentials(out clientId, out clientSecret)) 
            {
                context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
                context.Rejected();
                return;
            }
    
            ApplicationDatabaseContext dbContext = context.OwinContext.Get<ApplicationDatabaseContext>();
            ApplicationUserManager userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
    
            if (dbContext == null)
            {
                context.SetError("server_error");
                context.Rejected();
                return;
            }
    
            try
            {
                AppClient client = await dbContext
                    .Clients
                    .FirstOrDefaultAsync(clientEntity => clientEntity.Id == clientId);
    
                if (client != null && userManager.PasswordHasher.VerifyHashedPassword(client.ClientSecretHash, clientSecret) == PasswordVerificationResult.Success)
                {
                    // Client has been verified.
                    context.OwinContext.Set<AppClient>("oauth:client", client);
                    context.Validated(clientId);
                }
                else
                {
                    // Client could not be validated.
                    context.SetError("invalid_client", "Client credentials are invalid.");
                    context.Rejected();
                }
            }
            catch (Exception ex)
            {
                string errorMessage = ex.Message;
                context.SetError("server_error");
                context.Rejected();
            }
      }
    

    一篇很好的文章可以找到here .
    blog系列中可以找到更好的解释 .

    UPDATE

    我做了一些挖掘,webstuff是对的 .

    为了将 errorDescription 传递给客户端,我们需要在使用 SetError 设置错误之前拒绝:

    context.Rejected();
    context.SetError("invalid_client", "The information provided are not valid !");
    return;
    

    或者我们可以在描述中通过序列化的json对象扩展它:

    context.Rejected();
    context.SetError("invalid_client", Newtonsoft.Json.JsonConvert.SerializeObject(new { result = false, message = "The information provided are not valid !" }));
    return;
    

    enter image description here

    使用 javascript/jQuery 客户端,我们可以反序列化文本响应并读取扩展消息:

    $.ajax({
        type: 'POST',
        url: '<myAuthorizationServer>',
        data: { username: 'John', password: 'Smith', grant_type: 'password' },
        dataType: "json",
        contentType: 'application/x-www-form-urlencoded; charset=utf-8',
        xhrFields: {
            withCredentials: true
        },
        headers: {
            'Authorization': 'Basic ' + authorizationBasic
        },  
        error: function (req, status, error) {
                if (req.responseJSON && req.responseJSON.error_description)
                {
                   var error = $.parseJSON(req.responseJSON.error_description);
                        alert(error.message);
                }
        }
    });
    
  • 0

    另外,如果要设置自定义错误消息,则必须交换 context.Rejectedcontext.SetError 的顺序 .

    // Summary:
        //     Marks this context as not validated by the application. IsValidated and HasError
        //     become false as a result of calling.
        public virtual void Rejected();
    

    如果在 context.SetError 之后放置 context.Rejected ,则属性 context.HasError 将重置为 false ,因此使用它的正确方法是:

    // Client could not be validated.
        context.Rejected();
        context.SetError("invalid_client", "Client credentials are invalid.");
    
  • 10

    只是为了添加LeftyX的答案,这里是你可以完全控制上下文被拒绝后发送给客户端的响应 . 注意代码注释 .

    Based on Greg P's original answer,进行了一些修改

    Step1: Create a class which will act as your middleware

    using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, System.Object>,
    System.Threading.Tasks.Task>;
    

    namespace SignOnAPI.Middleware.ResponseMiddleware {

    public class ResponseMiddleware 
    {
        AppFunc _next;
        ResponseMiddlewareOptions _options;
    
        public ResponseMiddleware(AppFunc nex, ResponseMiddlewareOptions options)
        {
            _next = next;
        }
    
        public async Task Invoke(IDictionary<string, object> environment)
        {
            var context = new OwinContext(environment);
    
            await _next(environment);
    
            if (context.Response.StatusCode == 400 && context.Response.Headers.ContainsKey("Change_Status_Code"))
            {
                //read the status code sent in the response
                var headerValues = context.Response.Headers.GetValues("Change_Status_Code");
    
                //replace the original status code with the new one
                context.Response.StatusCode = Convert.ToInt16(headerValues.FirstOrDefault());
    
                //remove the unnecessary header flag
                context.Response.Headers.Remove("Change_Status_Code");
            }
        }
    }
    

    Step2 : Create the extensions class (Can be omitted).

    此步骤是可选的,可以修改为接受可以传递给中间件的选项 .

    public static class ResponseMiddlewareExtensions
    {
        //method name that will be used in the startup class, add additional parameter to accept middleware options if necessary
        public static void UseResponseMiddleware(this IAppBuilder app)
        {
            app.Use<ResponseMiddleware>();
        }
    }
    

    Step3: Modify GrantResourceOwnerCredentials method in your OAuthAuthorizationServerProvider implementation

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
    
            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
    
            if (<logic to validate username and password>)
            {
                //first reject the context, to signify that the client is not valid
                context.Rejected();
    
                //set the error message
                context.SetError("invalid_username_or_password", "Invalid userName or password" );
    
                //add a new key in the header along with the statusCode you'd like to return
                context.Response.Headers.Add("Change_Status_Code", new[] { ((int)HttpStatusCode.Unauthorized).ToString() }); 
                return;
            }
        }
    

    Step4: Use this middleware in the startup class

    public void Configuration(IAppBuilder app)
    {
        app.UseResponseMiddleware();
    
        //configure the authentication server provider
        ConfigureOAuth(app);
    
        //rest of your code goes here....
    }
    

相关问题