如何使用 Web API 来对 MVC 应用程序进行身份验证

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用 Web API 来对 MVC 应用程序进行身份验证相关的知识,希望对你有一定的参考价值。

首先,让我们先更新 API 项目

我们将先对 API 项目进行必要的修改,修改完成之后再切换到 Web 项目对客户端进行更新。

第1步:我们需要一个数据库

在能做任何操作之前我们需要先创建一个数据库。本例中将使用 SQL Server Express。如果你没有安装,可以从这里下载 SQL Server Express。安装完成之后,创建一个名为 CallingWebApiFromMvc 的数据库。这就是第一步要做的。

Api 项目还需要一个数据库连接字符串,否则我们寸步难行。把下面这段代码插入到 Api 项目的Web.config 文件中:

<connectionStrings>
<add name="ApiFromMvcConnection" connectionString="Data Source=(local);Initial Catalog=CallingWebApiFromMvc;Integrated Security=True" providerName="System.Data.SqlClient" /></connectionStrings>

认证(Identity)框架会自动创建我们管理用户所需要的成员关系表,现在不需要担心去提前创建它们。

第2步:添加相关的Nuget包

接下来我们添加用于OWIN和Windows认证的Nuget包。打开包管理控制台,切换Api项目为缺省项目,输入以下命令:

Install-Package Microsoft.AspNet.WebApi.Owin
Install-Package Microsoft.Owin.Host.SystemWeb
Install-Package Microsoft.AspNet.Identity.EntityFramework
Install-Package Microsoft.AspNet.Identity.Owin

使用这些包可以在我们的应用中启动一个OWIN服务器,然后通过EntityFramework把我们的用户保存到SQL Server。

第3步:添加管理用户的Identity类

我们使用基于Windows认证机制之上的Entity框架来管理数据库相关的业务。首先我们需要添加一些用于处理的类。在Api项目里添加一个Identity目录作为我们要添加类的命名空间。然后添加如下的类:

public class ApplicationUser : IdentityUser



public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
public ApplicationDbContext() : base("ApiFromMvcConnection")
public static ApplicationDbContext Create()
return new ApplicationDbContext();



注意我们传给基类构造函数的参数ApiFromMvcConnection要和Web.config中的连接字符串中的name相匹配。

public class ApplicationUserManager : UserManager<ApplicationUser>
public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store)

public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
var manager = new ApplicationUserManager(new UserStore<ApplicationUser> (context.Get<ApplicationDbContext> ()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser> (manager)

AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
;
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator

RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
; var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)

manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser> (dataProtectionProvider.Create("ASP.NET Identity"));
return manager;



第4步:添加OWIN启动类

为了让我们的应用程序作为OWIN服务器上运行,我们需要在应用程序启动时初始化。我们可以通过一个启动类做到这一点。我们将装点这个类的
OwinStartup属性,因此在应用程序启动时触发。这也意味着,我们可以摆脱的Global.asax和移动它们的
Application_Start代码转换成我们新的启动类。

using Microsoft.Owin;

[assembly: OwinStartup(typeof(Levelnis.Learning.CallingWebApiFromMvc.Api.Startup))]
namespace Levelnis.Learning.CallingWebApiFromMvc.Api

using System;
using System.Web.Http;
using Identity;
using Microsoft.Owin.Security.OAuth;
using Owin;
using Providers;
public class Startup
public void Configuration(IAppBuilder app)

GlobalConfiguration.Configure(WebApiConfig.Register);
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager> (ApplicationUserManager.Create); var oAuthOptions = new OAuthAuthorizationServerOptions

TokenEndpointPath = new PathString("/api/token"),
Provider = new ApplicationOAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
AllowInsecureHttp = true
;
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(oAuthOptions);




在应用程序启动时,我们正在建立自己的服务器。在这里,我们配置令牌端点并设置自己的自定义提供商,我们用我们的用户进行身份验证。在我们的例子中,我们使用了ApplicationOAuthProvider类。让我们来看看现在:

第5步:添加OAuth的提供商

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)

context.Validated();
return Task.FromResult<object> (null);

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)

var userManager = context.OwinContext.GetUserManager<ApplicationUserManager> ();
var user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)

context.SetError("invalid_grant", "The user name or password is incorrect."); return;

var oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType); var cookiesIdentity = await user.GenerateUserIdentityAsync(userManager, CookieAuthenticationDefaults.AuthenticationType); var properties = CreateProperties(user.UserName); var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);

private static AuthenticationProperties CreateProperties(string userName)

var data = new Dictionary<string, string>


"userName", userName

;
return new AuthenticationProperties(data);



我们感兴趣的是这里2种方法。第一,ValidateClientAuthentication,只是验证客户端。我们有一个客户端,所以返回成
功。这是一个异步方法签名但没有异步调用发生。正因为如此,我们可以离开了异步修改,但我们必须返回一个任务自己。我们增加了一个名为
GenerateUserIdentityAsync的ApplicationUser,它看起来像这样的方法:

public class ApplicationUser : IdentityUser
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)

var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
return userIdentity;



第6步:注册一个新用户 - API端
所以,我们有到位的所有Identity类管理用户。让我们来看看RegisterController,将新用户保存到我们的数据库。它接受一个RegisterApi模式,这是简单的:

public class RegisterApiModel

[Required]
[EmailAddress] public string Email get; set;

[Required]
[StringLength(100, ErrorMessage = "The 0 must be at least 2 characters long.", MinimumLength = 6)]
public string Password
get; set;


[Required]
[Display(Name = "Confirm Password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword get; set;


控制器本身,如果注册成功只是返回一个200 OK响应。如果验证失败,则返回401错误请求的响应。

public class RegisterController : ApiController
private ApplicationUserManager UserManager
get
return Request.GetOwinContext().GetUserManager<ApplicationUserManager> ();

public IHttpActionResult Post(RegisterApiModel model)
if (!ModelState.IsValid)
return BadRequest(ModelState);
var user = new ApplicationUser

Email = model.Email,
UserName = model.Email,
EmailConfirmed = true
;
var result = UserManager.Create(user, model.Password);
return result.Succeeded ? Ok() : GetErrorResult(result);

private IHttpActionResult GetErrorResult(IdentityResult result)

if (result == null)

return InternalServerError();

if (result.Errors != null)

foreach (var error in result.Errors)

ModelState.AddModelError("", error);


if (ModelState.IsValid)

// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();

return BadRequest(ModelState);

参考技术A 1 控制器基类ApiControllerBase
[csharp] view plaincopy

///
/// Controller的基类,用于实现适合业务场景的基础功能
///
///
[BasicAuthentication]
public abstract class ApiControllerBase : ApiController



2 权限属性BaseAuthenticationAttribute
[csharp] view plaincopy

///
/// 基本验证Attribtue,用以Action的权限处理
///
public class BasicAuthenticationAttribute : ActionFilterAttribute

///
/// 检查用户是否有该Action执行的操作权限
///
///
public override void OnActionExecuting(HttpActionContext actionContext)

//检验用户ticket信息,用户ticket信息来自调用发起方
if (actionContext.Request.Headers.Authorization != null)

//解密用户ticket,并校验用户名密码是否匹配
var encryptTicket = actionContext.Request.Headers.Authorization.Parameter;
if (ValidateUserTicket(encryptTicket))
base.OnActionExecuting(actionContext);
else
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);

else

//检查web.config配置是否要求权限校验
bool isRquired = (WebConfigurationManager.AppSettings["WebApiAuthenticatedFlag"].ToString() == "true");
if (isRquired)

//如果请求Header不包含ticket,则判断是否是匿名调用
var attr = actionContext.ActionDescriptor.GetCustomAttributes().OfType();
bool isAnonymous = attr.Any(a => a is AllowAnonymousAttribute);
//是匿名用户,则继续执行;非匿名用户,抛出“未授权访问”信息
if (isAnonymous)
base.OnActionExecuting(actionContext);
else
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);

else

base.OnActionExecuting(actionContext);



///
/// 校验用户ticket信息
///
///
///
private bool ValidateUserTicket(string encryptTicket)

var userTicket = FormsAuthentication.Decrypt(encryptTicket);
var userTicketData = userTicket.UserData;
string userName = userTicketData.Substring(0, userTicketData.IndexOf(":"));
string password = userTicketData.Substring(userTicketData.IndexOf(":") + 1);
//检查用户名、密码是否正确,验证是合法用户
//var isQuilified = CheckUser(userName, password);
return true;



3 api服务Controller实例
[csharp] view plaincopy

public class ProductController : ApiControllerBase

[HttpGet]
public object Find(string id)

return ProductServiceInstance.Find(2);

// GET api/product/5
[HttpGet]
[AllowAnonymous]
public Product Get(string id)

var headers = Request.Headers;
var p = ProductServiceInstance.GetById(long.Parse(id));
if (p == null)

throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest)
Content = new StringContent("id3 not found"), ReasonPhrase = "product id not exist." );

return p;



4. 其它配置说明
1)、 Mvc前端Web.Config 配置
[html] view plaincopy

<</SPAN>system.web>
<</SPAN>compilation debug="true" targetFramework="4.5">
<</SPAN>assemblies>
<</SPAN>add assembly="System.Web.Http.Data.Helpers, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</</SPAN>assemblies>
</</SPAN>compilation>
<</SPAN>httpRuntime targetFramework="4.5" />
<</SPAN>authentication mode="Forms">
<</SPAN>forms loginUrl="~/Account/Login" defaultUrl="~/Home/Index" protection="All" timeout="90" name=".AuthCookie"></</SPAN>forms>
</</SPAN>authentication>
<</SPAN>machineKey validationKey="3FFA12388DDF585BA5D35E7BC87E3F0AB47FBBEBD12240DD3BEA2BEAEC4ABA213F22AD27E8FAD77DCFEE306219691434908D193A17C1FC8DCE51B71A4AE54920" decryptionKey="ECB6A3AF9ABBF3F16E80685ED68DC74B0B13CCEE538EBBA97D0B893139683B3B" validation="SHA1" decryption="AES" />
</</SPAN>system.web>

machineKey节点配置,是应用于对用户ticket数据加密和解密。
2)、WebApi服务端Web.Config配置
[html] view plaincopy

<</SPAN>system.web>
<</SPAN>machineKey validationKey="3FF112388DDF585BA5D35E7BC87E3F0AB47FBBEBD12240DD3BEA2BEAEC4ABA213F22AD27E8FAD77DCFEE306219691434908D193A17C1FC8DCE51B71A4AE54920" decryptionKey="ECB6A3AF9ABBF3F16E80685ED68DC74B0B13CCEE538EBBA97D0B893139683B3B" validation="SHA1" decryption="AES" />
</</SPAN>system.web>

machineKey节点配置,是应用于对用户ticket数据加密和解密。
参考技术B 首先,让我们先更新 API 项目

我们将先对 API 项目进行必要的修改,修改完成之后再切换到 Web 项目对客户端进行更新。

第1步:我们需要一个数据库

在能做任何操作之前我们需要先创建一个数据库。本例中将使用 SQL Server Express。如果你没有安装,可以从这里下载 SQL Server Express。安装完成之后,创建一个名为 CallingWebApiFromMvc 的数据库。这就是第一步要做的。

Api 项目还需要一个数据库连接字符串,否则我们寸步难行。把下面这段代码插入到 Api 项目的Web.config 文件中:

<connectionStrings>
<add name="ApiFromMvcConnection" connectionString="Data Source=(local);Initial Catalog=CallingWebApiFromMvc;Integrated Security=True" providerName="System.Data.SqlClient" /></connectionStrings>
认证(Identity)框架会自动创建我们管理用户所需要的成员关系表,现在不需要担心去提前创建它们。

如何使用 ASP.NET 5 MVC 6 保护 Web API

【中文标题】如何使用 ASP.NET 5 MVC 6 保护 Web API【英文标题】:How to protect a Web API using ASP.NET 5 MVC 6 【发布时间】:2015-06-22 20:54:37 【问题描述】:

我有一个不错的 ASP.NET 5 / MVC 6 应用程序正在运行。基本上出于这个目的,它只是您在开始一个新项目时获得的普通示例应用程序,以使其保持简单。到目前为止,我可以:

注册用户 登录 退出 保护页面(强制登录等)

现在,我想要为应用程序提供一种 API 机制来登录并获取身份验证令牌。具体来说,我正在开发两个要测试的移动应用程序,一个使用 Angular / Cordova,一个使用 Xamarin。

我从高处到低处,似乎都找不到一个例子来说明如何使这项工作发挥作用。到目前为止,我发现的每个示例都假设用户将通过正常的 Web 表单/发布周期登录,然后被带到加载 Angular 的页面,并且身份验证令牌已经在浏览器中。

MVC 控制器的 AccountController.cs 文件中的相关代码如下。我最终想要的是等效功能,但来自一个纯 API 调用,允许 Angular/Xamarin 向它发送用户名/密码并取回身份验证令牌或失败。

    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
    
        ViewBag.ReturnUrl = returnUrl;
        if (ModelState.IsValid)
        
            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, set shouldLockout: true
            var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
            if (result.Succeeded)
            
                return RedirectToLocal(returnUrl);
            
            if (result.RequiresTwoFactor)
            
                return RedirectToAction("SendCode", new  ReturnUrl = returnUrl, RememberMe = model.RememberMe );
            
            if (result.IsLockedOut)
            
                return View("Lockout");
            
            else
            
                ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                return View(model);
            
        

        // If we got this far, something failed, redisplay form
        return View(model);
    

使用 ASP.NET MVC 6 保护 Web API 的推荐方法是什么?

【问题讨论】:

这可以帮助你:***.com/a/25928738/1295965 这可以帮助你bitoftech.net/2014/06/01/… 它不是 vnext,但我能够将这种方法迁移到 vnext。 您必须使用“个人账户”实现 API。这很简单。您使用用户名/密码进行注册,并且 Web API 为您提供了一个令牌,您可以使用它来访问 API。 我有一个正在开发的应用程序,但我无法向公众分享该代码,因为它仍在进行中并且是商业项目。许多开发人员都需要这个特定的集合代码,所以我会在这个周末专门写一篇关于这个的完整博客文章,所以将赏金再延长 1 周。 @alekkowalczyk 您是如何设法创建令牌的? ASP.NET 5 没有内置 OAuth 授权服务器。 【参考方案1】:

我认为保护 WebApi2 的推荐方法是通过授权服务器。授权服务器负责生成令牌。但基于this,作为Katana3 一部分的基于OAuth2 的授权服务器已从Asp.Net 5 中删除。

我假设您的应用还不是实时应用,因为 ASP.NET 5 和 MVC 6 都还没有处于最终发布阶段。因此,如果您可以更改您的身份/身份验证过程,您可以使用 Thinktecture 的IdentityServer3。

Dominick Baier 已在博客中介绍了 The State of Security on ASP.NET 5 and MVC 6 以及 IdSvr3 的作用。该博客有一个指向示例 API 控制器的 Github 存储库的链接,还有一个 API 客户端。它还具有 MVC Web 应用程序的示例。 And it can work with Asp.Net Identity.

更新:

如果这对您不起作用,您可以尝试AspNet.Security.OpenIdConnect.Server。请注意,它在 Github 上有未解决的问题,因此您可能会在使用它时遇到问题。还要注意它的依赖关系,尤其是 azureadwebstacknightly

请注意,ASP.NET 5 和 MVC 6 可能处于稳定的 beta 版本中,但它们仍处于 beta 版本中。它仍然可以改变。

此外,IdSvr3 v2.0 可能处于其最终版本,但它是由一个单独的团队开发的。而且它仅在 2 周前发布,因此恕我直言,就像大多数软件一样,您可能会遇到可能错过测试的事情。请注意 ASP.NET Team,上周,tweeted 关于 IdSvr3 (v2.0) 的发布,所以看起来他们正在认可它。

【讨论】:

有趣。我正在研究他们的解决方案。 IdentityServer3 的不幸之处在于它们不支持 Core CLR。​​ 还没有,但他们计划这样做。 Dominick 在我引用/链接到的 Asp.net OAuth2 问题日志上发布的回复中对此发表了评论。 仅供参考,示例不起作用。我必须进行多项更改才能启动它。 @Martin,如果您在相关的 Github 存储库问题跟踪中记录您遇到的问题,它可能会帮助其他使用示例的人,如果它不存在的话。 我已经记录了几个问题(例如两个 Startup.Configure 方法,无法解析包等)。他们提供的样本不起作用。【参考方案2】:

这是我承诺的博客文章,这是初稿,对有 ASP.NET MVC 5 经验的人会有所帮助:Bookstore - Web API with Authorization

Github 上的完整源代码:https://github.com/kbajpai/bookstore

授权 API:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using Bookstore.Models;

namespace Bookstore.Controllers

  public class BooksController : ApiController
  
    private BooksDbContext db = new BooksDbContext();

    // GET: api/Books
    [Authorize(Roles="superuser,user")]
    public IQueryable<Book> GetBooks()
    
      return db.Books;
    

    // GET: api/Books/5
    [ResponseType(typeof(Book))]
    [Authorize(Roles = "superuser,user")]
    public async Task<IHttpActionResult> GetBook(string id)
    
      Book book = await db.Books.FindAsync(id);
      if (book == null)
      
        return NotFound();
      

      return Ok(book);
    

    // PUT: api/Books/5
    [ResponseType(typeof(void))]
    [Authorize(Roles = "superuser")]
    public async Task<IHttpActionResult> PutBook(string id, Book book)
    
      if (!ModelState.IsValid)
      
        return BadRequest(ModelState);
      

      if (id != book.Id)
      
        return BadRequest();
      

      db.Entry(book).State = EntityState.Modified;

      try
      
        await db.SaveChangesAsync();
      
      catch (DbUpdateConcurrencyException)
      
        if (!BookExists(id))
        
          return NotFound();
        
        else
        
          throw;
        
      

      return StatusCode(HttpStatusCode.NoContent);
    

    // POST: api/Books
    [Authorize(Roles = "superuser")]
    [ResponseType(typeof(Book))]
    public async Task<IHttpActionResult> PostBook(Book book)
    
      if (!ModelState.IsValid)
      
        return BadRequest(ModelState);
      

      db.Books.Add(book);

      try
      
        await db.SaveChangesAsync();
      
      catch (DbUpdateException)
      
        if (BookExists(book.Id))
        
          return Conflict();
        
        else
        
          throw;
        
      

      return CreatedAtRoute("DefaultApi", new  id = book.Id , book);
    

    // DELETE: api/Books/5
    [Authorize(Roles = "superuser")]
    [ResponseType(typeof(Book))]
    public async Task<IHttpActionResult> DeleteBook(string id)
    
      Book book = await db.Books.FindAsync(id);
      if (book == null)
      
        return NotFound();
      

      db.Books.Remove(book);
      await db.SaveChangesAsync();

      return Ok(book);
    

    protected override void Dispose(bool disposing)
    
      if (disposing)
      
        db.Dispose();
      
      base.Dispose(disposing);
    

    private bool BookExists(string id)
    
      return db.Books.Count(e => e.Id == id) > 0;
    
  

【讨论】:

问题是针对 ASP.NET 5 MVC 6,而不是 ASP.NET MVC 5。 MVC6 与 MVC5 有很大不同吗?这个想法是一样的。 是的Kunal,MVC6与MVC5有很大不同 @KunalB。真是太不可思议了。随着框架到组件的全新分解以及跨平台的重建......这确实是 MVC 的重生,迁移场景在许多方面都变得微不足道。

以上是关于如何使用 Web API 来对 MVC 应用程序进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Web API 来对 MVC 应用程序进行身份验证

如何使用 Web API 来对 MVC 应用程序进行身份验证

如何使用 ASP.NET 5 MVC 6 保护 Web API

如何在 Asp.net MVC 中添加 Web Api,然后在同一个应用程序中使用 WebAPI

如何将自定义标头从 mvc 项目发送到 Web api 项目?

如何将 Web API 添加到现有的 ASP.NET MVC (5) Web 应用程序项目中?