webentwicklung-frage-antwort-db.com.de

Hinzufügen einer zusätzlichen Logik zur Trägerautorisierung

Ich versuche, die OWIN-Träger-Token-Autorisierung zu implementieren und basiert auf diesem Artikel . Es gibt jedoch eine zusätzliche Information, die ich als Trägermarke benötige, die ich nicht umsetzen kann.

In meiner Anwendung muss ich aus dem Träger-Token Benutzerinformationen ableiten (z. B. userid). Dies ist wichtig, weil ich nicht will, dass ein berechtigter Benutzer als anderer Benutzer fungiert. Ist das machbar? Ist es überhaupt der richtige Ansatz? Wenn die Benutzer-ID eine Guid ist, wäre dies einfach. In diesem Fall ist es eine ganze Zahl. Ein autorisierter Benutzer kann sich möglicherweise nur durch Erraten/Brute Force als unpersönlich ausgeben, was inakzeptabel ist. 

Betrachten Sie diesen Code:

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);
    }
}

Ich würde denken, dass es möglich ist, die Autorisierung/Authentifizierung zu überschreiben, um dem zu entsprechen, was ich brauche.

16
Echiban

Es scheint, dass etwas in Ihrem Code fehlt.
Sie überprüfen Ihren Kunden nicht.

Sie sollten ValidateClientAuthentication implementieren und die Anmeldeinformationen Ihres Clients dort überprüfen. 

Das ist was ich mache:

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();
        }
  }

Einen guten Artikel mit vielen Details finden Sie hier .
Eine noch bessere Erklärung finden Sie in dieser blog -Reihe. 

UPDATE:

Ich habe ein wenig gegraben und webstuff ist richtig.

Um errorDescription an den Client zu übergeben, müssen wir Ablehnen, bevor wir den Fehler mit SetError setzen:

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

oder wir können es in der Beschreibung durch ein serialisiertes Json-Objekt erweitern: 

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

Mit einem javascript/jQuery-Client könnten wir die Textantwort deserialisieren und die erweiterte Nachricht lesen:

$.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);
            }
    }
});
18
LeftyX

Wenn Sie eine benutzerdefinierte Fehlermeldung festlegen möchten, müssen Sie die Reihenfolge von context.Rejected und context.SetError vertauschen.

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

Wenn Sie context.Rejected hinter context.SetError platzieren, wird die Eigenschaft context.HasError auf false zurückgesetzt. Daher lautet die korrekte Verwendung:

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

Um die Antwort von LeftyX zu ergänzen, können Sie die Antwort, die an den Client gesendet wird, vollständig steuern, sobald der Kontext abgelehnt wird. Beachten Sie die Code-Kommentare.

Basierend auf der ursprünglichen Antwort von Greg P ​​ , mit einigen Modifikationen

Step1: Erstellen Sie eine Klasse, die als Middleware fungiert.

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: Erstellen Sie die Erweiterungsklasse (kann weggelassen werden). 

Dieser Schritt ist optional und kann geändert werden, um Optionen zu akzeptieren, die an die Middleware übergeben werden können.

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>();
    }
}

Schritt3: Ändern Sie die GrantResourceOwnerCredentials-Methode in Ihrer OAuthAuthorizationServerProvider-Implementierung

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: Verwenden Sie diese Middleware in der Startklasse.

public void Configuration(IAppBuilder app)
{
    app.UseResponseMiddleware();

    //configure the authentication server provider
    ConfigureOAuth(app);

    //rest of your code goes here....
}
0
Giridhar Karnik