使用自定义令牌身份验证保护 ASP.NET Web API






4.92/5 (61投票s)
在本文中,我们将学习如何使用自定义令牌身份验证来保护 asp.net web API。
引言
在当今的开发时代,我们出于各种目的使用 Web API 来共享数据,或用于绑定网格、下拉列表和其他控件,但如果我们不保护这些 API,那么访问您的 Web 应用程序或服务的其他人可能会以某种方式滥用它,而且我们正处于客户端框架(JavaScript、Angular js、React js、Express js、Common js 等)的时代,如果您使用其中一个客户端框架,那么您就使用了 Web 服务或 Web API。获取或发布到服务器的数据是真实的,而且位于客户端安全性较低,您需要付出额外的努力来保护它。
在本文中,我们将学习这额外的内容,保护 Web API 的过程始于注册过程。在此部分,我们将首先注册一个用户,用户注册后,接下来注册的用户将登录应用程序,登录应用程序后,用户需要注册一个将使用此服务的公司,公司注册后,下一步我们将获得 ClientID
和 ClientSecert
密钥。
获取密钥后,下一步我们将使用这些密钥进行身份验证。第一次访问 API 的请求必须附带有效的 ClientID
和 ClientSecert
。接下来,它将验证密钥,然后将以 Token 作为响应,您需要在每次请求中使用此 Token 来验证您是有效用户,此 Token 的有效期为 30 分钟,但如果您想根据需要提供自定义时间,也可以这样做。
此外,此 Token 使用 AES 256 加密算法进行保护。
进程
- 注册用户
- 登录
- 注册公司
- 获取
ClientID
和ClientSecert
- 进行身份验证以获取 Token
- 使用 Token 进行身份验证和访问数据
数据库部分
在本部分中,我们创建了一个名为“MusicDB
”的数据库,其中包含五个我们将用于应用程序的表。
- 注册用户:存储用户信息
- 注册公司:存储公司信息
- 客户端密钥:存储
ClientID
和ClientSecert
- TokensManager:存储所有 Token 信息
- Music Store:存储客户端访问的所有音乐信息
创建 WEB API 应用程序
在本部分中,我们将创建一个简单的 Web API 应用程序,为此我选择了“ASP.NET Web Application (.NET Framework)”模板,并将项目命名为“MusicAPIStore
”,然后,我们将选择“Web API”模板来创建项目。
创建项目后,项目完整视图如下。
使用 Entity Framework Code First 方法将应用程序连接到数据库
第一步是在 Web.config 中添加数据库连接字符串。
第二步是根据“MusicDB
”数据库中的表添加 Model。
下面的快照显示,我们创建了所有表的 Model,名称与表名相同。
第三步是创建一个名为 DatabaseContext
的类,该类将继承 DbContext
类,然后在此类中我们将添加一个 DbSet
对象。
Databasecontext 类的代码片段
using MusicAPIStore.Models;
using System.Data.Entity;
namespace MusicAPIStore.Context
{
public class DatabaseContext : DbContext
{
public DatabaseContext() : base("DefaultConnection")
{
}
public DbSet<RegisterUser> RegisterUser { get; set; }
public DbSet<RegisterCompany> RegisterCompany { get; set; }
public DbSet<TokensManager> TokensManager { get; set; }
public DbSet<ClientKeys> ClientKeys { get; set; }
public DbSet<MusicStore> MusicStore { get; set; }
}
}
完成添加 DatabaseContext
类后,下一步我们将向项目中添加一个名为 Repository 的文件夹。
向应用程序添加 Repository 文件夹
在此应用程序中,我们将使用存储库模式。
为了存储接口和具体类,我们需要向应用程序添加 Repository 文件夹。
我们已完成向应用程序添加 Repository 文件夹。接下来,我们将添加 RegisterUser
、Model
、Interface
、Concrete 类、Controller 和 Views。
注册用户
RegisterUser
Model- 添加
IRegisterUser
接口 - 添加
RegisterUserConcrete
RegisterUserConcrete
将继承IRegisterUser
- 添加控制器
- 添加视图
让我们看看 RegisterUser
Model。
1. RegisterUser Model
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MusicAPIStore.Models
{
[Table("RegisterUser")]
public class RegisterUser
{
[Key]
public int UserID { get; set; }
[Required(ErrorMessage = "Required Username")]
[StringLength(30, MinimumLength = 2,
ErrorMessage = "Username Must be Minimum 2 Charaters")]
public string Username { get; set; }
[DataType(DataType.Password)]
[Required(ErrorMessage = "Required Password")]
[MaxLength(30,ErrorMessage = "Password cannot be Greater than 30 Charaters")]
[StringLength(31, MinimumLength = 7 ,
ErrorMessage ="Password Must be Minimum 7 Charaters")]
public string Password { get; set; }
public DateTime CreateOn { get; set; }
[Required(ErrorMessage = "Required EmailID")]
[RegularExpression(@"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}",
ErrorMessage = "Please enter Valid Email ID")]
public string EmailID { get; set; }
}
}
添加 RegisterUser
Model 后,下一步我们将添加 Interface IRegisterUser
,在此接口中我们将声明所需的方法。
2. IRegisterUser 接口
我们将在创建的 Repository 文件夹中添加 IRegisterUser
接口。
此接口包含四个方法
Add
(将用户数据插入数据库)ValidateRegisteredUser
(验证用户是否已存在于数据库中,返回布尔值)
ValidateUsername
(验证
Username
是否已存在于数据库中,返回布尔值)GetLoggedUserID
(通过Username
和Password
获取UserID
)
代码片段
using MusicAPIStore.Models;
namespace MusicAPIStore.Repository
{
public interface IRegisterUser
{
void Add(RegisterUser registeruser);
bool ValidateRegisteredUser(RegisterUser registeruser);
bool ValidateUsername(RegisterUser registeruser);
int GetLoggedUserID(RegisterUser registeruser);
}
}
添加 IRegisterUser
接口后,下一步我们将添加 RegisterUserConcrete
类到 Repository 文件夹中。
3. RegisterUserConcrete
我们将在创建的 Repository 文件夹中添加 RegisterUserConcrete
类。
添加 RegisterUserConcrete
类后,下一步我们将使其继承自 IRegisterUser
接口并实现 IRegisterUser
接口中的所有方法。
代码片段
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MusicAPIStore.Models;
using MusicAPIStore.Context;
namespace MusicAPIStore.Repository
{
public class RegisterUserConcrete : IRegisterUser
{
DatabaseContext _context;
public RegisterUserConcrete()
{
_context = new DatabaseContext();
}
public void Add(RegisterUser registeruser)
{
_context.RegisterUser.Add(registeruser);
_context.SaveChanges();
}
public int GetLoggedUserID(RegisterUser registeruser)
{
var usercount = (from User in _context.RegisterUser
where User.Username == registeruser.Username &&
User.Password == registeruser.Password
select User.UserID).FirstOrDefault();
return usercount;
}
public bool ValidateRegisteredUser(RegisterUser registeruser)
{
var usercount = (from User in _context.RegisterUser
where User.Username == registeruser.Username &&
User.Password == registeruser.Password
select User).Count();
if (usercount > 0)
{
return true;
}
else
{
return false;
}
}
public bool ValidateUsername(RegisterUser registeruser)
{
var usercount = (from User in _context.RegisterUser
where User.Username == registeruser.Username
select User).Count();
if (usercount > 0)
{
return true;
}
else
{
return false;
}
}
}
}
添加 RegisterUserConcrete
类后,下一步我们将添加 RegisterUserController
。
4. 添加 RegisterUser Controller
要添加控制器,只需右键单击 Controllers 文件夹,然后选择 Add -> Inside that select Controller,选择 Add。之后,将弹出一个名为“Add Scaffold”的新对话框,用于选择要添加的控制器类型,我们将只选择“MVC5Controller - Empty”并单击Add 按钮。之后,将弹出名为“Add Controller”的新对话框,要求输入 Controller 名称。在此,我们将控制器命名为 RegisterUserController
并单击Add 按钮。
注意:接下来,我们将在 RegisterUser
Controller 的构造函数中初始化一个对象。
代码片段
using MusicAPIStore.AES256Encryption;
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Web.Mvc;
namespace MusicAPIStore.Controllers
{
public class RegisterUserController : Controller
{
IRegisterUser repository;
public RegisterUserController()
{
repository = new RegisterUserConcrete();
}
// GET: RegisterUser/Create
public ActionResult Create()
{
return View(new RegisterUser());
}
// POST: RegisterUser/Create
[HttpPost]
public ActionResult Create(RegisterUser RegisterUser)
{
try
{
if (!ModelState.IsValid)
{
return View("Create", RegisterUser);
}
// Validating Username
if (repository.ValidateUsername(RegisterUser))
{
ModelState.AddModelError("", "User is Already Registered");
return View("Create", RegisterUser);
}
RegisterUser.CreateOn = DateTime.Now;
// Encrypting Password with AES 256 Algorithm
RegisterUser.Password = EncryptionLibrary.EncryptText(RegisterUser.Password);
// Saving User Details in Database
repository.Add(RegisterUser);
TempData["UserMessage"] = "User Registered Successfully";
ModelState.Clear();
return View("Create", new RegisterUser());
}
catch
{
return View();
}
}
}
}
在此 Controller 中,我们添加了两个 Create 的 Action 方法,一个用于处理 [HttpGet]
请求,另一个用于处理 [HttpPost]
请求。在 [HttpPost]
请求中,我们将首先 Validate
检查 Username
是否已存在。如果不存在,我们将创建用户,接下来我们还将接收密码作为输入,我们不能将其以明文形式存储在数据库中。我们需要将其以加密格式存储。为此,我们将使用 AES 256 算法。
5. RegisterUser View
注册用户后,以下是在 RegisterUser
表中存储的详细信息。
6. RegisterUser 表
完成注册部分后,下一步我们将创建一个登录页面。
2. 登录
在本部分中,我们将在其中添加具有 Login 和 Logout Action 方法的 Login Controller。
这里,我们不需要添加新的接口或具体类,因为我们已经创建了一个 RegisterUserConcrete
方法,其中包含 Login Controller 所需的方法。
代码片段
using MusicAPIStore.AES256Encryption;
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MusicAPIStore.Controllers
{
public class LoginController : Controller
{
IRegisterUser _IRegisterUser;
public LoginController()
{
_IRegisterUser = new RegisterUserConcrete();
}
public ActionResult Login()
{
return View(new RegisterUser());
}
[HttpPost]
public ActionResult Login(RegisterUser RegisterUser)
{
try
{
if (string.IsNullOrEmpty(RegisterUser.Username) &&
(string.IsNullOrEmpty(RegisterUser.Password)))
{
ModelState.AddModelError("", "Enter Username and Password");
}
else if (string.IsNullOrEmpty(RegisterUser.Username))
{
ModelState.AddModelError("", "Enter Username");
}
else if (string.IsNullOrEmpty(RegisterUser.Password))
{
ModelState.AddModelError("", "Enter Password");
}
else
{
RegisterUser.Password =
EncryptionLibrary.EncryptText(RegisterUser.Password);
if (_IRegisterUser.ValidateRegisteredUser(RegisterUser))
{
var UserID = _IRegisterUser.GetLoggedUserID(RegisterUser);
Session["UserID"] = UserID;
return RedirectToAction("Create", "RegisterCompany");
}
else
{
ModelState.AddModelError("", "User is Already Registered");
return View("Create", RegisterUser);
}
}
return View("Login", RegisterUser);
}
catch
{
return View();
}
}
public ActionResult Logout()
{
Session.Abandon();
return RedirectToAction("Login", "Login");
}
}
}
完成登录后,下一步我们将在 Repository 文件夹中创建 Register Company Interface 和 Concrete,然后,我们将添加 RegisterCompanyController
和 Action 方法以及 View。
注意:我们将使用带盐的 AES 256 算法进行加密和解密。
3. 注册公司
在本部分中,我们将注册一个公司,然后为该公司生成 ClientID
和 ClientSecret
。
让我们开始添加一个名为 IRegisterCompany
的接口,其中将包含所有需要由 Concrete
类实现的方法。
IRegisterCompany 代码片段
using MusicAPIStore.Models;
using System.Collections.Generic;
namespace MusicAPIStore.Repository
{
public interface IRegisterCompany
{
IEnumerable<RegisterCompany> ListofCompanies(int UserID);
void Add(RegisterCompany entity);
void Delete(RegisterCompany entity);
void Update(RegisterCompany entity);
RegisterCompany FindCompanyByUserId(int UserID);
bool ValidateCompanyName(RegisterCompany registercompany);
bool CheckIsCompanyRegistered(int UserID);
}
}
我们已声明 Register Company Controller 所需的所有方法。接下来,我们将添加一个名为 RegisterCompanyConcrete
的 Concrete
类,该类将实现 IRegisterCompany
接口。
代码片段
using System;
using System.Collections.Generic;
using System.Linq;
using MusicAPIStore.Models;
using MusicAPIStore.Context;
namespace MusicAPIStore.Repository
{
public class RegisterCompanyConcrete : IRegisterCompany
{
DatabaseContext _context;
public RegisterCompanyConcrete()
{
_context = new DatabaseContext();
}
public IEnumerable<RegisterCompany> ListofCompanies(int UserID)
{
try
{
var CompanyList = (from companies in _context.RegisterCompany
where companies.UserID == UserID
select companies).ToList();
return CompanyList;
}
catch (Exception)
{
throw;
}
}
public void Add(RegisterCompany entity)
{
try
{
_context.RegisterCompany.Add(entity);
_context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
public void Delete(RegisterCompany entity)
{
try
{
var itemToRemove = _context.RegisterCompany.SingleOrDefault
(x => x.CompanyID == entity.CompanyID);
_context.RegisterCompany.Remove(itemToRemove);
_context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
public RegisterCompany FindCompanyByUserId(int UserID)
{
try
{
var Company =
_context.RegisterCompany.SingleOrDefault(x => x.UserID == UserID);
return Company;
}
catch (Exception)
{
throw;
}
}
public bool ValidateCompanyName(RegisterCompany registercompany)
{
try
{
var result = (from company in _context.RegisterCompany
where company.Name == registercompany.Name &&
company.EmailID == registercompany.EmailID
select company).Count();
if (result > 0)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
public bool CheckIsCompanyRegistered(int UserID)
{
try
{
var companyExists = _context.RegisterCompany.Any(x => x.UserID == UserID);
if (companyExists)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
}
}
完成实现接口的所有方法后,下一步我们将添加 RegisterCompany
Controller。
添加 RegisterCompany Controller
在本部分中,我们将添加一个包含 Create 和 index Action 方法的 RegisterCompany
Controller。
在 index Action 方法中,我们将获取公司列表,在 Create [HttpGet]
Action 方法中,我们将根据 UserID
获取公司列表,在 Create [HttpPost]
Action 方法中,我们将将公司数据保存到数据库,在此之前,我们将检查公司名称是否已存在。如果公司名称已存在,我们将显示错误消息“Company is Already Registered”。
要添加 Controller
,请遵循与添加 RegisterUser
Controller 相同的步骤。添加控制器后,我们只需手动添加构造函数和三个 Action 方法,如下所示。
代码片段
using MusicAPIStore.Context;
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MusicAPIStore.Filters;
namespace MusicAPIStore.Controllers
{
[ValidateSessionAttribute]
public class RegisterCompanyController : Controller
{
IRegisterCompany _IRegister;
public RegisterCompanyController()
{
_IRegister = new RegisterCompanyConcrete();
}
// GET: Register
public ActionResult Index()
{
var RegisterList = _IRegister.ListofCompanies(Convert.ToInt32(Session["UserID"]));
return View(RegisterList);
}
// GET: Register/Create
public ActionResult Create()
{
var Company = _IRegister.CheckIsCompanyRegistered
(Convert.ToInt32(Session["UserID"]));
if (Company)
{
return RedirectToAction("Index");
}
return View();
}
// POST: Register/Create
[HttpPost]
public ActionResult Create(RegisterCompany RegisterCompany)
{
try
{
if (!ModelState.IsValid)
{
return View("Create", RegisterCompany);
}
if (_IRegister.ValidateCompanyName(RegisterCompany))
{
ModelState.AddModelError("", "Company is Already Registered");
return View("Create", RegisterCompany);
}
RegisterCompany.UserID = Convert.ToInt32(Session["UserID"]);
RegisterCompany.CreateOn = DateTime.Now;
_IRegister.Add(RegisterCompany);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}
完成添加 Controller 及其 Action 方法后,下一步我们将向 Create 和 index Action 方法添加 View。
添加 Index 和 Company View
在本部分中,我们将添加 Views。要添加 View,只需右键单击 Action 方法内部,然后从菜单列表中选择Add View,将弹出一个名为“Add View”的新对话框。接下来,我们不需要为视图命名,它已设置为默认值,即 Action 方法名称。在 Template 中,选择模板以取决于您要创建的视图(Create
、Index
),并选择 Model(RegisterCompany
)。单击Add 按钮。
添加 View 后,只需保存应用程序并运行,然后第一步是登录应用程序。登录后,您将看到 RegisterCompany
View,只需注册您的公司。
创建公司后,您将能够在下面的快照中看到包含您填写公司详细信息的 Index View。
现在我们已经注册了公司,下一步是获取 Application ClientID 和 Client Secret。
生成 ClientID 和 Client Secret 密钥
在本部分中,我们将为每个已注册的公司生成唯一的 ClientID 和 Client Secret 密钥。
我们使用 RNGCryptoServiceProvider
算法生成这些密钥。
Key Generator 类的代码片段
public static class KeyGenerator
{
public static string GetUniqueKey(int maxSize = 15)
{
char[] chars = new char[62];
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
byte[] data = new byte[1];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetNonZeroBytes(data);
Data = new byte[maxSize];
crypto.GetNonZeroBytes(data);
}
StringBuilder result = new StringBuilder(maxSize);
foreach (byte b in data)
{
result.Append(chars[b % (chars.Length)]);
}
return result.ToString();
}
}
生成 ClientID 和 Client Secret 密钥后,我们将把这些密钥与 UserID
一起插入 ClientKey
表中并显示给用户使用。
我们还在 GenerateKeys
View 上提供了一个ReGenerate Keys 按钮,以便用户如果需要,可以通过再次单击此按钮来生成一对新密钥,这些值将根据 UserID
更新到数据库。
让我们首先看看 ClientKey
Model。
在 Model 文件夹中添加 ClientKey
Model 后,下一步我们将继续添加一个名为 IClientKeys
的 Interface,在此接口中我们将声明五个方法。接口中的方法名称不言自明。
添加 Interface IClientKeys
using MusicAPIStore.Models;
namespace MusicAPIStore.Repository
{
public interface IClientKeys
{
bool IsUniqueKeyAlreadyGenerate(int UserID);
void GenerateUniqueKey(out string ClientID, out string ClientSecert);
int SaveClientIDandClientSecert(ClientKey ClientKeys);
int UpdateClientIDandClientSecert(ClientKey ClientKeys);
ClientKey GetGenerateUniqueKeyByUserID(int UserID);
}
}
添加 Interface 并声明其中的方法后,下一步我们将添加一个 Concrete 类,该类将继承此 IClientKeys
接口。
添加 Class ClientKeysConcrete
在本部分中,我们将添加一个名为 ClientKeysConcrete
的 Concrete
类,该类将继承 IClientKeys
接口并实现其中的所有方法。
using System.Linq;
using MusicAPIStore.Models;
using MusicAPIStore.Context;
using MusicAPIStore.AES256Encryption;
using System.Data.Entity;
namespace MusicAPIStore.Repository
{
public class ClientKeysConcrete : IClientKeys
{
DatabaseContext _context;
public ClientKeysConcrete()
{
_context = new DatabaseContext();
}
public void GenerateUniqueKey(out string ClientID, out string ClientSecert)
{
ClientID = EncryptionLibrary.KeyGenerator.GetUniqueKey();
ClientSecert = EncryptionLibrary.KeyGenerator.GetUniqueKey();
}
public bool IsUniqueKeyAlreadyGenerate(int UserID)
{
bool keyExists = _context.ClientKeys.Any
(clientkeys => clientkeys.UserID.Equals(UserID));
if (keyExists)
{
return true;
}
else
{
return false;
}
}
public int SaveClientIDandClientSecert(ClientKey ClientKeys)
{
_context.ClientKeys.Add(ClientKeys);
return _context.SaveChanges();
}
public ClientKey GetGenerateUniqueKeyByUserID(int UserID)
{
var clientkey = (from ckey in _context.ClientKeys
where ckey.UserID == UserID
select ckey).FirstOrDefault();
return clientkey;
}
public int UpdateClientIDandClientSecert(ClientKey ClientKeys)
{
_context.Entry(ClientKeys).State = EntityState.Modified;
_context.SaveChanges();
return _context.SaveChanges();
}
}
}
如果您查看 ClientKeysConcrete
类,会有一个“GenerateUniqueKey
”方法,该方法生成唯一的 ClientID 和 Client Secret 密钥并返回。接下来,我们将查看“IsUniqueKeyAlreadyGenerate
”方法。此方法检查此用户是否已生成 ClientID 和 Client Secret 密钥。接下来,我们将查看“SaveClientIDandClientSecert
”方法,顾名思义,它将把 ClientID 和 Client Secret 密钥详细信息保存到数据库中。
如果用户从门户网站注销,然后再次访问门户网站以查看其 ClientID 和 Client Secret 密钥,则为了从数据库中获取这些密钥,我们创建了“GetGenerateUniqueKeyByUserID
”方法。
我们将看到的最后一个方法是“UpdateClientIDandClientSecert
”。当用户单击我们将在其中添加的 View 上的“ReGenerate Keys”按钮时,此方法用于更新 ClientID 和 Client Secret 密钥。
添加 ApplicationKeys Controller
在本部分中,我们将添加一个包含两个 GenerateKeys
Action 方法的 ApplicationKeys
Controller。
一个 Action 方法处理 [HttpGet]
部分,另一个处理 [HttpPost]
部分。
在 [HttpGet] GenerateKeys
Action 方法中,我们将首先检查密钥是否已生成。如果未生成,我们将生成新密钥并将其保存到数据库并显示给用户。
当用户单击“ReGenerate Keys”按钮时,将调用 [HttpPost]
GenerateKeys
Action 方法,我们将生成新密钥并将其保存到数据库并显示给用户。
要添加 Controller,请遵循与添加 RegisterUser
Controller 相同的步骤。添加 Controller 后,我们只需手动添加构造函数和两个 Action 方法,如下所示。
ApplicationKeys Controller 代码片段
using MusicAPIStore.Filters;
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Web.Mvc;
namespace MusicAPIStore.Controllers
{
[ValidateSessionAttribute]
public class ApplicationKeysController : Controller
{
IClientKeys _IClientKeys;
IRegisterCompany _IRegisterCompany;
public ApplicationKeysController()
{
_IClientKeys = new ClientKeysConcrete();
_IRegisterCompany = new RegisterCompanyConcrete();
}
// GET: ApplicationKeys/GenerateKeys
[HttpGet]
public ActionResult GenerateKeys()
{
try
{
ClientKey clientkeys = new ClientKey();
// Validating ClientID and ClientSecert already Exists
var keyExists = _IClientKeys.IsUniqueKeyAlreadyGenerate
(Convert.ToInt32(Session["UserID"]));
if (keyExists)
{
// Getting Generate ClientID and ClientSecert Key By UserID
clientkeys = _IClientKeys.GetGenerateUniqueKeyByUserID
(Convert.ToInt32(Session["UserID"]));
}
else
{
string clientID=string.Empty;
string clientSecert = string.Empty;
int companyId = 0;
var company = _IRegisterCompany.FindCompanyByUserId
(Convert.ToInt32(Session["UserID"]));
companyId = company.CompanyID;
//Generate Keys
_IClientKeys.GenerateUniqueKey(out clientID, out clientSecert);
//Saving Keys Details in Database
clientkeys.ClientKeyID = 0;
clientkeys.CompanyID = companyId;
clientkeys.CreateOn = DateTime.Now;
clientkeys.ClientID = clientID;
clientkeys.ClientSecret = clientSecert;
clientkeys.UserID = Convert.ToInt32(Session["UserID"]);
_IClientKeys.SaveClientIDandClientSecert(clientkeys);
}
return View(clientkeys);
}
catch (Exception)
{
throw;
}
}
// POST: ApplicationKeys/GenerateKeys
[HttpPost]
public ActionResult GenerateKeys(ClientKey clientkeys)
{
try
{
string clientID = string.Empty;
string clientSecert = string.Empty;
//Generate Keys
_IClientKeys.GenerateUniqueKey(out clientID, out clientSecert);
//Updating ClientID and ClientSecert
var company = _IRegisterCompany.FindCompanyByUserId
(Convert.ToInt32(Session["UserID"]));
clientkeys.CompanyID = company.CompanyID;
clientkeys.CreateOn = DateTime.Now;
clientkeys.ClientID = clientID;
clientkeys.ClientSecret = clientSecert;
clientkeys.UserID = Convert.ToInt32(Session["UserID"]);
_IClientKeys.UpdateClientIDandClientSecert(clientkeys);
return RedirectToAction("GenerateKeys");
}
catch (Exception ex)
{
return View();
}
}
}
}
完成添加 Controller 及其 Action 方法后,下一步我们将向 GenerateKeys
Action 方法添加 View。
添加 GenerateKeys View
在本部分中,我们将添加 Views。要添加 View,只需右键单击 Action 方法内部,然后从菜单列表中选择Add View,将弹出一个名为“Add View”的新对话框。接下来,我们不需要为视图命名。它已设置为默认值,即 Action 方法名称。在 Template 中,选择模板以取决于您要创建的视图(Create),并选择 Model(ClientKey)。单击Add 按钮。
添加 View 后,只需保存应用程序并运行,然后第一步是登录应用程序。登录后,您将看到 RegisterCompany Index View。
在 Master page 上,我们添加了一个链接来显示 GenerateKeys View。
现在单击 Application secret 链接,将显示 GenerateKeys View。
现在我们已完成 ClientID 和 Client Secret 的生成。接下来,我们将添加 Authenticate Controller 来对 ClientID 和 Client Secret 进行身份验证并以响应形式返回 Token 密钥。
身份验证机制
在此过程中,我们已将 ClientID 和 Client Secret 提供给客户端,现在我们需要开发一种身份验证机制,用户将此 ClientID 和 Client Secret 发送到服务器,然后我们将使用数据库验证这些密钥,然后,如果密钥有效,我们将以 Token 作为响应返回给用户,否则我们将返回错误消息。
让我们开始添加一个名为 IAuthenticate
的接口并声明其中的方法。
添加 IAuthenticate Interface
using MusicAPIStore.Models;
using System;
namespace MusicAPIStore.Repository
{
public interface IAuthenticate
{
ClientKey GetClientKeysDetailsbyCLientIDandClientSecert
(string clientID , string clientSecert);
bool ValidateKeys(ClientKey ClientKeys);
bool IsTokenAlreadyExists(int CompanyID);
int DeleteGenerateToken(int CompanyID);
int InsertToken(TokensManager token);
string GenerateToken(ClientKey ClientKeys, DateTime IssuedOn);
}
}
我们在 IAuthenticate
接口中声明了五个方法
GetClientKeysDetailsbyCLientIDandClientSecert
此方法以 ClientID 和 Client Secret 作为输入,并根据其从数据库获取数据。
ValidateKeys
此方法以 ClientKey Model 作为输入,其中检查用户通过的 ClientID 和 Client Secret 是否有效。
IsTokenAlreadyExists
此方法以
CompanyID
作为输入,并检查是否已为其生成 Token。DeleteGenerateToken
此方法以
CompanyID
作为输入,并根据CompanyID
参数删除已生成的 Token。InsertToken
此方法以
TokensManager
模型作为输入参数,用于将Token
值保存到数据库。GenerateToken
此方法生成并返回
Token
。
添加 Authenticate Concrete Class
在本部分中,我们将添加一个名为 AuthenticateConcrete
的 Concrete
类,该类将继承 IAuthenticate
接口并实现其中的所有方法。
using System;
using System.Linq;
using MusicAPIStore.Models;
using MusicAPIStore.Context;
using MusicAPIStore.AES256Encryption;
using static MusicAPIStore.AES256Encryption.EncryptionLibrary;
namespace MusicAPIStore.Repository
{
public class AuthenticateConcrete : IAuthenticate
{
DatabaseContext _context;
public AuthenticateConcrete()
{
_context = new DatabaseContext();
}
public ClientKey GetClientKeysDetailsbyCLientIDandClientSecert
(string clientID, string clientSecert)
{
try
{
var result = (from clientkeys in _context.ClientKeys
where clientkeys.ClientID == clientID &&
clientkeys.ClientSecret == clientSecert
select clientkeys).FirstOrDefault();
return result;
}
catch (Exception)
{
throw;
}
}
public bool ValidateKeys(ClientKey ClientKeys)
{
try
{
var result = (from clientkeys in _context.ClientKeys
where clientkeys.ClientID == ClientKeys.ClientID &&
clientkeys.ClientSecret == ClientKeys.ClientSecret
select clientkeys).Count();
if (result > 0)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
public bool IsTokenAlreadyExists(int CompanyID)
{
try
{
var result = (from token in _context.TokensManager
where token.CompanyID == CompanyID
select token).Count();
if (result > 0)
{
return true;
}
else
{
return false;
}
}
catch (Exception)
{
throw;
}
}
public int DeleteGenerateToken(int CompanyID)
{
try
{
var token = _context.TokensManager.SingleOrDefault
(x => x.CompanyID == CompanyID);
_context.TokensManager.Remove(token);
return _context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
public string GenerateToken(ClientKey ClientKeys, DateTime IssuedOn)
{
try
{
string randomnumber =
string.Join(":", new string[]
{ Convert.ToString(ClientKeys.UserID),
KeyGenerator.GetUniqueKey(),
Convert.ToString(ClientKeys.CompanyID),
Convert.ToString(IssuedOn.Ticks),
ClientKeys.ClientID
});
return EncryptionLibrary.EncryptText(randomnumber);
}
catch (Exception)
{
throw;
}
}
public int InsertToken(TokensManager token)
{
try
{
_context.TokensManager.Add(token);
return _context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
}
}
添加 Interface IAuthenticate 和 AuthenticateConcrete 后的快照
完成添加 Interface IAuthenticate
和 AuthenticateConcrete
后,下一步我们将添加一个名为 Authenticate
的 ApiController
。
添加 Authenticate Controller
在本部分中,我们将添加一个名为 Authenticate
的 ApiController
,其中将包含一个名为 Authenticate
的 Action 方法,该方法以 [FromBody
] 的形式接受 ClientKey Model 作为输入。
using MusicAPIStore.Models;
using MusicAPIStore.Repository;
using System;
using System.Configuration;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MusicAPIStore.Controllers
{
public class AuthenticateController : ApiController
{
IAuthenticate _IAuthenticate;
public AuthenticateController()
{
_IAuthenticate = new AuthenticateConcrete();
}
// POST: api/Authenticate
public HttpResponseMessage Authenticate([FromBody]ClientKey ClientKeys)
{
if (string.IsNullOrEmpty(ClientKeys.ClientID) &&
string.IsNullOrEmpty(ClientKeys.ClientSecret))
{
var message = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
message.Content = new StringContent("Not Valid Request");
return message;
}
else
{
if (_IAuthenticate.ValidateKeys(ClientKeys))
{
var clientkeys =
_IAuthenticate.GetClientKeysDetailsbyCLientIDandClientSecert
(ClientKeys.ClientID, ClientKeys.ClientSecret);
if (clientkeys == null)
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound);
message.Content = new StringContent("InValid Keys");
return message;
}
else
{
if (_IAuthenticate.IsTokenAlreadyExists(clientkeys.CompanyID))
{
_IAuthenticate.DeleteGenerateToken(clientkeys.CompanyID);
return GenerateandSaveToken(clientkeys);
}
else
{
return GenerateandSaveToken(clientkeys);
}
}
}
else
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound);
message.Content = new StringContent("InValid Keys");
return new HttpResponseMessage
{ StatusCode = HttpStatusCode.NotAcceptable };
}
}
}
[NonAction]
private HttpResponseMessage GenerateandSaveToken(ClientKey clientkeys)
{
var IssuedOn = DateTime.Now;
var newToken = _IAuthenticate.GenerateToken(clientkeys, IssuedOn);
TokensManager token = new TokensManager();
token.TokenID = 0;
token.TokenKey = newToken;
token.CompanyID = clientkeys.CompanyID;
token.IssuedOn = IssuedOn;
token.ExpiresOn = DateTime.Now.AddMinutes(Convert.ToInt32
(ConfigurationManager.AppSettings["TokenExpiry"]));
token.CreatedOn = DateTime.Now;
var result = _IAuthenticate.InsertToken(token);
if (result == 1)
{
HttpResponseMessage response = new HttpResponseMessage();
response = Request.CreateResponse(HttpStatusCode.OK, "Authorized");
response.Headers.Add("Token", newToken);
response.Headers.Add("TokenExpiry",
ConfigurationManager.AppSettings["TokenExpiry"]);
response.Headers.Add("Access-Control-Expose-Headers", "Token,TokenExpiry");
return response;
}
else
{
var message = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
message.Content = new StringContent("Error in Creating Token");
return message;
}
}
}
}
让我们来理解代码
public HttpResponseMessage Authenticate([FromBody]ClientKey ClientKeys)
{
if (string.IsNullOrEmpty(ClientKeys.ClientID) &&
string.IsNullOrEmpty(ClientKeys.ClientSecret))
{
var message = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
message.Content = new StringContent("Not Valid Request");
return message;
}
else
{
if (_IAuthenticate.ValidateKeys(ClientKeys))
{
var clientkeys =
_IAuthenticate.GetClientKeysDetailsbyCLientIDandClientSecert
(ClientKeys.ClientID, ClientKeys.ClientSecret);
if (clientkeys == null)
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound);
message.Content = new StringContent("InValid Keys");
return message;
}
else
{
if (_IAuthenticate.IsTokenAlreadyExists(clientkeys.CompanyID))
{
_IAuthenticate.DeleteGenerateToken(clientkeys.CompanyID);
return GenerateandSaveToken(clientkeys);
}
else
{
return GenerateandSaveToken(clientkeys);
}
}
}
else
{
var message = new HttpResponseMessage(HttpStatusCode.NotFound);
message.Content = new StringContent("InValid Keys");
return new HttpResponseMessage
{ StatusCode = HttpStatusCode.NotAcceptable };
}
}
}
此部分的第一步是 Authenticate
方法接受 ClientKey
模型作为输入,从该模型中,我们将仅使用两个参数:ClientID 和 Client Secret。接下来,我们将首先检查这些参数是否为 null
。如果为 Null
,我们将以“Not Valid Request
”的响应发送 HttpResponseMessage
。
如果 ClientID 和 Client Secret 不是 null
,我们将把 ClientID 和 Client Secret 发送到 ValidateKeys
方法,以检查传递的值是否已存在于数据库中。
如果 ClientID 和 Client Secret 值存在于数据库中,那么我们将把这两个值传递给“GetClientKeysDetailsbyCLientIDandClientSecert
”方法来获取所有 ClientKey
详细信息。这里我们再次检查密钥是否有效,如果无效,我们将以“InValid Keys
”的响应发送 HttpResponseMessage
。
如果 ClientID 和 Client Secret 有效,那么我们将从数据库获取 ClientKey
详细信息,并从该 Model 中,我们将 CompanyID
传递给“IsTokenAlreadyExists
”方法,以检查 Token
是否已存在于数据库中。
如果数据库中已存在 Token
,那么我们将删除旧的 Token 并生成新的 Token,然后将新的 Token 插入数据库,我们还将以 Token 作为响应发送给发送请求的客户端。
如果数据库中不存在 Token,那么我们将生成 Token 并将新的 Token 插入数据库,我们还将以 Token 作为响应发送给发送请求的客户端。
现在我们已经理解了工作流程。让我们尝试一个实际的例子。
首先,我们将使用 Postman Web Debugger 调用 Web API api/Authenticate
方法。
下载 Postman Chrome App
安装 Postman Chrome App
https://www.getpostman.com/docs/introduction
安装 Postman Chrome App 后,现在您可以打开 Postman Chrome App。下面的快照是 Postman Chrome App。
在下一步中,我们将查看 ClientID 和 ClientSecret,因为我们需要将这些密钥发送到服务器以获取 Token。
获取密钥
接下来,我们将把密钥(ClientID
和 ClientSecret
)设置在 Post 请求的 FromBody
中。
在 POSTMAN 中设置 POST 请求的值
- 从下拉列表中选择 Post 请求
- 设置 URL:https://:4247/api/Authenticate
- 我们将把这些密钥作为请求的
FromBody
发送。 - 它将是原始数据,Content Type 为
application/json
- 设置 '
ClientID
' 和 'ClientSecret
'{ 'ClientID':'pGU6RJ8ELcVRZmN', 'ClientSecret':'tiIfdZ3vh5IwGVm' }
- 单击Send 按钮发送请求。
请求的响应
在 Response
中,我们得到 Token
和 TokenExpiry
。
Token:XbCsogSJXKLSq2TBUs0QZrbClRpiuXZFrfjKy0WRtEdPQYpA87Pav9KozmmKoNMd1W3Q8Hg8hoaGYKDtyTH2Rg==
TokenExpiry: 30
Token 仅在 30 分钟内有效。
收到响应后,让我们查看 TokenManager
表,看看其中插入了哪些字段。
在下一步中,我们将添加一个名为 APIAuthorizeAttribute
的 AuthorizeAttribute
。
添加 AuthorizeAttribute (APIAuthorizeAttribute)
在本部分中,我们将在 Filter 文件夹中创建一个名为“APIAuthorizeAttribute
”的 AuthorizeAttribute
。
要添加此 AuthorizeAttribute
,首先我们将创建一个名为“APIAuthorizeAttribute
”的类,该类将继承“AuthorizeAttribute
”类并实现其中的所有方法。
在此“APIAuthorizeAttribute
”中,我们将验证从客户端发送的 Token。
- 第一步是从客户端 Header 接收 Token。
- 之后,我们将检查此 Token 是否为
Null
。 - 接下来,我们将解密此 Token。
- 解密此 Token 后,我们得到字符串值作为输出。
- 接下来,我们将分割(‘:’)我们收到的
string
值。 - 分割后,我们得到(
UserID
、随机密钥、CompanyID、Ticks、ClientID
)值。 - 接下来,我们将把(
UserID
、CompanyID
、ClientID
)传递给数据库,以检查我们收到的参数是否有效。 - 之后,我们将检查 Token 过期。
- 如果在此步骤中出现任何错误,我们将返回
false
值。 - 如果我们有有效值且 Token 未过期,它将返回
true
。
带有解释的 Authorize Attribute 快照
查看快照后,您将对它的工作原理有详细的了解。接下来,我们将看到 APIAuthorizeAttribute
的完整代码片段。
APIAuthorizeAttribute 代码片段
using MusicAPIStore.AES256Encryption;
using MusicAPIStore.Context;
using System;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Controllers;
namespace MusicAPIStore.Filters
{
public class APIAuthorizeAttribute : AuthorizeAttribute
{
private DatabaseContext db = new DatabaseContext();
public override void OnAuthorization(HttpActionContext filterContext)
{
if (Authorize(filterContext))
{
return;
}
HandleUnauthorizedRequest(filterContext);
}
protected override void HandleUnauthorizedRequest(HttpActionContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
}
private bool Authorize(HttpActionContext actionContext)
{
try
{
var encodedString = actionContext.Request.Headers.GetValues("Token").First();
bool validFlag = false;
if (!string.IsNullOrEmpty(encodedString))
{
var key = EncryptionLibrary.DecryptText(encodedString);
string[] parts = key.Split(new char[] { ':' });
var UserID = Convert.ToInt32(parts[0]); // UserID
var RandomKey = parts[1]; // Random Key
var CompanyID = Convert.ToInt32(parts[2]); // CompanyID
long ticks = long.Parse(parts[3]); // Ticks
DateTime IssuedOn = new DateTime(ticks);
var ClientID = parts[4]; // ClientID
// By passing this parameter
var registerModel = (from register in db.ClientKeys
where register.CompanyID == CompanyID
&& register.UserID == UserID
&& register.ClientID == ClientID
select register).FirstOrDefault();
if (registerModel != null)
{
// Validating Time
var ExpiresOn = (from token in db.TokensManager
where token.CompanyID == CompanyID
select token.ExpiresOn).FirstOrDefault();
if ((DateTime.Now > ExpiresOn))
{
validFlag = false;
}
else
{
validFlag = true;
}
}
else
{
validFlag = false;
}
}
return validFlag;
}
catch (Exception)
{
return false;
}
}
}
}
完成添加 APIAuthorizeAttribute
后,下一步我们需要将此属性应用于控制器。
但我们尚未添加需要应用此属性的控制器(ApiController
),让我们添加一个名为“LatestMusic
”的 ApiController
。
添加 ApiController LatestMusic
在本部分中,我们将添加一个名为“LatestMusic
”的 ApiController
,其中将包含一个名为 GetMusicStore
的 Action 方法,该方法将从数据库返回最新的音乐列表。
LatestMusic Controller 代码片段
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using MusicAPIStore.Context;
using MusicAPIStore.Models;
using MusicAPIStore.Filters;
namespace MusicAPIStore.Controllers
{
[APIAuthorizeAttribute]
public class LatestMusicController : ApiController
{
private DatabaseContext db = new DatabaseContext();
// GET: api/LatestMusic
public List<MusicStore> GetMusicStore()
{
try
{
var listofSongs = db.MusicStore.ToList();
return listofSongs;
}
catch (System.Exception)
{
throw;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
在上面的代码片段中,如果您仔细观察,您可以看到我们已在控制器级别应用了 Attribute
。
现在,每当有人访问此控制器时,他都需要一个有效的 Token。只有这样,他才能访问 LatestMusic API。
访问 LatestMusic API Controller
要访问 LatestMusic API,我们需要在 Header 中发送一个有效的 Token。
让我们首先从身份验证过程开始,在身份验证后,我们将收到一个有效的 Token 作为响应,然后我们将再次使用此 Token 来访问 LatestMusic API。
- 身份验证过程以获取 Token
- 发送 Token 以访问
LatestMusic
API 并以响应形式获取 Top 10 Music 热门列表。
响应详情
传入无效 Token 以测试响应
会话过期后验证 Token
发送请求后,当 Token 过期时,将显示如下错误消息。
存储 Token 过期日期和时间的表
结论
在本文中,我们以循序渐进和详细的方式学习了如何使用基于 Token 的身份验证来保护 WEB API,以便初级开发人员也能轻松理解。现在,您可以使用此过程来保护您最常用的客户端应用程序,以及服务器端应用程序。
感谢您的阅读。希望您喜欢我的文章。
历史
- 2017 年 4 月 20 日:初始版本