web-dev-qa-db-fra.com

API Web / OWIN, SignalR et autorisation

Je développe un prototype d'application AngularJS, API Web, SignalR comme point de départ potentiel pour un nouveau projet dans VS 2013.

À ce stade, j'utilise à peu près le code standard généré par Visual Studio pour les comptes d'utilisateurs individuels.

Il y a une ligne dans le code StartUp.Auth.cs qui ressemble à ceci.

app.UseOAuthBearerTokens(OAuthOptions);

Avec cela en place, je peux ajouter l'attribut [Autoriser] aux contrôleurs et cela fonctionne très bien.

Par ailleurs, avec angular j'ai pu ajouter un en-tête standard contenant le jeton dans le JavaScript comme suit.

$http.defaults.headers.common.Authorization = 'bearer ' + access_token;

J'ai ensuite ajouté SignalR au projet.
Il prend en charge sa propre version de l'attribut [Autoriser] mais il n'y a aucun moyen de passer des en-têtes personnalisés lors de l'utilisation de SignalR.
C'est une limitation du côté du navigateur.
La documentation indique que vous pouvez transmettre le jeton dans le cadre de la chaîne de requête.
J'ai ajouté ce code du côté JavaScript. Mon code SignalR incluait maintenant cela.
J'ai transmis le jeton en tant que 'bearer_token'.

this.connection = $.hubConnection("/TestHub", { useDefaultPath: false, qs: "bearer_token=" + token });

Mon problème était donc de savoir comment faire en sorte qu'OWIN reconnaisse le jeton maintenant qu'il n'était plus dans l'en-tête.
Après un certain nombre de recherches, j'ai fini par ajouter du code qui a déplacé le jeton de la chaîne de requête dans l'en-tête.
Pour mon prototype, je viens d'ajouter un petit code au-dessus de la ligne d'origine dans StartUp.Auth.cs.

Alors maintenant, ça ressemblait à ça:

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
    Provider = new OAuthBearerAuthenticationProvider()
    {
        OnRequestToken = context =>
        {
            if (context.Request.Path.Value.StartsWith("/TestHub"))
            {
                string bearerToken = context.Request.Query.Get("bearer_token");
                if (bearerToken != null)
                {
                    string[] authorization = new string[] { "bearer " + bearerToken };
                    context.Request.Headers.Add("Authorization", authorization);
                }
            }

            return Task.FromResult(context);
        }
    }
});


// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);

Le code ci-dessus est approximatif mais c'est un prototype, donc je voulais vraiment voir si cela fonctionnait.

Enfin, pour arriver à la question: Est-ce le bon schéma pour intégrer l'autorisation de jeton de support avec SignalR et le pipeline OWIN.
Je n'arrivais pas à trouver beaucoup de bonnes informations sur la bonne façon de procéder.

28
Peter Trenery

J'utilise une classe comme celle-ci:

public class OAuthTokenProvider : OAuthBearerAuthenticationProvider
{
    private List<Func<IOwinRequest, string>> _locations;
    private readonly Regex _bearerRegex = new Regex("((B|b)earer\\s)");
    private const string AuthHeader = "Authorization";

    /// <summary>
    /// By Default the Token will be searched for on the "Authorization" header.
    /// <para> pass additional getters that might return a token string</para>
    /// </summary>
    /// <param name="locations"></param>
    public OAuthTokenProvider(params Func<IOwinRequest, string>[] locations)
    {
        _locations = locations.ToList();
        //Header is used by default
        _locations.Add(x => x.Headers.Get(AuthHeader));
    }

    public override Task RequestToken(OAuthRequestTokenContext context)
    {
        var getter = _locations.FirstOrDefault(x => !String.IsNullOrWhiteSpace(x(context.Request)));
        if (getter != null)
        {
            var tokenStr = getter(context.Request);
            context.Token = _bearerRegex.Replace(tokenStr, "").Trim();
        }
        return Task.FromResult<object>(null);
    }
}

Ce qui au lieu de simplement passer le jeton à l'en-tête, l'analyse et le place dans le contexte.

Ensuite, il pourrait être utilisé dans la configuration de votre application comme ceci:

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
    Provider = new OAuthTokenProvider(
         req => req.Query.Get("bearer_token"),
         req => req.Query.Get("access_token"),
         req => req.Query.Get("token"),
         req => req.Headers.Get("X-Token"))    
});

Ensuite, les styles de demandes suivants auraient leur jeton non chiffré, à utiliser avec l'authentification et l'autorisation:

GET https://www.myapp.com/authorized/endpoint?bearer_token=123ABC HTTP/1.1
GET https://www.myapp.com/authorized/endpoint?access_token=123ABC HTTP/1.1
GET https://www.myapp.com/authorized/endpoint?token=123ABC HTTP/1.1

GET https://www.myapp.com/authorized/endpoint HTTP/1.1
X-Token: 123ABC

GET https://www.myapp.com/authorized/endpoint HTTP/1.1
Authorization: 123ABC
19
calebboyd

Voilà comment je l'ai résolu

var authData = localStorageService.get('authorizationData');
var token = authData.token;
 $.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };

Pas de changement sur le code côté serveur

J'espère que ça aide quelqu'un

16
Santiago Robledo

Je posterai ceci pour les personnes qui auront ce problème à l'avenir:

Il existe plusieurs approches à cela pour le faire fonctionner, mais en fonction de l'objectif de l'application.

  1. Le premier que j'ai vu fait fonctionner SignalR avec des en-têtes, ce qui semble très facile à implémenter:

    $.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };

L'énorme inconvénient est qu'il force SignalR à utiliser longPolling, ce que vous ne voulez certainement pas, car vous utilisez SignalR.

  1. La deuxième approche consiste à passer le access token que le client reçoit lors de la connexion en tant que query string, juste avant de vous connecter. Ensuite, faites un Attribute personnalisé qui hérite du AuthorizeAttribute ( suivez ce dépôt - pas génial à mon avis, mais cela fait un point ).

Une autre approche en passant le jeton comme query string est suivez cette SO qui crée un OAuth Provider et

récupère toutes les autres valeurs du jeton au début du pipeline

Encore une fois, cette méthode n'est pas optimale puisque query strings sont assez vulnérables:

les chaînes de requête ont tendance à être stockées dans les journaux du serveur Web (ou exposées d'autres manières même lors de l'utilisation de SSL). Il y a un risque que quelqu'un intercepte les jetons. Vous devez sélectionner une approche qui correspond le mieux à votre scénario.

  1. La solution que j'essaie actuellement de mettre en œuvre (et reviendra avec des mises à jour une fois que cela fonctionne: D) est basée sur ce billet de blog qui utilise le OAuth Bearer Token authentication avec SignalR en passant le jeton sur un cookie dans le pipeline SignalR.

Je pense que c'est la solution avec le moins de compromis, mais je reviendrai avec plus d'informations une fois ma mise en œuvre terminée.

J'espère que cela t'aides. Bonne chance!

MISE À JOUR J'ai réussi à faire en sorte que l'authentification par jeton WebApi fonctionne avec SignalR en stockant le jeton dans un cookie, puis en le récupérant chez un fournisseur.

Vous pouvez prendre un regardez ce dépôt GitHub , mais j'ai surtout suivi l'article d'en haut. Explicitement, voici ce que j'ai fait:

J'ai créé une classe OAuthBearerTokenAuthenticationProvider qui hérite de OAuthBearerAuthenticationProvider et j'ai remplacé la méthode RequestToken.

Maintenant, il recherche le cookie où le BearerToken est stocké et récupère sa valeur. Ensuite, il définit le context.Token à la valeur du cookie.

Ensuite, dans la partie JavaScript, vous devez obtenir le jeton (en vous authentifiant à l'aide du nom d'utilisateur et du mot de passe) et stocker le jeton dans un cookie.

public class OAuthBearerTokenAuthenticationProvider : OAuthBearerAuthenticationProvider
{
    public override Task RequestToken(OAuthRequestTokenContext context)
    {
        var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];

        if (!String.IsNullOrEmpty(tokenCookie))
            context.Token = tokenCookie;

        return Task.FromResult<object>(null);
    }
}

Pour un échantillon de travail, veuillez jeter un œil au dépôt ci-dessus.

Bonne chance!

14
radu-matei