原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证

chsakell分享了前端使用AngularJS,后端使用ASP.NET Web API的购物车案例,非常精彩,这里这里记录下对此项目的理解。

文章:
http://chsakell.com/2015/01/31/angularjs-feat-web-api/
http://chsakell.com/2015/03/07/angularjs-feat-web-api-enable-session-state/

源码:
https://github.com/chsakell/webapiangularjssecurity

本系列共三篇,本篇是第三篇。

购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(1)--后端
购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session
购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证

这里会涉及到三方面的内容:

1、ASP.NET Identity & Entity Framework

● Identity User
● User Mnager

2、OWIN Middleware

● Authorization Server
● Bearer Auhentication

3、AngularJS

● Generate Tokens
● Creae authorized requests

1、ASP.NET Identity & Entity Framework

首先安装Microsoft ASP.NET Identity EntityFramework。

添加一个有关用户的领域模型,继承IdentityUser。

public class AppStoreUser : IdentityUser
{...
}

配置用户,继承EntityTypeConfiguration<T>

public class AppStoreUserConfiguraiton : EntityTypeConfiguration<AppStoreUser>
{public AppStoreUserConfiguration(){ToTable("Users");}
}

然后让上下文继承Identity特有的上下文类。

public class StoreContext : IdentityDbContext<AppStoreUser>
{public StoreContext() : base("StoreContext", thrwoIfVISchema: false){protected override void OnModelCreating(DbModelBuilder modelBuilder){modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });modelBuilder.Configurations.Add(new AppStoreUserConfiguration());modelBuilder.Configurations.Add(new CategoryConfiguration());modelBuilder.Configurations.Add(new OrderConfiguration());}      }
}

继承Identity的UserManager类:

public class AppStoreUserManager : UserManager<AppStoreUser>
{public AppStoreUserManager(IUserStore<AppStoreUser> store) : base(store){}
}

2、OWIN Middleware

在NuGet中输入owin,确保已经安装如下组件:

Microsoft.Owin.Host.SystemWeb
Microsoft.Owin
Microsoft ASP.NET Web API 2.2 OWIN
Microsoft.Owin.Security
Microsoft.Owin.Security.OAth
Microsoft.Owin.Security.Cookies (optional)
Microsoft ASP.NET Identity Owin
OWIN

在项目根下创建Startup.cs部分类。

[assembly: OwinStartup(typeof(Store.Startup))]
namespace Store
{public partial class Startup{public void Configuration(IAppBuilder app){ConfigureStoreAuthentication(app);}}
}

在App_Start中创建Startup.cs部分类。

//启用OWIN的Bearer Token Authentication
public partial class Startup
{public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }public static string PublicClientId { get; private set; }public void ConfigureStoreAuthentication(IAppBuilder app){// User a single instance of StoreContext and AppStoreUserManager per request
        app.CreatePerOwinContext(StoreContext.Create);app.CreatePerOwinContext<AppStoreUserManager>(AppStoreUserManager.Create);// Configure the application for OAuth based flowPublicClientId = "self";OAuthOptions = new OAuthAuthorizationServerOptions{TokenEndpointPath = new PathString("/Token"),Provider = new ApplicationOAuthProvider(PublicClientId),AccessTokenExpireTimeSpan = TimeSpan.FromDays(10),AllowInsecureHttp = true};app.UseOAuthBearerTokens(OAuthOptions);}
}

在Identity用户管理类中添加如下代码:

public class AppStoreUserManager : UserManager<AppStoreUser>
{public AppStoreUserManager(IUserStore<AppStoreUser> store): base(store){}public static AppStoreUserManager Create(IdentityFactoryOptions<AppStoreUserManager> options, IOwinContext context){var manager = new AppStoreUserManager(new UserStore<AppStoreUser>(context.Get<StoreContext>()));// Configure validation logic for usernamesmanager.UserValidator = new UserValidator<AppStoreUser>(manager){AllowOnlyAlphanumericUserNames = false,RequireUniqueEmail = true};// Password Validationsmanager.PasswordValidator = new PasswordValidator{RequiredLength = 6,RequireNonLetterOrDigit = false,RequireDigit = false,RequireLowercase = true,RequireUppercase = true,};var dataProtectionProvider = options.DataProtectionProvider;if (dataProtectionProvider != null){manager.UserTokenProvider = new DataProtectorTokenProvider<AppStoreUser>(dataProtectionProvider.Create("ASP.NET Identity"));}return manager;}public async Task<ClaimsIdentity> GenerateUserIdentityAsync(AppStoreUser user, string authenticationType){var userIdentity = await CreateIdentityAsync(user, authenticationType);return userIdentity;}
}

当在API中需要获取用户的时候,就会调用以上的代码,比如:

Request.GetOwinContext().GetUserManager<AppStoreUserManager>();

为了能够使用OWIN的功能,还需要实现一个OAuthAuthorizationServerProvider。

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{private readonly string _publicClientId;public ApplicationOAuthProvider(string publicClientId){if (publicClientId == null){throw new ArgumentNullException("publicClientId");}_publicClientId = publicClientId;}public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context){var userManager = context.OwinContext.GetUserManager<AppStoreUserManager>();AppStoreUser user = await userManager.FindAsync(context.UserName, context.Password);if (user == null){context.SetError("invalid_grant", "Invalid username or password.");return;}ClaimsIdentity oAuthIdentity = await userManager.GenerateUserIdentityAsync(user, OAuthDefaults.AuthenticationType);AuthenticationProperties properties = new AuthenticationProperties(); AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);context.Validated(ticket);}public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){if (context.ClientId == null){context.Validated();}return Task.FromResult<object>(null);}
}

OWIN这个中间件的工作原理大致是:

→对Token的请求过来
→OWIN调用以上的GrantResourceOwnerCredentials方法
→OAuthAuthorizationServerProvider获取UerManager的实例
→OAuthAuthorizationServerProvider创建access token
→OAuthAuthorizationServerProvider创建access token给响应
→Identity的UserManager检查用户的credentials是否有效
→Identity的UserManager创建ClaimsIdentity

接着,在WebApiConfig中配置,让API只接受bearer token authentication。

public static class WebApiConfig
{public static void Register(HttpConfiguration config){// Web API configuration and services// Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));// Web API routes
        config.MapHttpAttributeRoutes();}
}

在需要验证的控制器上加上Authorize特性。

[Authorize]
public class OrdersController : ApiController
{}

AccountController用来处理用户的相关事宜。

[Authorize]
[RoutePrefix("api/Account")]
public class AccountController : ApiController
{//private const string LocalLoginProvider = "Local";private AppStoreUserManager _userManager;public AccountController(){}public AccountController(AppStoreUserManager userManager,ISecureDataFormat<AuthenticationTicket> accessTokenFormat){UserManager = userManager;AccessTokenFormat = accessTokenFormat;}public AppStoreUserManager UserManager{get{return _userManager ?? Request.GetOwinContext().GetUserManager<AppStoreUserManager>();}private set{_userManager = value;}}public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }// POST api/Account/Register
    [AllowAnonymous][Route("Register")]public async Task<IHttpActionResult> Register(RegistrationModel model){if (!ModelState.IsValid){return BadRequest(ModelState);}var user = new AppStoreUser() { UserName = model.Email, Email = model.Email };IdentityResult result = await UserManager.CreateAsync(user, model.Password);if (!result.Succeeded){return GetErrorResult(result);}return Ok();}protected override void Dispose(bool disposing){if (disposing && _userManager != null){_userManager.Dispose();_userManager = null;}base.Dispose(disposing);}#region Helpersprivate IAuthenticationManager Authentication{get { return Request.GetOwinContext().Authentication; }}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;}#endregion
}

3、AngularJS

在前端,把token相关的常量放到主module中去。

angular.module('gadgetsStore').constant('gadgetsUrl', 'http://localhost:61691/api/gadgets').constant('ordersUrl', 'http://localhost:61691/api/orders').constant('categoriesUrl', 'http://localhost:61691/api/categories').constant('tempOrdersUrl', 'http://localhost:61691/api/sessions/temporders').constant('registerUrl', '/api/Account/Register').constant('tokenUrl', '/Token').constant('tokenKey', 'accessToken').controller('gadgetStoreCtrl', function ($scope, $http, $location, gadgetsUrl, categoriesUrl, ordersUrl, tempOrdersUrl, cart, tokenKey) {

提交订单的时候需要把token写到headers的Authorization属性中去。

$scope.sendOrder = function (shippingDetails) {var token = sessionStorage.getItem(tokenKey);console.log(token);var headers = {};if (token) {headers.Authorization = 'Bearer ' + token;}var order = angular.copy(shippingDetails);order.gadgets = cart.getProducts();$http.post(ordersUrl, order, { headers: { 'Authorization': 'Bearer ' + token } }).success(function (data, status, headers, config) {$scope.data.OrderLocation = headers('Location');$scope.data.OrderID = data.OrderID;cart.getProducts().length = 0;$scope.saveOrder();$location.path("/complete");}).error(function (data, status, headers, config) {if (status != 401)$scope.data.orderError = data.Message;else {$location.path("/login");}}).finally(function () {});
}

在主module中增加登出和注册用户的功能。

$scope.logout = function () {sessionStorage.removeItem(tokenKey);
}
$scope.createAccount = function () {$location.path("/register");
}

当然还需要添加对应的路由:

 $routeProvider.when("/login", {templateUrl: "app/views/login.html"});
$routeProvider.when("/register", {templateUrl: "app/views/register.html"});

再往主module中添加一个controller,用来处理用户账户相关事宜。

angular.module("gadgetsStore").controller('accountController', function ($scope, $http, $location, registerUrl, tokenUrl, tokenKey) {$scope.hasLoginError = false;$scope.hasRegistrationError = false;// Registration$scope.register = function () {$scope.hasRegistrationError = false;$scope.result = '';var data = {Email: $scope.registerEmail,Password: $scope.registerPassword,ConfirmPassword: $scope.registerPassword2};$http.post(registerUrl, JSON.stringify(data)).success(function (data, status, headers, config) {$location.path("/login");}).error(function (data, status, headers, config) {$scope.hasRegistrationError = true;var errorMessage = data.Message;console.log(data);$scope.registrationErrorDescription = errorMessage;if (data.ModelState['model.Email'])$scope.registrationErrorDescription += data.ModelState['model.Email'];if (data.ModelState['model.Password'])$scope.registrationErrorDescription += data.ModelState['model.Password'];if (data.ModelState['model.ConfirmPassword'])$scope.registrationErrorDescription += data.ModelState['model.ConfirmPassword'];if (data.ModelState[''])$scope.registrationErrorDescription +=  data.ModelState[''];}).finally(function () {});}$scope.login = function () {$scope.result = '';var loginData = {grant_type: 'password',username: $scope.loginEmail,password: $scope.loginPassword};$http({method: 'POST',url: tokenUrl,data: $.param(loginData),headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}}).then(function (result) {console.log(result);$location.path("/submitorder");sessionStorage.setItem(tokenKey, result.data.access_token);$scope.hasLoginError = false;$scope.isAuthenticated = true;}, function (data, status, headers, config) {$scope.hasLoginError = true;$scope.loginErrorDescription = data.data.error_description;});}});

有关登录页:

<div ng-controller="accountController"><form role="form"><input name="email" type="email" ng-model="loginEmail" autofocus=""><input  name="password" type="password" ng-model="loginPassword" value=""><div ng-show="hasLoginError"><a href="#" ng-bind="loginErrorDescription"></a></div><a href="" ng-click="login()">Login</a><a href="" ng-click="createAccount()">Create account</a></form>
</div>

有关注册页:

<div ng-controller="accountController"><form role="form"><input name="email" type="email" ng-model="registerEmail" autofocus=""><input name="password" type="password" ng-model="registerPassword" value=""><input name="confirmPassword" type="password" ng-model="registerPassword2" value=""><div ng-show="hasRegistrationError"><a href="#" ng-bind="registrationErrorDescription"></a></div><a href="" ng-click="register()">Create account</a</form>
</div>

在购物车摘要区域添加一个登出按钮。

<a href="" ng-show="isUserAuthenticated()" ng-click="logout()">Logout</a>

最后可以把账户相关封装在一个服务中。

angular.module("gadgetsStore").service('accountService', function ($http, registerUrl, tokenUrl, tokenKey) {this.register = function (data) {var request = $http.post(registerUrl, data);return request;}this.generateAccessToken = function (loginData) {var requestToken = $http({method: 'POST',url: tokenUrl,data: $.param(loginData),headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}});return requestToken;}this.isUserAuthenticated = function () {var token = sessionStorage.getItem(tokenKey);if (token)return true;elsereturn false;}this.logout = function () {sessionStorage.removeItem(tokenKey);}});

把有关订单相关,封装在storeService.js中:

angular.module("gadgetsStore").service('storeService', function ($http, gadgetsUrl, categoriesUrl, tempOrdersUrl, ordersUrl, tokenKey) {this.getGadgets = function () {var request = $http.get(gadgetsUrl);return request;}this.getCategories = function () {var request = $http.get(categoriesUrl);return request;}this.submitOrder = function (order) {var token = sessionStorage.getItem(tokenKey);console.log(token);var headers = {};if (token) {headers.Authorization = 'Bearer ' + token;}var request = $http.post(ordersUrl, order, { headers: { 'Authorization': 'Bearer ' + token } });return request;}this.saveTempOrder = function (currentProducts) {var request = $http.post(tempOrdersUrl, currentProducts);return request;}this.loadTempOrder = function () {var request = $http.get(tempOrdersUrl);return request;}});

本系列结束

购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证相关推荐

  1. 购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session

    原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session chsakell分享了前端使用AngularJS,后端使用ASP.NE ...

  2. 【转】在ASP.NET Web API 2中使用Owin基于Token令牌的身份验证

    基于令牌的身份验证 基于令牌的身份验证主要区别于以前常用的基于cookie的身份验证,基于cookie的身份验证在B/S架构中使用比较多,但是在Web Api中因其特殊性,基于cookie的身份验证已 ...

  3. 前端使用AngularJS的$resource,后端ASP.NET Web API,实现增删改查

    首页 > 技术 > 编程 > NET > 前端使用AngularJS的$resource,后端ASP.NET Web API,实现增删改查 前端使用AngularJS的$res ...

  4. 利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理

    在Asp.net Web API中,对业务数据的分页查询处理是一个非常常见的接口,我们需要在查询条件对象中,定义好相应业务的查询参数,排序信息,请求记录数和每页大小信息等内容,根据这些查询信息,我们在 ...

  5. ASP.NET Web API 安全筛选器

    原文:https://msdn.microsoft.com/zh-cn/magazine/dn781361.aspx 身份验证和授权是应用程序安全的基础.身份验证通过验证提供的凭据来确定用户身份,而授 ...

  6. ASP.NET Web API与Owin OAuth:使用Access Toke调用受保护的API(二)

    在前一篇博文中,我们使用OAuth的Client Credential Grant授权方式,在服务端通过CNBlogsAuthorizationServerProvider(Authorization ...

  7. ASP.NET Web API与Owin OAuth:使用Access Toke调用受保护的API

    在前一篇博文中,我们使用OAuth的Client Credential Grant授权方式,在服务端通过CNBlogsAuthorizationServerProvider(Authorization ...

  8. Angularjs 通过asp.net web api认证登录

    Angularjs 通过asp.net web api认证登录 Angularjs利用asp.net mvc提供的asp.net identity,membership实现居于数据库的用户名/密码的认 ...

  9. VS Code 开发asp.net.web.API,C#后端开发

    ASP.NET Core 创建最小 Web API C#后端开发插件合集 创建Web.API 快捷配置 EFcore C#后端开发插件合集 c# C# Extensions NuGet Package ...

最新文章

  1. linux内核percpu变量声明,Linux kernel percpu变量解析
  2. ab plc软件_【万泉河】PLC垃圾程序赏析2:AB RSLOGIX 5000
  3. mysql 结果集 超大_使用MySQL流式传输大型结果集
  4. 将勾选数据从dataset中筛选出来
  5. python的dropna 和notna的性能_python轻松滤除缺失数据
  6. hdu2063 二分图的匈牙利匹配
  7. 第1节 kafka消息队列:5、javaAPI操作
  8. 验证DG最大性能模式下使用ARCH/LGWR及STANDBY LOG的不同情况
  9. 论一个好翻译的重要性
  10. openGL中向量的加减乘除运算
  11. java-基于Swing的图形界面设计(上)
  12. 戴尔易安信引领科技创新,以全面的端到端解决方案助力企业“数”造未来
  13. 极速办公(PPT)如何修改文字为斜体
  14. abp mysql .net core_基于abp vNext和.NET Core 开发博客
  15. 计算机硬件软件的英语,计算机软件和硬件,PC computer software and hardware,音标,读音,翻译,英文例句,英语词典...
  16. python五角星符号怎么打出来_如何使用python输出连续星号?
  17. 小程序微信小程序设计注册页面
  18. 使用Zerotier+Moon结点内网穿透
  19. 怎么用HTML5制作万花筒,如何用html5写出万花筒效果?
  20. java获取dns记录_java-使用JNDI获取DNS SRV记录

热门文章

  1. Django:序列化的几种方法
  2. 简单了解Vue的自定义组件与生命周期
  3. 单链表的查找和取值-1
  4. 内联元素与块级元素及内外边距的影响
  5. boost function对象
  6. gridcontrol 控件的用法
  7. 全排列算法原理和实现
  8. 洛谷P4555 [国家集训队]最长双回文串(manacher 线段树)
  9. 【opencv】VideoCapture打不开本地视频文件或者网络IP摄像头
  10. 系统调用syscall---用户态切换到内核态的唯一途径