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

简介: 原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证  chsakell分享了前端使用AngularJS,后端使用ASP.NET Web API的购物车案例,非常精彩,这里这里记录下对此项目的理解。
原文: 购物车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 flow
        PublicClientId = "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 usernames
        manager.UserValidator = new UserValidator<AppStoreUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        // Password Validations
        manager.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 Helpers

    private 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;
            else
                return 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;
        }

    });

 

本系列结束

 

目录
相关文章
|
6月前
|
并行计算 前端开发 JavaScript
Web Worker:让前端飞起来的隐形引擎
在现代 Web 开发中,前端性能优化是一个至关重要的课题,尤其是对于计算密集型的应用,如图像处理、视频处理、大规模数据分析等任务。单线程的 JavaScript 引擎常常成为性能瓶颈,导致应用变得迟缓。Web Worker,作为一种强大的技术,使得前端能够在后台进行并行计算,从而实现高效的任务处理,不影响主线程的运行和用户的交互体验。
574 108
|
6月前
|
JavaScript 前端开发 Java
前端框架选择之争:jQuery与Vue在现代Web开发中的真实地位-优雅草卓伊凡
前端框架选择之争:jQuery与Vue在现代Web开发中的真实地位-优雅草卓伊凡
659 72
前端框架选择之争:jQuery与Vue在现代Web开发中的真实地位-优雅草卓伊凡
|
8月前
|
移动开发 前端开发 JavaScript
前端web创建命令
本项目使用 Vite 搭建 Vue + TypeScript 开发环境,并基于 HTML5 Boilerplate 提供基础模板,快速启动现代前端开发。
125 2
|
8月前
|
Web App开发 编解码 移动开发
零基础音视频入门:你所不知道的Web前端音视频知识
本文回顾了Web端音视频的发展历程,同时还介绍了视频的编码、帧率、比特率等概念,提到了Canvas作为视频播放的替代方案,以及FFmpeg在音视频处理中的重要作用等知识。
248 1
|
前端开发
【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
355 1
【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
|
前端开发 JavaScript 搜索推荐
HTML与CSS在Web组件化中的核心作用及前端技术趋势
本文探讨了HTML与CSS在Web组件化中的核心作用及前端技术趋势。从结构定义、语义化到样式封装与布局控制,两者不仅提升了代码复用率和可维护性,还通过响应式设计、动态样式等技术增强了用户体验。面对兼容性、代码复杂度等挑战,文章提出了相应的解决策略,强调了持续创新的重要性,旨在构建高效、灵活的Web应用。
343 6
|
JSON 缓存 测试技术
构建高效RESTful API的后端实践指南####
本文将深入探讨如何设计并实现一个高效、可扩展且易于维护的RESTful API。不同于传统的摘要概述,本节将直接以行动指南的形式,列出构建RESTful API时必须遵循的核心原则与最佳实践,旨在为开发者提供一套直接可行的实施框架,快速提升API设计与开发能力。 ####
|
缓存 前端开发 API
深入浅出:后端开发中的RESTful API设计原则
【10月更文挑战第43天】在数字化浪潮中,后端开发如同搭建梦想的脚手架,而RESTful API则是连接梦想与现实的桥梁。本文将带你领略API设计的哲学之美,探索如何通过简洁明了的设计,提升开发效率与用户体验。从资源定位到接口约束,从状态转换到性能优化,我们将一步步构建高效、易用、可维护的后端服务。无论你是初涉后端的新手,还是寻求进阶的开发者,这篇文章都将为你的开发之路提供指引。让我们一起走进RESTful API的世界,解锁后端开发的新篇章。
|
JSON API 数据格式
探索后端开发:从零构建简易RESTful API
在数字时代的浪潮中,后端开发如同搭建一座桥梁,连接着用户界面与数据世界。本文将引导读者步入后端开发的殿堂,通过构建一个简易的RESTful API,揭示其背后的逻辑与魅力。我们将从基础概念出发,逐步深入到实际操作,不仅分享代码示例,更探讨如何思考和解决问题,让每一位读者都能在后端开发的道路上迈出坚实的一步。
|
缓存 API 开发者
构建高效后端服务:RESTful API设计原则与实践
【10月更文挑战第43天】在数字化时代的浪潮中,后端服务的稳定性和效率成为企业竞争力的关键。本文将深入探讨如何构建高效的后端服务,重点介绍RESTful API的设计原则和实践技巧,帮助开发者提升服务的可用性、可扩展性和安全性。通过实际代码示例,我们将展示如何将这些原则应用到日常开发工作中,以确保后端服务能够支撑起现代Web和移动应用的需求。