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

Posted

tags:

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

  Web API有两种方式进行身份验证:在宿主程序中的身份验证和使用 HTTP Message Handlers进行身份验证。
  如果你的Web API运行在IIS中,那么身份验证程序就是HTTP Modules,可以使用内置的asp.net身份验证模块进行身份验证,也可以自己写一个身份验证模块完成自定义身份验证。
  当在宿主程序中进行身份验证时,宿主程序会创建一个principal对象,这个对象的类实现了IPrincipal接口,用来代表当前代码运行的安全上下文。宿主通过设置Thread.CurrentPrincipal 将主体附加到当前进程。principal包含一个关联用户信息的Identity 对象,如果用户验证通过,Identity.IsAuthenticated 属性返回true;对于匿名请求,IsAuthenticated 返回false。关于更多的principals信息,参见Role-Based Security。
参考技术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 Yii框架 Yii是一个基于组件、用于开发大型 Web 应用的 高性能 php 框架。Yii 几乎拥有了 所有的特性 ,包括 MVC、DAO/ActiveRecord、I18N/L10N、caching、基于 JQuery 的 AJAX 支持、用户认证和基于角色的访问控制、脚手架、输入验证、部件

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

【中文标题】如何在 Asp.net MVC 中添加 Web Api,然后在同一个应用程序中使用 WebAPI【英文标题】:How to add WebApi in Asp.net MVC and then consume the WebAPI in the same application 【发布时间】:2014-02-12 21:09:13 【问题描述】:

我已经实现了创建 WebApi 本身,我可以从浏览器浏览它并获取输出。

对我不起作用的是,我正在尝试从 MVC 控制器使用 WebAPI,并且我已经在“cshtml”视图中编写了用于调用 WebAPI 的代码。

但这不起作用,因为我在加载页面时遇到错误,我知道我做错了什么。所以第一个问题是:我这样做是否正确,或者在 MVC 项目中创建 WebAPI 部分然后尝试从控制器在同一个 MVC 项目中使用它是完全错误的?

【问题讨论】:

发布您的错误。但是,您为什么要从同一个应用程序中使用 Web API,因为我假设该应用程序已经可以访问该服务所封装的所有代码?为什么要在不需要时花费额外的开销? 我完全同意你的看法,但是有什么技术原因我不能这样做,或者这只是一种错误的实现方式? @Maess、MVC 和 WebAPI 被设计为存在于同一个应用程序中。如果需要,您可以将特定的 API 控制器发送到单独的文件夹,例如根目录中的“API”以将它们分开。这就是为什么你有一个RouteConfig.cs 和一个WebApiConfig.cs 存在于同一个App_Start 文件夹中的原因——因为它预计会在同一个项目中利用这两者。不过,这不是必需的。 @user1570094,您不想从视图内部调用 WebAPI。您想从 MVC 前端控制器内部调用 WebAPI,如下所述。 【参考方案1】:

要回答您的问题,它实际上是“按设计”的,建议您将 WebAPI 和 MVC 客户端放在同一个项目中。这就是为什么在 MVC 项目中同时拥有 RouteConfig.csWebApiConfig.cs 的原因。 RouteConfig.cs 用于您的 MVC 控制器,WebApiConfig.cs 显然用于您的 Api 控制器。

将两者放在同一个项目中很容易。我所做的是在我的根目录中添加一个名为“API”的文件夹,并将我所有的 WebAPI 控制器放在那里。请记住,我相信您知道,WebAPI 控制器和 MVC 控制器之间的唯一区别是 WebAPI 控制器继承 ApiController,这是 System.Web.Http 的一部分(我相信),而 MVC 控制器继承 @ 987654328@ 是System.Web.MVC 的一部分。

以下是使 GET/PUT/DELETE/POST 请求TO您的 WebAPI FROM MVC 前端的正确方法。它是否在同一个项目中并不重要,因为您在控制器的构造函数中指定了 WebAPI URL。如果您的 WebAPI 与前端 MVC 应用程序位于不同的服务器上,则需要启用 CORS 支持,这是 WebAPI 版本 2 及更高版本中提供的功能。

这是从前端 MVC 客户端调用 WebAPI 的正确方法。

在您的控制器页面中,删除与 DbContext、Entity Framework 等有关的任何内容。原因是默认情况下,控制器将希望通过调用 DbContext 来执行 CRUD 操作,而我们不希望这样。我们想调用 WebAPI 来执行此操作。当我提到“控制器”时,我指的是 MVC 控制器,而不是 WebAPI 控制器。

首先,在 MVC 控制器中声明一些成员变量。您的 MVC 控制器的其余部分将使用这些:

    HttpClient client = new HttpClient();
    HttpResponseMessage response = new HttpResponseMessage();
    Uri contactUri = null;

    在您的 MVC 控制器中,为您的控制器创建一个构造函数,如下所示:

    public ContactController()
    
        // set base address of WebAPI depending on your current environment
        // the URL below, if the API is in the same project, will be something 
        // like "http://server/YourProjectName" - replace server with either
        // "localhost", etc.
        client.BaseAddress = new Uri("http://server/YourAPI/");
    
        // Add an Accept header for JSON format.
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
    
    

    将索引操作的代码替换为以下内容。请注意,唯一相关的部分是 client.GetAsync() 调用和 var contacts 分配。对于此问题的上下文,其他所有内容都不是必需的。 client.GetAsync() 中的值应该是您的控制器的名称,前面是您在 WebApiConfig.cs 中设置的任何自定义路由 - 就我而言,我在路由中添加了 api 部分以区分 API 调用和正常调用调用:

    public ActionResult Index()
    
        response = client.GetAsync("api/contact").Result;
        if (response.IsSuccessStatusCode)
        
            var contacts = response.Content.ReadAsAsync<IEnumerable<Contact>>().Result;
            return View(contacts);
        
        else
        
            // add something here to tell the user hey, something went wrong
            return RedirectToAction("Index");
        
    
    

    将 Create 操作(HttpPost 操作)替换为以下内容。同样,唯一重要的部分是 client.PostAsJsonAsync() 部分 - 这就是调用 WebAPI 的 POST 操作的部分,在我的例子中,它负责将新记录插入数据库:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(Contact contact)
    
        // Create a new product
        response = client.PostAsJsonAsync("api/contact", contact).Result;
        if (response.IsSuccessStatusCode)
        
            return RedirectToAction("Index");
        
        else
        
            // add something here to tell the user hey, something went wrong
            return RedirectToAction("Index");
        
    
    

    将 Edit 操作(非 HttpPost 操作)替换为以下内容。这有点棘手,因为为了进行编辑,您必须先检索记录,所以基本上,HttpPost 版本的 Edit 将包含一些类似的代码,还有一行代码执行编辑 POST (PUT)。下面,我们通过向 WebAPI 传递一个特定的记录 ID 来获取响应。因此,就像 Index (GET) 一样,我们只是在做同样的事情,只传递 ID,所以我们只取回一条记录。然后,我们将响应转换为可以在 View 中操作的实际对象:

    public ActionResult Edit(int id = 0)
    
        response = client.GetAsync(string.Format("api/contact/0", id)).Result;
        Contact contact = response.Content.ReadAsAsync<Contact>().Result;
        if (contact == null)
        
            return HttpNotFound();
        
        return View(contact);
    
    

    将 Edit 操作(HttpPost 操作)替换为以下内容。下面,我们通过调用client.GetAsync() 并将主键作为参数(contact_id)传递来获取要编辑的记录。然后,我们从该响应中获取 RequestUri 并将其保存。然后,我们调用 client.PutAsJsonAsync() 并传入 Uri.PathAndQuery(我们刚刚保存的内容)以及要编辑的对象。

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(Contact contact)
    
        response = client.GetAsync(string.Format("api/contact/0", contact.contact_id)).Result;
        contactUri = response.RequestMessage.RequestUri;
        response = client.PutAsJsonAsync(contactUri.PathAndQuery, contact).Result;
        if (response.IsSuccessStatusCode)
        
            return RedirectToAction("Index");
        
        else
        
            // add something here to tell the user hey, something went wrong
            return RedirectToAction("Index");
        
    
    

    将 Delete 操作(非 HttpPost 操作)替换为以下内容。同样,我们通过简单地调用 client.GetAsync() 并将其转换为我的应用知道的实际对象来从数据库中获取记录。

    public ActionResult Delete(int id = 0)
    
        response = client.GetAsync(string.Format("api/contact/0", id)).Result;
        Contact contact = response.Content.ReadAsAsync<Contact>().Result;
    
        if (contact == null)
        
            return HttpNotFound();
        
        return View(contact);
    
    

    最后,将 Delete 操作(HttpPost 操作)替换为以下内容。同样,我们正在执行类似于 Edit 操作的操作。我们正在获取要删除的记录,将其转换为一个对象,然后将该对象传递给client.DeleteAsync() 调用,如下所示。

    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    
        response = client.GetAsync(string.Format("api/contact/0", id)).Result;
        contactUri = response.RequestMessage.RequestUri;
        response = client.DeleteAsync(contactUri).Result;
        return RedirectToAction("Index");
    
    

【讨论】:

非常感谢您提供如此详细而精彩的解释,我完全同意了。但是在这个类似的场景中,我在同一个项目中有一个 WebApi 和 MVC 客户端,我可以通过在视图中编写 jquery 来访问 WebApi,并且该视图由 MVC 控制器作为 ActionResult 返回吗?也请给我一个答案,因为我认为它应该有效。我试过这个,但没有奏效。谢谢。 不错的答案,但登录呢?如果我们在 WebAPI 控制器上使用 [Authorize] 属性,我们如何更改代码?请澄清一下。谢谢! 我在使用 web-api-2 时遇到了问题,您的第 2 步添加了操作索引代码,例如 public ActionResult Index()。基于此question on why ... -ihttpactionresult-instead-of-httpresponsemessage,我假设对于 web-api-2,第 2 步的代码需要更改为public IHttpActionResult Index()。这是正确的吗?

以上是关于如何使用 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 应用程序项目中?