• 购物车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;
            }
    
        });

    本系列结束

  • 相关阅读:
    WiFi热点
    计算器
    flask的使用
    Python logging
    串口
    C# 定时器
    C# 控件
    cookie 和 session
    文件
    Linux命令
  • 原文地址:https://www.cnblogs.com/darrenji/p/4959721.html
Copyright © 2020-2023  润新知