Xác thực ASP.NET Core Web API


98

Tôi đang đấu tranh với cách thiết lập xác thực trong dịch vụ web của mình. Dịch vụ được xây dựng bằng api web ASP.NET Core.

Tất cả khách hàng của tôi (ứng dụng WPF) phải sử dụng cùng một thông tin xác thực để gọi các hoạt động dịch vụ web.

Sau một số nghiên cứu, tôi đã đưa ra xác thực cơ bản - gửi tên người dùng và mật khẩu trong tiêu đề của yêu cầu HTTP. Nhưng sau nhiều giờ nghiên cứu, đối với tôi, có vẻ như xác thực cơ bản không phải là cách để đi trong ASP.NET Core.

Hầu hết các tài nguyên tôi tìm thấy đều đang triển khai xác thực bằng OAuth hoặc một số phần mềm trung gian khác. Nhưng điều đó dường như là quá khổ đối với kịch bản của tôi, cũng như việc sử dụng phần Identity của ASP.NET Core.

Vậy đâu là cách phù hợp để đạt được mục tiêu của tôi - xác thực đơn giản với tên người dùng và mật khẩu trong dịch vụ web ASP.NET Core?

Cảm ơn trước!

Câu trả lời:


75

Bạn có thể triển khai một phần mềm trung gian xử lý xác thực Cơ bản.

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

Mã này được viết trong phiên bản beta của lõi asp.net. Hy vọng nó giúp.


1
Cảm ơn câu trả lời của bạn! Đây chính xác là những gì tôi đang tìm kiếm - một giải pháp đơn giản để xác thực cơ bản.
Felix

1
Có một lỗi trong mã này do sử dụng chuỗi thông tin đăng nhập (':') - nó sẽ không xử lý chính xác các mật khẩu có chứa dấu hai chấm. Mã trong câu trả lời của Felix không bị vấn đề này.
Phil Dennis

111

Bây giờ, sau khi tôi đã đi đúng hướng, đây là giải pháp hoàn chỉnh của tôi:

Đây là lớp phần mềm trung gian được thực thi trên mọi yêu cầu đến và kiểm tra xem yêu cầu có thông tin đăng nhập chính xác hay không. Nếu không có thông tin đăng nhập nào hoặc nếu chúng sai, dịch vụ sẽ phản hồi với lỗi 401 Unauthorized ngay lập tức.

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

Phần mở rộng phần mềm trung gian cần được gọi trong phương thức Định cấu hình của lớp Khởi động dịch vụ

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

    app.UseMiddleware<AuthenticationMiddleware>();

    app.UseMvc();
}

Và đó là tất cả! :)

Bạn có thể tìm thấy một tài nguyên rất tốt cho phần mềm trung gian trong .Net Core và xác thực tại đây: https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/


4
Cảm ơn bạn đã đăng giải pháp hoàn chỉnh. Tuy nhiên, tôi phải thêm dòng 'context.Response.Headers.Add ("WWW-Xác thực", "Cơ bản cảnh giới = \" Cảnh giới \ "");' vào phần 'không có tiêu đề ủy quyền' để trình duyệt yêu cầu thông tin xác thực.
m0n0ph0n

Xác thực này an toàn đến mức nào? Điều gì sẽ xảy ra nếu ai đó đánh hơi thấy tiêu đề yêu cầu và nhận được tên người dùng / mật khẩu?
Bewar Salah

5
@BewarSalah, bạn phải cung cấp loại giải pháp này qua https
wal

2
Một số bộ điều khiển nên cho phép ẩn danh. Giải pháp phần mềm trung gian này sẽ không thành công trong trường hợp đó vì nó sẽ kiểm tra tiêu đề ủy quyền trong mỗi yêu cầu.
Karthik

28

Để sử dụng điều này chỉ cho các bộ điều khiển cụ thể, ví dụ, hãy sử dụng:

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

22

Tôi nghĩ bạn có thể sử dụng JWT (Json Web Tokens).

Trước tiên, bạn cần cài đặt gói System.IdentityModel.Tokens.Jwt:

$ dotnet add package System.IdentityModel.Tokens.Jwt

Bạn sẽ cần thêm bộ điều khiển để tạo và xác thực mã thông báo như sau:

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

Sau khi cập nhật đó lớp Startup.cs trông giống như bên dưới:

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

Vậy là xong, việc còn lại bây giờ là đặt [Authorize]thuộc tính vào Controllers hoặc Action mà bạn muốn.

Đây là một liên kết của một hướng dẫn chuyển tiếp hoàn chỉnh.

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


9

Tôi đã triển khai BasicAuthenticationHandlercho xác thực cơ bản để bạn có thể sử dụng nó với các thuộc tính standart AuthorizeAllowAnonymous.

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

Đăng ký tại Startup.cs - app.UseMiddleware<BasicAuthenticationMiddleware>();. Với mã này, bạn có thể hạn chế bất kỳ bộ điều khiển nào có thuộc tính standart Autorize:

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

và sử dụng thuộc tính AllowAnonymousnếu bạn áp dụng bộ lọc ủy quyền ở cấp ứng dụng.


1
Tôi đã sử dụng mã của bạn, nhưng tôi nhận thấy không có vấn đề nếu Ủy quyền (ActiveAuthenticationSchemes = "Basic")] được đặt hay không trong mỗi cuộc gọi, phần mềm trung gian được kích hoạt dẫn đến mọi bộ điều khiển được xác thực khi không mong muốn.
CSharper

Tôi thích câu trả lời này
KTOV

1
ví dụ làm việc tại đây: jasonwatmore.com/post/2018/09/08/…
bside

Tôi nghĩ rằng đây là câu trả lời là cách để đi, vì nó cho phép bạn sử dụng các thuộc tính ủy quyền / cho phép tiêu chuẩn được nâng cao hơn nữa trong giải pháp. Bên cạnh đó, nó phải là dễ dàng để sử dụng chương trình xác thực khác sau này trong giai đoạn của dự án nên có thể được yêu cầu
Frederik Gheysels

0

Trong đại diện Github công khai này https://github.com/boskjoett/BasicAuthWebApi, bạn có thể thấy một ví dụ đơn giản về API web ASP.NET Core 2.2 với các điểm cuối được bảo vệ bằng Xác thực cơ bản.


Nếu bạn muốn sử dụng Danh tính được xác thực trong bộ điều khiển của mình (SecureValuesController), thì việc tạo một vé là không đủ vì đối tượng Request.User trống. Chúng ta có cần gán ClaimsPrincipal này cho Ngữ cảnh hiện tại trong AuthenticationHandler không? Đó là cách chúng tôi đã làm trong WebApi cũ hơn ...
pseabury

0

Như đã nói đúng trong các bài viết trước, một trong những cách là triển khai phần mềm trung gian xác thực cơ bản tùy chỉnh. Tôi đã tìm thấy mã hoạt động tốt nhất với lời giải thích trong blog này: Auth cơ bản với phần mềm trung gian tùy chỉnh

Tôi đã giới thiệu cùng một blog nhưng phải thực hiện 2 lần chuyển thể:

  1. Trong khi thêm phần mềm trung gian trong tệp khởi động -> Chức năng định cấu hình, hãy luôn thêm phần mềm trung gian tùy chỉnh trước khi thêm app.UseMvc ().
  2. Trong khi đọc tên người dùng, mật khẩu từ tệp appsettings.json, hãy thêm thuộc tính chỉ đọc tĩnh trong tệp Khởi động. Sau đó, đọc từ appsettings.json. Cuối cùng, đọc các giá trị từ bất kỳ đâu trong dự án. Thí dụ:

    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

Bạn có thể sử dụng một 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();
    }
}

và thêm thuộc tính vào bộ điều khiển của bạn

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


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.