ASP.NET Core 웹 API 인증


98

내 웹 서비스에서 인증을 설정하는 방법에 어려움을 겪고 있습니다. 이 서비스는 ASP.NET Core 웹 API로 빌드됩니다.

내 모든 클라이언트 (WPF 애플리케이션)는 동일한 자격 증명을 사용하여 웹 서비스 작업을 호출해야합니다.

몇 가지 조사 끝에 HTTP 요청 헤더에 사용자 이름과 비밀번호를 보내는 기본 인증을 생각해 냈습니다. 그러나 몇 시간의 연구 끝에 기본 인증이 ASP.NET Core로 이동하는 방법이 아닌 것 같습니다.

내가 찾은 대부분의 리소스는 OAuth 또는 다른 미들웨어를 사용하여 인증을 구현하고 있습니다. 그러나 그것은 ASP.NET Core의 ID 부분을 사용하는 것뿐만 아니라 내 시나리오에 비해 너무 큰 것 같습니다.

그렇다면 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 core의 베타 버전으로 작성되었습니다. 도움이 되었기를 바랍니다.


1
답변 해 주셔서 감사합니다! 이것이 바로 제가 찾던 것입니다. 기본 인증을위한 간단한 솔루션입니다.
Felix

1
이 코드에는 credentialstring.Split ( ':') 사용으로 인한 버그가 있습니다. 콜론이 포함 된 암호를 올바르게 처리하지 못합니다. Felix의 답변에있는 코드에는이 문제가 없습니다.
Phil Dennis

111

이제 올바른 방향을 가리키고 나면 완전한 솔루션이 있습니다.

이것은 들어오는 모든 요청에서 실행되고 요청에 올바른 자격 증명이 있는지 확인하는 미들웨어 클래스입니다. 자격 증명이 없거나 잘못된 경우 서비스는 즉시 401 Unauthorized 오류로 응답 합니다.

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

미들웨어 확장은 서비스 Startup 클래스의 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를 통해 이러한 종류의 솔루션을 제공해야합니다

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기본 인증 을 구현 하여 표준 속성 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;
    }
}

Startup.cs- app.UseMiddleware<BasicAuthenticationMiddleware>();. 이 코드를 사용하면 표준 속성 Autorize로 모든 컨트롤러를 제한 할 수 있습니다.

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

AllowAnonymous응용 프로그램 수준에서 권한 부여 필터를 적용하는 경우 속성을 사용합니다 .


1
귀하의 코드를 사용했지만 Authorize (ActiveAuthenticationSchemes = "Basic")]가 설정되어 있는지 여부에 관계없이 모든 호출에서 미들웨어가 활성화되어 원하지 않는 경우에도 모든 컨트롤러의 유효성이 검사됩니다.
CSharper

이 답변이 마음에
듭니다


솔루션에서 표준 권한 부여 / 익명 허용 속성을 사용할 수 있기 때문에 이것이 답이라고 생각합니다. 그 다음에는 프로젝트 단계 후반에 필요한 다른 인증 체계를 쉽게 사용할 수 있어야합니다
Frederik Gheysels

0

이 공개 Github 리포지토리 https://github.com/boskjoett/BasicAuthWebApi 에서 기본 인증으로 보호되는 엔드 포인트가있는 ASP.NET Core 2.2 웹 API의 간단한 예제를 볼 수 있습니다.


컨트롤러 (SecureValuesController)에서 Authenticated Identity를 사용하려는 경우 Request.User 개체가 비어 있으므로 티켓을 만드는 것만으로는 충분하지 않습니다. 이 ClaimsPrincipal을 AuthenticationHandler의 현재 컨텍스트에 할당해야합니까? 이것이 우리가 이전 WebApi에서 한 방식입니다 ...
pseabury

0

이전 게시물에서 올바르게 언급했듯이 방법 중 하나는 사용자 지정 기본 인증 미들웨어를 구현하는 것입니다. 이 블로그에서 설명과 함께 가장 잘 작동하는 코드를 찾았습니다. 사용자 지정 미들웨어를 사용한 기본 인증

동일한 블로그를 참조했지만 두 가지 수정 작업을 수행해야했습니다.

  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")]


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.