ASP.NET Core Web API Authentication


98

ฉันกำลังดิ้นรนกับวิธีตั้งค่าการรับรองความถูกต้องในบริการเว็บของฉัน บริการนี้สร้างด้วย API เว็บ ASP.NET Core

ไคลเอนต์ทั้งหมดของฉัน (แอปพลิเคชัน WPF) ควรใช้ข้อมูลประจำตัวเดียวกันเพื่อเรียกใช้การดำเนินการบริการเว็บ

หลังจากการวิจัยบางอย่างฉันได้พบกับการรับรองความถูกต้องขั้นพื้นฐาน - ส่งชื่อผู้ใช้และรหัสผ่านในส่วนหัวของคำขอ HTTP แต่หลังจากการวิจัยหลายชั่วโมงสำหรับฉันดูเหมือนว่าการรับรองความถูกต้องขั้นพื้นฐานไม่ใช่วิธีที่จะไปใน ASP.NET Core

ทรัพยากรส่วนใหญ่ที่ฉันพบคือการใช้การตรวจสอบสิทธิ์โดยใช้ OAuth หรือมิดเดิลแวร์อื่น ๆ แต่ดูเหมือนว่าจะมีขนาดใหญ่เกินไปสำหรับสถานการณ์ของฉันเช่นเดียวกับการใช้ส่วน Identity ของ ASP.NET Core

วิธีที่ถูกต้องในการบรรลุเป้าหมายของฉันคืออะไร - การตรวจสอบความถูกต้องง่ายๆด้วยชื่อผู้ใช้และรหัสผ่านในบริการเว็บ ASP.NET Core?

ขอบคุณล่วงหน้า!

คำตอบ:


75

คุณสามารถใช้มิดเดิลแวร์ที่จัดการการรับรองความถูกต้องพื้นฐาน

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 รุ่นเบต้า หวังว่าจะช่วยได้


1
ขอบคุณสำหรับคำตอบ! นี่คือสิ่งที่ฉันกำลังมองหา - วิธีง่ายๆสำหรับการตรวจสอบสิทธิ์ขั้นพื้นฐาน
Felix

1
มีข้อบกพร่องในรหัสนี้เนื่องจากการใช้ credentialstring.Split (':') - จะจัดการรหัสผ่านที่มีเครื่องหมายทวิภาคไม่ถูกต้อง รหัสในคำตอบของเฟลิกซ์ไม่ประสบปัญหานี้
Phil Dennis

111

หลังจากที่ฉันชี้ไปในทิศทางที่ถูกต้องนี่คือคำตอบที่สมบูรณ์ของฉัน:

นี่คือคลาสมิดเดิลแวร์ที่ดำเนินการกับทุกคำขอที่เข้ามาและตรวจสอบว่าคำขอมีข้อมูลประจำตัวที่ถูกต้องหรือไม่ หากไม่มีข้อมูลรับรองอยู่หรือหากผิดบริการจะตอบสนองด้วยข้อผิดพลาด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;
        }
    }
}

จำเป็นต้องเรียกส่วนขยายมิดเดิลแวร์ในวิธีกำหนดค่าของคลาสการเริ่มต้นบริการ

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 \ "");' ไปที่ส่วน 'no authorization header' เพื่อให้เบราว์เซอร์ร้องขอข้อมูลรับรอง
m0n0ph0n

การรับรองความถูกต้องนี้ปลอดภัยมากน้อยเพียงใด จะเกิดอะไรขึ้นถ้ามีใครบางคนเข้าไปที่ส่วนหัวของคำขอและได้รับชื่อผู้ใช้ / รหัสผ่าน?
Bewar Salah

5
@BewarSalah คุณต้องใช้วิธีแก้ปัญหาแบบนี้ผ่าน https
wal

2
ตัวควบคุมบางตัวควรอนุญาตแบบไม่ระบุตัวตน โซลูชันมิดเดิลแวร์นี้จะล้มเหลวในกรณีนั้นเนื่องจากจะตรวจสอบส่วนหัวการอนุญาตในแต่ละคำขอ
Karthik

28

หากต้องการใช้สิ่งนี้สำหรับคอนโทรลเลอร์เฉพาะตัวอย่างให้ใช้สิ่งนี้:

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

22

ฉันคิดว่าคุณสามารถใช้ JWT (Json Web Tokens) ได้

ก่อนอื่นคุณต้องติดตั้งแพ็คเกจ 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สำหรับการตรวจสอบสิทธิ์ขั้นพื้นฐานเพื่อให้คุณสามารถใช้กับคุณสมบัติมาตรฐาน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

1
ตัวอย่างการทำงานที่นี่: jasonwatmore.com/post/2018/09/08/…
bside

ฉันคิดว่านี่คือคำตอบคือหนทางที่จะไปเนื่องจากช่วยให้คุณสามารถใช้แอตทริบิวต์การอนุญาต / อนุญาตให้ใช้มาตรฐานเพิ่มเติมในโซลูชัน ถัดจากนั้นควรใช้รูปแบบการรับรองความถูกต้องอื่นในภายหลังในขั้นตอนของโครงการได้ง่าย
Frederik Gheysels

0

ใน repo Github สาธารณะนี้ https://github.com/boskjoett/BasicAuthWebApi คุณสามารถดูตัวอย่างง่ายๆของ ASP.NET Core 2.2 web API พร้อมจุดสิ้นสุดที่ได้รับการป้องกันโดย Basic Authentication


หากคุณต้องการใช้ Authenticated Identity ในคอนโทรลเลอร์ของคุณ (SecureValuesController) การสร้างตั๋วไม่เพียงพอตามคำขอวัตถุของผู้ใช้ว่างเปล่า เรายังจำเป็นต้องกำหนด ClaimsPrincipal นี้ให้กับบริบทปัจจุบันใน AuthenticationHandler หรือไม่ นั่นคือวิธีที่เราทำใน WebApi รุ่นเก่า ...
pseabury

0

ตามที่กล่าวไว้ในโพสต์ก่อนหน้านี้วิธีหนึ่งคือการใช้มิดเดิลแวร์การตรวจสอบสิทธิ์ขั้นพื้นฐานที่กำหนดเอง ฉันพบโค้ดที่ใช้งานได้ดีที่สุดพร้อมคำอธิบายในบล็อกนี้: Basic Auth with custom middleware

ฉันอ้างถึงบล็อกเดียวกัน แต่ต้องทำการดัดแปลง 2 ครั้ง:

  1. ในขณะที่เพิ่มมิดเดิลแวร์ในไฟล์เริ่มต้น -> ฟังก์ชั่นกำหนดค่าให้เพิ่มมิดเดิลแวร์ที่กำหนดเองเสมอก่อนที่จะเพิ่มแอพใช้แอพใช้ ()
  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")]


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.