ASP.NET Core Web API身份验证


96

我正在努力如何在Web服务中设置身份验证。该服务是使用ASP.NET Core Web API构建的。

我所有的客户端(WPF应用程序)应使用相同的凭据来调用Web服务操作。

经过研究,我想到了基本的身份验证-在HTTP请求的标头中发送用户名和密码。但是经过数小时的研究,在我看来,基本身份验证不是ASP.NET Core的方法。

我发现的大多数资源都是使用OAuth或其他中间件来实现身份验证。但这对于我的情况以及使用ASP.NET Core的Identity部分来说似乎太大了。

那么,实现我的目标的正确方法是什么-在ASP.NET Core Web服务中使用用户名和密码进行简单身份验证?

提前致谢!

Answers:


73

您可以实现处理基本身份验证的中间件。

public async Task Invoke(HttpContext context)
{
    var authHeader = context.Request.Headers.Get("Authorization");
    if (authHeader != null && authHeader.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
    {
        var token = authHeader.Substring("Basic ".Length).Trim();
        System.Console.WriteLine(token);
        var credentialstring = Encoding.UTF8.GetString(Convert.FromBase64String(token));
        var credentials = credentialstring.Split(':');
        if(credentials[0] == "admin" && credentials[1] == "admin")
        {
            var claims = new[] { new Claim("name", credentials[0]), new Claim(ClaimTypes.Role, "Admin") };
            var identity = new ClaimsIdentity(claims, "Basic");
            context.User = new ClaimsPrincipal(identity);
        }
    }
    else
    {
        context.Response.StatusCode = 401;
        context.Response.Headers.Set("WWW-Authenticate", "Basic realm=\"dotnetthoughts.net\"");
    }
    await _next(context);
}

这段代码是用ASP.NET Core的Beta版编写的。希望能帮助到你。


1
感谢您的回答!这正是我一直在寻找的-一种基本身份验证的简单解决方案。
费利克斯

1
这段代码中有一个错误,原因是使用了passwordstring.Split(':')-无法正确处理包含冒号的密码。Felix答案中的代码不存在此问题。
菲尔·丹尼斯

110

现在,在向正确的方向指示之后,这是我的完整解决方案:

这是在每个传入请求上执行的中间件类,用于检查请求是否具有正确的凭据。如果没有凭据或凭据不正确,该服务将立即响应401未经授权错误。

public class AuthenticationMiddleware
{
    private readonly RequestDelegate _next;

    public AuthenticationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        string authHeader = context.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.StartsWith("Basic"))
        {
            //Extract credentials
            string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
            Encoding encoding = Encoding.GetEncoding("iso-8859-1");
            string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));

            int seperatorIndex = usernamePassword.IndexOf(':');

            var username = usernamePassword.Substring(0, seperatorIndex);
            var password = usernamePassword.Substring(seperatorIndex + 1);

            if(username == "test" && password == "test" )
            {
                await _next.Invoke(context);
            }
            else
            {
                context.Response.StatusCode = 401; //Unauthorized
                return;
            }
        }
        else
        {
            // no authorization header
            context.Response.StatusCode = 401; //Unauthorized
            return;
        }
    }
}

需要在服务启动类的Configure方法中调用中间件扩展

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.UseMiddleware<AuthenticationMiddleware>();

    app.UseMvc();
}

就这样!:)

可以在此处找到.Net Core中的中间件和身份验证的很好的资源:https : //www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/


4
感谢您发布完整的解决方案。但是,我必须添加“ context.Response.Headers.Add(“ WWW-Authenticate”,“ Basic realm = \” realm \“”);“行;' 进入“无授权标头”部分,以使浏览器请求凭据。
m0n0ph0n

此身份验证多少安全?如果有人嗅到请求标头并获得用户名/密码怎么办?
Bewar Salah

5
@BewarSalah,您必须通过https提供这种解决方案
wal

2
一些控制器应允许匿名。在这种情况下,此中间件解决方案将失败,因为它将在每个请求中检查授权标头。
Karthik

28

例如,要将其仅用于特定的控制器,请使用以下命令:

app.UseWhen(x => (x.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase)), 
            builder =>
            {
                builder.UseMiddleware<AuthenticationMiddleware>();
            });

21

我认为您可以使用JWT(Json Web令牌)。

首先,您需要安装软件包System.IdentityModel.Tokens.Jwt:

$ dotnet add package System.IdentityModel.Tokens.Jwt

您将需要添加一个用于令牌生成和身份验证的控制器,如下所示:

public class TokenController : Controller
{
    [Route("/token")]

    [HttpPost]
    public IActionResult Create(string username, string password)
    {
        if (IsValidUserAndPasswordCombination(username, password))
            return new ObjectResult(GenerateToken(username));
        return BadRequest();
    }

    private bool IsValidUserAndPasswordCombination(string username, string password)
    {
        return !string.IsNullOrEmpty(username) && username == password;
    }

    private string GenerateToken(string username)
    {
        var claims = new Claim[]
        {
            new Claim(ClaimTypes.Name, username),
            new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()),
            new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString()),
        };

        var token = new JwtSecurityToken(
            new JwtHeader(new SigningCredentials(
                new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Secret Key You Devise")),
                                         SecurityAlgorithms.HmacSha256)),
            new JwtPayload(claims));

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

在更新Startup.cs类后,如下所示:

namespace WebAPISecurity
{   
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        services.AddAuthentication(options => {
            options.DefaultAuthenticateScheme = "JwtBearer";
            options.DefaultChallengeScheme = "JwtBearer";
        })
        .AddJwtBearer("JwtBearer", jwtBearerOptions =>
        {
            jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Secret Key You Devise")),
                ValidateIssuer = false,
                //ValidIssuer = "The name of the issuer",
                ValidateAudience = false,
                //ValidAudience = "The name of the audience",
                ValidateLifetime = true, //validate the expiration and not before values in the token
                ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
            };
        });

    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseAuthentication();

        app.UseMvc();
    }
}

就是这样,现在剩下的就是将[Authorize]属性放在所需的控制器或动作上。

这是完整的直接教程的链接。

http://www.blinkingcaret.com/2017/09/06/secure-web-api-in-asp-net-core/


9

我已经实现BasicAuthenticationHandler了基本身份验证,因此您可以将其与standart属性Authorize和一起使用AllowAnonymous

public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var authHeader = (string)this.Request.Headers["Authorization"];

        if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
        {
            //Extract credentials
            string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
            Encoding encoding = Encoding.GetEncoding("iso-8859-1");
            string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));

            int seperatorIndex = usernamePassword.IndexOf(':', StringComparison.OrdinalIgnoreCase);

            var username = usernamePassword.Substring(0, seperatorIndex);
            var password = usernamePassword.Substring(seperatorIndex + 1);

            //you also can use this.Context.Authentication here
            if (username == "test" && password == "test")
            {
                var user = new GenericPrincipal(new GenericIdentity("User"), null);
                var ticket = new AuthenticationTicket(user, new AuthenticationProperties(), Options.AuthenticationScheme);
                return Task.FromResult(AuthenticateResult.Success(ticket));
            }
            else
            {
                return Task.FromResult(AuthenticateResult.Fail("No valid user."));
            }
        }

        this.Response.Headers["WWW-Authenticate"]= "Basic realm=\"yourawesomesite.net\"";
        return Task.FromResult(AuthenticateResult.Fail("No credentials."));
    }
}

public class BasicAuthenticationMiddleware : AuthenticationMiddleware<BasicAuthenticationOptions>
{
    public BasicAuthenticationMiddleware(
       RequestDelegate next,
       IOptions<BasicAuthenticationOptions> options,
       ILoggerFactory loggerFactory,
       UrlEncoder encoder)
       : base(next, options, loggerFactory, encoder)
    {
    }

    protected override AuthenticationHandler<BasicAuthenticationOptions> CreateHandler()
    {
        return new BasicAuthenticationHandler();
    }
}

public class BasicAuthenticationOptions : AuthenticationOptions
{
    public BasicAuthenticationOptions()
    {
        AuthenticationScheme = "Basic";
        AutomaticAuthenticate = true;
    }
}

在Startup.cs-上注册app.UseMiddleware<BasicAuthenticationMiddleware>();。使用此代码,您可以使用标准属性Autorize限制任何控制器:

[Authorize(ActiveAuthenticationSchemes = "Basic")]
[Route("api/[controller]")]
public class ValuesController : Controller

AllowAnonymous在应用程序级别应用授权过滤器时使用属性。


1
我使用了您的代码,但是无论在每次调用中是否都设置了Authorize(ActiveAuthenticationSchemes =“ Basic”)],我都没有注意到中间件被激活,导致每个控制器在不需要时也得到了验证。
CSharper

我喜欢这个答案
KTOV


我认为这是解决之道,因为它允许您在解决方案中进一步使用标准的authorize / allowanonymous属性。紧接着,应该很容易在项目后期使用另一个身份验证方案
Frederik Gheysels

0

在此公开的Github存储库 https://github.com/boskjoett/BasicAuthWebApi中, 您可以看到一个带有受基本身份验证保护的终结点的ASP.NET Core 2.2 Web API的简单示例。


如果要在控制器(SecureValuesController)中使用Authenticated Identity,则创建票证是不够的,因为Request.User对象为空。我们仍然需要将此ClaimsPrincipal分配给AuthenticationHandler中的当前Context吗?这就是我们在较旧的WebApi中所做的方式...
pseabury

0

就像以前的帖子所正确说的那样,一种方法是实现自定义的基本身份验证中间件。我在此博客中找到了具有解释的最佳工作代码: 带有自定义中间件的基本身份验证

我提到了同一个博客,但必须进行2次改编:

  1. 在启动文件->配置功能中添加中间件时,请始终在添加app.UseMvc()之前添加自定义中间件。
  2. 从appsettings.json文件读取用户名和密码时,在启动文件中添加静态只读属性。然后从appsettings.json中读取。最后,从项目中的任何位置读取值。例:

    public class Startup
    {
      public Startup(IConfiguration configuration)
      {
        Configuration = configuration;
      }
    
      public IConfiguration Configuration { get; }
      public static string UserNameFromAppSettings { get; private set; }
      public static string PasswordFromAppSettings { get; private set; }
    
      //set username and password from appsettings.json
      UserNameFromAppSettings = Configuration.GetSection("BasicAuth").GetSection("UserName").Value;
      PasswordFromAppSettings = Configuration.GetSection("BasicAuth").GetSection("Password").Value;
    }

0

您可以使用 ActionFilterAttribute

public class BasicAuthAttribute : ActionFilterAttribute
{
    public string BasicRealm { get; set; }
    protected NetworkCredential Nc { get; set; }

    public BasicAuthAttribute(string user,string pass)
    {
        this.Nc = new NetworkCredential(user,pass);
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var req = filterContext.HttpContext.Request;
        var auth = req.Headers["Authorization"].ToString();
        if (!String.IsNullOrEmpty(auth))
        {
            var cred = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(auth.Substring(6)))
                .Split(':');
            var user = new {Name = cred[0], Pass = cred[1]};
            if (user.Name == Nc.UserName && user.Pass == Nc.Password) return;
        }

        filterContext.HttpContext.Response.Headers.Add("WWW-Authenticate",
            String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
        filterContext.Result = new UnauthorizedResult();
    }
}

并将属性添加到您的控制器

[BasicAuth("USR", "MyPassword")]


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.