Autenticação baseada em 'Token' na API Web sem qualquer interface de utilizador

Estou a desenvolver uma API de descanso em ASP.Net API Web. A minha API só será acessível através de clientes não baseados em navegador. Eu preciso implementar a segurança para a minha API então eu decidi ir com autenticação baseada em Token. Eu tenho uma compreensão justa da autenticação baseada em token e li alguns tutoriais, mas todos eles têm alguma interface de usuário para login. Eu não preciso de qualquer UI para login como os detalhes de login serão passados pelo cliente através de HTTP POST que será autorizado a partir de nossa base de dados. Como posso implementar autenticação baseada em token na minha API? Por favor, note - minha API será acessada em alta frequência, então eu também tenho que cuidar do desempenho. Por favor, avise-me se puder explicar melhor.

Author: Legolas Greenleaf, 2016-07-29

2 answers

Acho que há alguma confusão sobre a diferença entre o MVC e a API Web. Em resumo, para MVC você pode usar um formulário de login e criar uma sessão usando cookies. Para a Api Web não há sessão. É por isso que queres usar o símbolo.

Você não precisa de um formulário de login. O Token endpoint é tudo o que precisas. Como o Win descreveu, vai enviar as credenciais para o ponto final onde é tratado.

Aqui está um código C# do lado do cliente para obter um token.
    //using System;
    //using System.Collections.Generic;
    //using System.Net;
    //using System.Net.Http;
    //string token = GetToken("https://localhost:<port>/", userName, password);

    static string GetToken(string url, string userName, string password) {
        var pairs = new List<KeyValuePair<string, string>>
                    {
                        new KeyValuePair<string, string>( "grant_type", "password" ), 
                        new KeyValuePair<string, string>( "username", userName ), 
                        new KeyValuePair<string, string> ( "Password", password )
                    };
        var content = new FormUrlEncodedContent(pairs);
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        using (var client = new HttpClient()) {
            var response = client.PostAsync(url + "Token", content).Result;
            return response.Content.ReadAsStringAsync().Result;
        }
    }

A fim de use o item adicione-o ao cabeçalho do pedido:

    //using System;
    //using System.Collections.Generic;
    //using System.Net;
    //using System.Net.Http;
    //var result = CallApi("https://localhost:<port>/something", token);

    static string CallApi(string url, string token) {
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        using (var client = new HttpClient()) {
            if (!string.IsNullOrWhiteSpace(token)) {
                var t = JsonConvert.DeserializeObject<Token>(token);

                client.DefaultRequestHeaders.Clear();
                client.DefaultRequestHeaders.Add("Authorization", "Bearer " + t.access_token);
            }
            var response = client.GetAsync(url).Result;
            return response.Content.ReadAsStringAsync().Result;
        }
    }

Onde está a ficha:

//using Newtonsoft.Json;

class Token
{
    public string access_token { get; set; }
    public string token_type { get; set; }
    public int expires_in { get; set; }
    public string userName { get; set; }
    [JsonProperty(".issued")]
    public string issued { get; set; }
    [JsonProperty(".expires")]
    public string expires { get; set; }
}
Agora para o lado do servidor:

No Arranque.Autenticacao.cs

        var oAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider("self"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            // https
            AllowInsecureHttp = false
        };
        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(oAuthOptions);

E no pedido, o autor fornece.cs o código que efectivamente concede ou nega o acesso:

//using Microsoft.AspNet.Identity.Owin;
//using Microsoft.Owin.Security;
//using Microsoft.Owin.Security.OAuth;
//using System;
//using System.Collections.Generic;
//using System.Security.Claims;
//using System.Threading.Tasks;

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
    private readonly string _publicClientId;

    public ApplicationOAuthProvider(string publicClientId)
    {
        if (publicClientId == null)
            throw new ArgumentNullException("publicClientId");

        _publicClientId = publicClientId;
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

        var user = await userManager.FindAsync(context.UserName, context.Password);
        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }

        ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager);
        var propertyDictionary = new Dictionary<string, string> { { "userName", user.UserName } };
        var properties = new AuthenticationProperties(propertyDictionary);

        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
        // Token is validated.
        context.Validated(ticket);
    }

    public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    {
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
        {
            context.AdditionalResponseParameters.Add(property.Key, property.Value);
        }
        return Task.FromResult<object>(null);
    }

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        // Resource owner password credentials does not provide a client ID.
        if (context.ClientId == null)
            context.Validated();

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

    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
    {
        if (context.ClientId == _publicClientId)
        {
            var expectedRootUri = new Uri(context.Request.Uri, "/");

            if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                context.Validated();
        }
        return Task.FromResult<object>(null);
    }

}

Como pode ver, não há nenhum controlador envolvido na recuperação do token. Na verdade, você pode remover todas as referências MVC se você quiser uma Api Web somente. Simplifiquei o código do lado do servidor para torná-lo mais legível. Você pode adicionar código para melhorar a segurança.

Certifique-se que usa apenas SSL. Implemente os Requirehttps que contribuem para forçar isso.

Você pode usar os atributos autorize / AllowAnonymous para proteger a sua Api Web. Além disso, você pode adicionar filtros (como RequireHttpsAttribute) para tornar a sua Api Web mais segura. Espero que isto ajude.

 57
Author: Ruard van Elburg, 2016-07-30 04:01:57

ASP.Net a API Web já tem compilação do servidor de autorização. Você pode vê-lo dentro do arranque.cs quando se cria um novo ASP.Net aplicação Web com modelo de API Web.

OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
    // In production mode set AllowInsecureHttp = false
    AllowInsecureHttp = true
};

Tudo o que tem de fazer é publicar o nome de utilizador codificado pelo URL e a senha dentro do texto da pesquisa.

/Token/userName=johndoe%40example.com&password=1234&grant_type=password

Se você quiser saber mais detalhes, você pode assistir registo do utilizador e login-Angular frente a trás com API Web por Deborah Kurata .

 15
Author: Win, 2016-07-29 14:49:35