两台服务器身份验证,OAuth 2 从入门到精通(一) - 身份认证服务器

前言

创建认证服务

新建WebAPI项目

Framework 4.6,新建ASP.NET应用程序,选择“Empty”,“Web API”,“No Authentication”。

引入Nuget包管理

使用Nuget包管理器安装用于Owin服务器的类库,打开Nuget Package Manger控制台,使用如下命令安装:

Install-Package Microsoft.AspNet.WebApi.Owin -Version 5.1.2

Install-Package Microsoft.Owin.Host.SystemWeb -Version 2.1.0

添加OWIN启动类

using Microsoft.Owin;

using Owin;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Http;

[assembly: OwinStartup(typeof(AuthenServer.Startup))]

namespace AuthenServer

{

public class Startup

{

public void Configuration(IAppBuilder app)

{

HttpConfiguration config = new HttpConfiguration();

WebApiConfig.Register(config);

app.UseWebApi(config);

}

}

}

Startup类将会在Owin服务启动的时候被调用,请注意“assembly”特性标注了将会被调用的类。

方法中的代码,在基本的WebAPI脚手架中,位于Global,ascx文件中,用于注册API路由及相关运行参数。

在步骤一中,通常VS已经生成了一个“WebApiConfig”类,如果还没有,我们可以现在添加一个,默认将此类放在"App_Start"文件夹中。代码如下:

public static class WebApiConfig

{

public static void Register(HttpConfiguration config)

{

// Web API routes

config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute(

name: "DefaultApi",

routeTemplate: "api/{controller}/{id}",

defaults: new { id = RouteParameter.Optional }

);

var jsonFormatter = config.Formatters.OfType().First();

jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

}

}

删除“Global.ascx”文件。

添加ASP.NET Identity身份识别系统

为简(FU)单(ZA)起见,我们直接使用微软的ASP.NET Identity身份识别系统,先引入Identity组件包:

Install-Package Microsoft.AspNet.Identity.Owin -Version 2.0.1

Install-Package Microsoft.AspNet.Identity.EntityFramework -Version 2.0.1

添加一个用于连接SQL Server数据库的上下文AuthenContext类,以及一个UserViewModel实体类。

public class AuthenContext: IdentityDbContext

{

public AuthenContext(): base("AuthenContext")

{

}

}

-------------------------------------------------------------------------

public class UserViewModel

{

[Required]

[Display(Name = "用户名")]

public string UserName { get; set; }

[Required]

[StringLength(100, ErrorMessage = "{0} 要求最少 {2} 位字符长度。", MinimumLength = 6)]

[DataType(DataType.Password)]

[Display(Name = "密码")]

public string Password { get; set; }

[DataType(DataType.Password)]

[Display(Name = "确认密码")]

[Compare("Password", ErrorMessage = "两次输入的密码不一致。")]

public string ConfirmPassword { get; set; }

}

在Web.config中添加数据库连接字符串配置项

需要注意的是,name值要和上面AuthenContext类的构造函数中使用的参数值一致,都是“AuthenContext”,数据库我们使用LocalDB,数据库名OAuth,数据库文件放在哪儿没有说明,系统会自动存放在Windows当前登录用户的用户文件夹中。有关连接字符串的详细说明,可以查阅 EF 6.0 入门系列 - 数据库连接 。

创建用户身份识别仓储类

第一步我们实现“RegisterUser”及“FindUser”两个方法。ASP.NET Identity System将会帮助我们对密码进行HASH编码等一系列复杂工作,我们只需要简单的将前端传入的用户密码等参数提供给UserManager类。

public class AuthenRepository : IDisposable

{

private AuthenContext _context;

private UserManager _userManager;

public AuthenRepository()

{

_context= new AuthenContext();

_userManager= new UserManager(new UserStore(_context));

}

public async Task RegisterUser(UserViewModel userModel)

{

IdentityUser user = new IdentityUser

{

UserName = userModel.UserName

};

var result = await _userManager.CreateAsync(user, userModel.Password);

return result;

}

public async Task FindUser(string userName, string password)

{

IdentityUser user = await _userManager.FindAsync(userName, password);

return user;

}

public void Dispose()

{

_context.Dispose();

_userManager.Dispose();

}

}

添加AccountAPIController

实现一个Register方法,返回成功或者失败。

[RoutePrefix("api/Account")]

public class AccountAPIController : ApiController

{

private AuthenRepository _repository = null;

public AccountAPIController()

{

_repository = new AuthenRepository();

}

// POST api/Account/Register

[AllowAnonymous]

[Route("Register")]

public async Task Register(UserViewModel userModel)

{

if (!ModelState.IsValid)

{

return BadRequest(ModelState);

}

IdentityResult result = await _repository.RegisterUser(userModel);

IHttpActionResult errorResult = GetErrorResult(result);

if (errorResult != null)

{

return errorResult;

}

return Ok();

}

protected override void Dispose(bool disposing)

{

if (disposing)

{

_repository.Dispose();

}

base.Dispose(disposing);

}

private IHttpActionResult GetErrorResult(IdentityResult result)

{

if (result == null)

{

return InternalServerError();

}

if (!result.Succeeded)

{

if (result.Errors != null)

{

foreach (string 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);

}

return null;

}

}

上面代码中,实现了一个地址为“api/account/register”的Web API方法,可以通过使用HTTP POST方法发送如下的JSON对象来实现新用户注册。

{

"userName": "Taiser",

"password": "SuperPass",

"confirmPassword": "SuperPass"

}

运行此Web应用程序,并打开POSTMAN,发送一个HTTP POST请求到 “http://localhost:port/api/account/register”,运气好的话,你将会收到HTTP 200状态码,同时,在Web.config连接字符串指向的数据库将被创建(第一次运行),同时发送的JSON对象中的用户将会被添加到数据表“AspNetUsers”中。

注册成功

如果你没有修改JSON字串中的用户名,直接再次按下“Send”按钮,将会出现如下情况:

请求无效

添加一个受保护的API

项目右键添加一个空的API控制器OrdersAPIController,我们将确保此控制权只会向已认证用户返回订单信息,为简单起见,演示代码直接使用静态数据。

[RoutePrefix("api/Orders")]

public class OrdersAPIController : ApiController

{

[Authorize]

[Route("")]

public IHttpActionResult Get()

{

return Ok(Order.CreateOrders());

}

}

#region Helpers

public class Order

{

public int OrderID { get; set; }

public string CustomerName { get; set; }

public string ShipperCity { get; set; }

public Boolean IsShipped { get; set; }

public static List CreateOrders()

{

List OrderList = new List

{

new Order {OrderID = 10248, CustomerName = "Taiseer Joudeh", ShipperCity = "Amman", IsShipped = true },

new Order {OrderID = 10249, CustomerName = "Ahmad Hasan", ShipperCity = "Dubai", IsShipped = false},

new Order {OrderID = 10250,CustomerName = "Tamer Yaser", ShipperCity = "Jeddah", IsShipped = false },

new Order {OrderID = 10251,CustomerName = "Lina Majed", ShipperCity = "Abu Dhabi", IsShipped = false},

new Order {OrderID = 10252,CustomerName = "Yasmeen Rami", ShipperCity = "Kuwait", IsShipped = true}

};

return OrderList;

}

}

#endregion

需要注意的是我们在“Get”方法前面添加了“Authorize”特性,这样一来,当你向“http://localhost:port/api/orders”发送HTTP GET请求时,将会收到401 未授权状态码,因为此时,你发送的请求中并未包含任何验证信息。

添加OAuth Berrer Tokens(承载令牌)生成代码

第一步,引入如下OAuth包

Install-Package Microsoft.Owin.Security.OAuth -Version 2.1.0

第二步,在Startup类中添加“ConfigureOAuth”以配置API使用OAuth验证流程。

public class Startup

{

public void Configuration(IAppBuilder app)

{

ConfigureOAuth(app);

// 此处为原有代码,省略......

}

public void ConfigureOAuth(IAppBuilder app)

{

OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()

{

AllowInsecureHttp = true,

TokenEndpointPath = new PathString("/token"),

AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),

Provider = new SimpleAuthorizationServerProvider()

};

// 令牌生成

app.UseOAuthAuthorizationServer(OAuthServerOptions);

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

}

}

“OAuthAuthorizationServerOptions” 配置

令牌的过期时间为24小时,也就是说,用户在24小时候,还使用之前的令牌,他的请求将会被拒绝,收到401状态码。

用户申请令牌的时候,使用 “SimpleAuthorizationServerProvider” 进行用户验证。

实现SimpleAuthorizationServerProvider”类

public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider

{

public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)

{

context.Validated();

}

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)

{

context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

using (AuthRepository _repo = new AuthRepository())

{

IdentityUser user = await _repo.FindUser(context.UserName, context.Password);

if (user == null)

{

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

return;

}

}

var identity = new ClaimsIdentity(context.Options.AuthenticationType);

identity.AddClaim(new Claim("sub", context.UserName));

identity.AddClaim(new Claim("role", "user"));

context.Validated(identity);

}

}

“SimpleAuthorizationServerProvider” 覆盖基类 “OAuthAuthorizationServerProvider“ 的两个方法,分别是 “ValidateClientAuthentication” 和 “GrantResourceOwnerCredentials”。

第一个方法职责是验证连接客户端, The first method is responsible for validating the “Client”, 在我们的例子中,我们只有一个客户端,所以我们总是返回验证成功。

第二个方法 “GrantResourceOwnerCredentials” 职责是验证用户名和密码, 使用前面编写的 “AuthenRepository” 类方法 “FindUser” 来验证用户名和密码的有效性。

如果凭据有效,我们将创建“ClaimsIdentity”类,并将认证类型传递给它(在我们的例子中是“bearer token”),然后我们将在签名的令牌中添加两个声明 “sub” 及 “role”。您可以在这里添加其他不同的声明,但令牌大小会因此增加。

当我们调用“context.Validated(identity)”时,令牌将由系统自动生成。

如果需要允许在令牌中间件提供商上使用CORS (跨源资源共享 Cross-Origin Resource Sharing),我们需要向Owin上下文添加标题“Access-Control-Allow-Origin”,如果您忘记了这一点,那么当您尝试从浏览器调用它时,生成令牌将失败。 这不允许CORS的令牌中间件提供程序不是ASP.NET Web API,我们将在下一步添加。 这句话貌似是作者的笔误,感觉最开始的 Not that应该是Note that。

在ASP.NET Web API中配置允许CORS

第一步还是引入Nuget包

Install-Package Microsoft.Owin.Cors -Version 2.1.0

第二步,打开 “Startup” 类,在 “Configuration” 方法中添加一行代码:

public void Configuration(IAppBuilder app)

{

HttpConfiguration config = new HttpConfiguration();

ConfigureOAuth(app);

WebApiConfig.Register(config);

app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

app.UseWebApi(config);

}

测试后端API

假设您在下面的步骤中注册了密码为“SuperPass”的用户名“Taiseer”,我们将使用相同的用户名来生成令牌,因此,为了测试这一点,打开您最喜欢的REST客户端应用程序,发出HTTP请求来生成用户“Taiseer”的令牌。我个人偏好使用PostMan。

令牌申请

在Headers页面,配置“content-type” 为“application/x-www-form-urlencoded”。(配图中看不见,自己试一下,我就不再为此占用无谓的互联网资源了)

在Body页面,配置类型为x-www-form-urlencoded,并添加如同中的三个键值"grant_type"、"username"、"password"。

下方的Raw是按下"Send"按钮后,username和password没有错误的话,令牌服务器返回的令牌,我们需要手工复制此令牌字符串,在下一步获取资源时作为Header提交。

接下来,使用申请的令牌来请求订单数据,URL为"http://localhost:23170/api/orders",HTTP GET请求,并在Header 加入“Authorization”键,内容是“Bearer” 和刚刚申请到的令牌字符串(中间加一空格)。

注意:在这儿,我们无需再传递用户名和密码。如下图:

资源获取

一切都正确的话,收到HTTP 状态码200以及所需的受保护数据。如果你修改令牌任意字符,将会收到HTTP 状态码401(未授权)。

到目前为止,我们建立的后端API已经可以从任何一种前端应用进行访问,不管是Web,Winform或者是原生手机APP。

你可能感兴趣的:(两台服务器身份验证)