【问题标题】:ASP.NET Core Web API AuthenticationASP.NET Core Web API 身份验证
【发布时间】:2016-12-22 22:59:17
【问题描述】:

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

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

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

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

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

提前致谢!

【问题讨论】:

    标签: c# asp.net authentication asp.net-core


    【解决方案1】:

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

    这是在每个传入请求上执行的中间件类,并检查请求是否具有正确的凭据。如果不存在凭据或它们有误,服务会立即响应 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/

    【讨论】:

    • 感谢您发布完整的解决方案。但是,我必须添加行 'context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"realm\"");'到“无授权标头”部分,以便让浏览器请求凭据。
    • 这种身份验证在多大程度上是安全的?如果有人嗅探请求头并获取用户名/密码怎么办?
    • @BewarSalah 你必须通过 https 提供这种解决方案
    • 一些控制器应该允许匿名。在这种情况下,此中间件解决方案将失败,因为它将检查每个请求中的授权标头。
    【解决方案2】:

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

    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 版本编写的。希望对您有所帮助。

    【讨论】:

    • 感谢您的回答!这正是我一直在寻找的 - 一个简单的基本身份验证解决方案。
    • 由于使用了credentialstring.Split(':'),此代码中存在一个错误——它不能正确处理包含冒号的密码。 Felix 的答案中的代码没有遇到这个问题。
    【解决方案3】:

    仅将其用于特定控制器,例如使用此:

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

    【讨论】:

      【解决方案4】:

      我认为你可以使用 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]属性放在你想要的Controllers或Actions上。

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

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

      【讨论】:

        【解决方案5】:

        我已经为基本身份验证实现了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&lt;BasicAuthenticationMiddleware&gt;();。使用此代码,您可以使用标准属性 Autorize 限制任何控制器:

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

        如果您在应用程序级别应用授权过滤器,请使用属性AllowAnonymous

        【讨论】:

        • 我使用了您的代码,但我注意到无论是否在每次调用中设置了 Authorize(ActiveAuthenticationSchemes = "Basic")] 中间件都会被激活,从而导致每个控制器在不需要时也得到验证.
        • 我喜欢这个答案
        • 此处的工作示例:jasonwatmore.com/post/2018/09/08/…
        • 我认为这是答案,因为它允许您在解决方案中进一步使用标准授权/允许匿名属性。除此之外,如果需要,在项目后期使用另一个身份验证方案应该很容易
        【解决方案6】:

        正如之前的帖子所说的那样,一种方法是实现自定义的基本身份验证中间件。我在这个博客中找到了最好的工作代码和解释: Basic Auth with custom middleware

        我参考了同一个博客,但不得不进行 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;
          }
          

        【讨论】:

          【解决方案7】:

          您可以使用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")]

          【讨论】:

            【解决方案8】:

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

            【讨论】:

            • 如果您想在控制器 (SecureValuesController) 中使用经过身份验证的身份,创建票证是不够的,因为 Request.User 对象为空。我们还需要将此 ClaimsPrincipal 分配给 AuthenticationHandler 中的当前 Context 吗?这就是我们在旧版 WebApi 中所做的方式...
            【解决方案9】:

            带有 Angular 的 ASP.NET Core 2.0

            https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login

            确保使用认证过滤器类型

            [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

            【讨论】:

              猜你喜欢
              • 2022-01-27
              • 2018-01-28
              • 2017-11-24
              • 2020-07-27
              • 2018-03-15
              • 2012-06-16
              • 2017-10-22
              • 1970-01-01
              • 2015-09-17
              相关资源
              最近更新 更多