将认证信息从 MVC 传递到 Angular

Posted

技术标签:

【中文标题】将认证信息从 MVC 传递到 Angular【英文标题】:Pass authentication information from MVC into angular 【发布时间】:2016-08-23 04:47:34 【问题描述】:

我的项目使用 MVC 来交付我网站的初始标记

MVC 控制器超级简单:

public class HomeController : Controller

    public ActionResult Index()
    
        return View();
    

我在布局视图中有ng-app 标签、捆绑和@RenderBody

<!DOCTYPE html>
<html ng-app="myAppName">
<head>
    @Styles.Render("~/Content/css")
</head>
<body>
    <div class="container body-content">
        @RenderBody()
    </div>

    @Scripts.Render("~/bundles/aBundle")
</body>
</html>

我的索引视图被尽可能简单地剥离:

<ng-view></ng-view>

我的 Angular app.ts 文件如下所示:

module app 
    var main = angular.module("myAppName", ["ngRoute", "breeze.angular"]);

    main.config(routeConfig);

    routeConfig.$inject = ["$routeProvider"];
    function routeConfig($routeProvider: ng.route.IRouteProvider): void 
        $routeProvider
            .when("/home",
            
                templateUrl: "app/views/homeView.html",
                controller: "HomeController as vm"
            )
            .when("/itemDetail/:itemId",
            
                templateUrl: "app/views/itemDetailView.html",
                controller: "ItemDetailController as vm"
            )
            .when("/addItem",
            
                templateUrl: "app/views/addItemView.html",
                controller: "AddItemController as vm"
            )
            .when("/login",
            
                templateUrl: "app/views/loginView.html",
                controller: "LoginController as vm"
            )
            .otherwise("/home");
    

我可以检查用户在 MVC 控制器中发送的请求,或者使用 @Request.IsAuthenticated 在 Razor 视图中查看用户是否已登录,但是将此信息传递给我的 Angular 应用程序的最佳方式是什么?我可以在用户首次登录时正确地将用户路由到登录页面,但如果他们在服务器上有活动会话则跳过登录页面?

我为弄清楚这一点所做的研究向我表明,我可能需要创建一个角度服务来存储有关用户是否经过身份验证的布尔值。然后,我需要添加一些代码来检查每个使用$routeChangeStart 的路由的服务,并仅在必要时重定向到登录页面。我查看了许多示例,但无法在我自己的应用程序的上下文中将这些部分组合在一起。

有人可以帮我把这些点联系起来吗?

【问题讨论】:

您希望 Angular 调用您的 MVC 控制器来询问此信息!因此,您可以在 app.run(function () ) 中调用一个服务,该服务调用一个控制器操作,该操作仅返回一个布尔值。 您可以做一些简单的事情,例如将适当的值存储在模板视图标记的隐藏字段中。您应该能够通过绑定到 $routeChangeStart 事件来测试您的“已登录”逻辑。 【参考方案1】:

我正在开发一个不使用剃须刀的项目。视图中只有 html 和 angular。所以...我所做的是:

我创建了一个包含布局的指令“appWrapper”。在它里面有一个部分有 ng-view。 该指令使用控制器 AuthCtrl,而该控制器使用服务 AuthService。 因此,我可以访问整个 html 中此控制器内部的所有内容。例如,我可以在侧边栏中的一个项目中说 ng-click="vm.logout()"。 这个“AuthService”有一些可以被控制器调用的方法。登录和注销是其中的 2 个。 当我执行登录时,我使用从控制器返回的一些信息设置了一些 cookie。 当我注销时,我会删除这些 cookie。 在我的路线所在的 app.js 上,我在最后设置了 .run,每次更改位置(url)时都会检查这些 cookie。如果它们存在,它让用户继续。如果没有,它将用户重定向到登录。

有帮助吗?如果需要,我可以发布代码供您使用。

无需在控制器内创建操作来检查这一点。对于用户执行的每个操作,您的应用程序都需要访问服务器、返回浏览器、返回服务器并返回浏览器。这不好。

【讨论】:

No need to create an action inside a controller for checking this. It would require your application to go to the server, back to the browser, back to the server and back to the browser for every action the user takes. This is not good.你可以很容易地在javascript客户端中缓存,所以这永远不是问题 我只是更喜欢这种方式。所以我可以根据 cookie 的过期时间保持记录,而不是每次关闭浏览器时都结束会话,但你是对的。对不起。而且,是的,他应该始终使用授权。【参考方案2】:

我可以分享我在自己的应用程序中所做的事情,虽然我使用的是 ui-router,但同样的技术can easily be applied to the built in router。

基本逻辑工作流程:

    自动将resolve 添加到每个获取用户当前身份验证状态并缓存的路由。 检查路由是否需要认证,如果用户未登录则拒绝,否则允许正常进行。 在$routeChangeError(或类似的东西)上检查以查看要执行的操作。

这样做的好处是,您可以在提供良好的客户端安全体验方面拥有很大的灵活性。

为每条路由添加凭据:

let originalStateFunction = $stateProvider.state;

$stateProvider.state = function (state, config) 

  //
  // The "allowAnonymous" is something we added manually
  // This will become important later because we might have
  // routes that don't require authentication, like say
  // the login page
  //
  if (angular.isDefined(config) && !config.allowAnonymous) 
      _.defaults(config.resolve || (config.resolve = ), 
          /*@ngInject*/
          userSession: function userSessionSecurityCheck($q, sessionService) 
              let def = $q.defer();        

              sessionService.getSession()
                .then(session => 
                   if(!session.isAuthenticated)
                      def.reject(
                        error:'AUTHENTICATION_REQUIRED'
                      );
                   
                );

              return def.promise;

              //You could also do more complex handling here...
              // like check permissions for specific routes
              // and reject the promise if they fail.
          
      );
      

  //Now call the original state/when method with our
  // newly augmented config object
  return originalStateFunction.apply(this, arguments);
;

检查路由错误

.run($rootScope => 
    "ngInject";

    $rootScope.$on('$stateChangeError', (event, toState, toParams, fromState, fromParams, error) => 
         event.preventDefault();
         if(error.error === "AUTHENTICATION_REQUIRED")
              //Now you can redirect the user appropriately
         
    );
);

您可以创建一个简单的端点,通过检查 Principal 来返回当前用户的状态,只要您对在客户端缓存它很聪明,每个用户只会受到一次命中。

完整的示例代码对于 SO 来说太大了,但希望这可以为您提供一个良好的起点。

【讨论】:

你可以做的一个改进是你不需要 $q 因为sessionService 已经使用了承诺!拒绝一个承诺就在里面throw!!如果你 throw 一个对象(就像你应该做的那样),这应该被传递到拒绝方法参数中。 @CallumLinington - 这是真的,但是我会使用异常来处理应用程序流,我通常会尽量避免。这里没有显示,但我的逻辑稍微复杂一些,我实际上检查了针对返回数组的特定权限,而不仅仅是isAuthenticated。这样,我就可以进一步限制用户。 好吧,与其创建一个新的def,不如直接做if (!session.isAuthenticated) return $q.reject( error: 'AUTHENTICATION_REQUIRED' ); 但是,不登录不是异常情况,所以我们应该抛出异常! 如果你知道它可能会发生,这并不例外 :) 但我确实喜欢你的另一个想法,即使用 reject【参考方案3】:

我认为您会按照以下方式进行:

MVC 控制器:

[Authorize] // Make sure we're authorising the whole controller.
public class ProfileController : Controller

    public ActionResult IsLoggedIn()
    
        return Json(this.Request.IsAuthenticated); 
        // you may need allow get as the second parameter
    

角度:

app.factory('ProfileService', function ($http) 
    var service = 
        isLoggedIn: isLoggedIn
    ;

    return service;

    function isLoggedIn() 
        return $http.get('Profile/IsLoggedIn').then(function (response) 
            return response.data; // this depends on what the response object looks like
        );
    
);

app.run(function (ProfileService, $location) 
    ProfileService.isLoggedIn().then(function (isLoggedIn) 
        if (!isLoggedIn) 
            $location.path('/unauthorised'); // just redirect them to the unauthorised route.
        
    );
);

这意味着每次您的应用程序运行时,它都会检查您是否已登录。您还可以使用此配置文件服务获取有关用户的其他信息!您可以将此服务拉入您希望执行此类操作的任何其他模块中。

请记住,尝试保护 javascript 是没有意义的,因为它是在沙箱中运行的。但请始终确保在 MVC 代码中使用 [Authorize] 属性,以便服务器始终强制执行授权和身份验证。

在有人说这不是 Typescript 之前,任何 Javascript 也是有效的 Typescript。我把它留给用户输入类型定义。

-- 更多信息--

如果您经常请求信息,您可以在登录时添加xmin 缓存到期或将这些详细信息存储到本地存储中:

app.factory('ProfileService', function ($http, $q) 
    var service = 
        isLoggedIn: isLoggedIn
    ;

    var cacheExpiries = 
        loggedIn:  value: null, expiry: null 
    ;

    return service;

    function isLoggedIn() 
        var cacheObj = cacheExpiries['loggedIn'];
        var useCache = false;
        if (cacheObj.expiry) 
            useCache = new Date() < cacheObj.expiry;
         

        if (useCache)  
            // because http returns a promise we need to 
            // short circuit function with a promise
            return $q(function (res, rej) 
                res(cacheObj.value); 
            );
        

        // set the new expiry for the cache, this just adds 5 minutes to now
        cacheObj.expiry = new Date(new Date().setMinutes(new Date().getMinutes() + 5));
       
        return $http.get('Profile/IsLoggedIn').then(function (response) 
            cacheObj.value = response.data;
            return response.data; // this depends on what the response object looks like
        );
    
);

您可以使用适当的 key value 存储 api 轻松地将缓存封装到工厂中。这将从您的精简服务中提取一些逻辑(以保持精简)

本地存储和 Cookie 存储

多年来我一直使用这个module 来进行本地存储访问,它可以代替缓存或者你仍然可以让你的缓存服务来包装这个存储解决方案,这样你可以完全与您的依赖项保持分离。

【讨论】:

以上是关于将认证信息从 MVC 传递到 Angular的主要内容,如果未能解决你的问题,请参考以下文章

是否可以将认证对象(用户凭据)从 struts 传递到 Spring 框架 Web 应用程序?

如何在asp.net mvc中将数据从视图传递到控制器

MVC 4如何将模型属性列表从视图传递给Action

在 MVC 中将数据从视图传递到控制器时,数据列表变为空

将约束从视图传递到控制器 (MVC)

MVC4 将模型从视图传递到控制器