【问题标题】:Obtaining the current Principal outside of the Web tier在 Web 层之外获取当前的 Principal
【发布时间】:2012-07-18 14:37:24
【问题描述】:

我有以下 ntier 应用程序:MVC > 服务 > 存储库 > 域。我正在使用表单身份验证。在我的 MVC 层之外使用 Thread.CurrentPrincipal 来获取我的应用程序的当前登录用户是否安全,或者我应该使用 HttpContext.Current.User 吗?

我问的原因是 Thread.CurrentPrincipal 似乎存在一些问题,但我谨慎地在 MVC 层之外添加对 System.Web 的引用,以防我将来需要提供非 Web 字体端.

更新

到目前为止,我一直遵循收到的建议,将用户名作为被调用方法的参数的一部分传递到服务中,这导致了我最初的问题的改进。我需要能够检查用户是否在我的许多服务和域方法中处于特定角色。似乎有几个解决方案,只是想知道哪种方法最好:

  1. 将整个 HttpContext.Current.User 作为参数传递,而不仅仅是用户名。
  2. 在我的 Web 层之外调用 Thread.CurrentPrincipal 并使用它。但是如何确保它等于 HttpContext.Current.User?
  3. 坚持按照目前的建议传递用户名,然后使用 Roles.IsUserInRole。这种方法的问题是它需要一个对 System.Web 的引用,我觉得这在我的 MVC 层之外是不正确的。

您建议我如何进行?

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-3 authentication forms-authentication iprincipal


    【解决方案1】:

    我也不会这样做,HttpContext.Current.User 是特定于您的 Web 层的。

    为什么不将用户名注入您的服务层?

    【讨论】:

    • 所以我可以将 HttpContext.Current.User 注入到我的服务中,服务接受 ctor 中的 IPrincipal?
    • 将通过 HttpContext.Current.User 填充的用户名传递到服务层。它可以是字符串参数,如果您需要更多值,甚至可以是新的对象参数。所以不,不是 IPrincipal。是的,成功登录后。
    • 如果您删除 Thread.CurrentPrincipal 并使用您自己的 POCO,您将拥有更大的灵活性并且更改会更容易。另一方面,您必须替换现有的代码,这些代码可能适合您相对简单的要求。您必须根据项目动态决定采用哪条路径,如果完成后更新页面会很好,这样我们就可以看到它是如何进行的。
    • 我还在调查这个。我面临的问题是,例如,我有一个名为 Document 的域对象。 Document 上有一个 CurrentUserCanEdit() 方法,它执行以下检查:Thread.CurrentPrincipal.IsInRole(documentEditorRole)。到目前为止,这似乎运行正常。唯一的选择似乎是每次调用角色敏感方法时都将所有用户角色传递到服务层,我不想这样做。
    • 为什么服务层不能查找角色?
    【解决方案2】:

    将相关的用户详细信息映射到新的Class 以表示 LoggedInUser 并将其作为参数传递给您的业务层方法

     public class LoggedInUser
     {
       public string UserName { set;get;}
       //other relevant proerties
     }
    

    现在设置 this 的值并传递给你的 BL 方法

    var usr=new LoggedInUser();
    usr.UserName="test value ";  //Read from the FormsAuthentication stuff and Set
    var result=YourBusinessLayerClass.SomeOperation(usr);
    

    【讨论】:

      【解决方案3】:

      您应该抽象您的用户信息,使其不依赖于Thread.CurrentPrincipalHttpContext.Current.User

      例如,您可以添加接受用户名的构造函数或方法参数。

      这是一个过于简化的构造函数参数示例:

      class YourBusinessClass 
      {
         string _userName;
         public YourBusinessClass(string userName)
         {
            _userName = userName;
         }
      
         public void SomeBusinessMethodThatNeedsUserName()
         {
            if (_userName == "sally")
            {
               // do something for sally
            }
         }
      }
      

      【讨论】:

      • 您的意思是创建我自己的 IPrincipal 实现吗?这会在成功登录期间设置吗?如果是这样,我将如何在业务层中检索它?
      • 不,只有业务层需要的东西。假设您只需要用户名,您可以执行类似于我添加到答案中的示例。 Shyju、Joe R 和我基本上都在说同样的话,只为您的业务层提供它需要的东西。
      • 同意,我们确实在说同样的话。 :-)
      • 所以我认为不应使用 Thread.CurrentPrincipal 是正确的?我有许多关于域对象的方法,例如 CanEdit,它们对此进行检查以确保用户通过身份验证。这很好用,因为我可以将结果自动映射到我的 Viewmodel 上的布尔值。如果 Thread.CurrentPrincipal 从我的域中删除,我似乎需要进行一些重大更改!
      【解决方案4】:

      我更喜欢选项 2(在 Web 层之外使用 Thread.CurrentPrincipal )。因为这不会影响您的服务层和数据层方法。附带奖金:您可以在自定义主体中存储您的角色 + 附加信息;

      确保您的服务和数据层中的 Thread.CurrentPrincipal 与您的 Web 层相同;您可以在 Global.asax(Application_AuthenticateRequest) 中设置您的 HttpContext.Current.User (Context.User)。底部添加了您可以设置的其他替代位置。

      示例代码:

          //sample synchronizing HttpContext.Current.User with Thread.CurrentPrincipal
          protected void Application_AuthenticateRequest(Object sender, EventArgs e)
          {
              HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
      
              //make sure principal is not set for anonymous user/unauthenticated request
              if (authCookie != null && Request.IsAuthenticated)
              {
                  FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
      
                  //your additional info stored in cookies: multiple roles, privileges, etc
                  string userData = authTicket.UserData;
      
                  CustomPrincipal userPrincipal = PrincipalHelper.CreatePrincipal(authTicket.Name, authTicket.UserData, Request.IsAuthenticated);
      
                  Context.User = userPrincipal;
              }
          }
      

      当然,首先您必须实现您的登录表单以创建包含您的自定义主体的授权 cookie。

      Application_AuthenticateRequest 将对服务器的任何请求(css 文件、javascript 文件、图像文件等)执行。要将此功能仅限于控制器操作,您可以尝试在 ActionFilter 中设置自定义主体(我没有尝试过)。我尝试的是在 Interceptor for Controllers 中设置此功能(我使用 Castle Windsor 进行依赖注入和面向方面的编程)。

      【讨论】:

        【解决方案5】:

        我相信您遇到了这个问题,因为您需要进一步限制您的域责任。您的服务或文件不应该负责处理授权。该责任应由您的 MVC 层处理,因为当前用户登录到您的 Web 应用程序,而不是您的域。

        如果您不是尝试从您的服务或文档中查找当前用户,而是在您的 MVC 应用程序中执行检查,您会得到如下结果:

        if(Roles.IsUserInRole("DocumentEditorRole")){
        
            //UpdateDocument does NOT authorize the user. It does only 1 thing, update the document.
            myDocumentService.UpdateDocument(currentUsername, documentToEdit);
        
        } else {
        
            lblPermissionDenied.InnerText = @"You do not have permission 
                                              to edit this document.";
        
        }
        

        它干净、易于阅读,并且允许您使您的服务和域类免于授权问题。您仍然可以将Roles.IsUserInRole("DocumentEditorRole") 映射到您的视图模型,因此您唯一丢失的是 Document 类上的 CurrentUserCanEdit 方法。但是,如果您认为您的域模型代表真实世界的对象,那么该方法无论如何都不属于 Document。您可能会将其视为域用户对象 (user.CanEditDocument(doc)) 上的一种方法,但总而言之,如果您将授权排除在域层之外,我认为您会更开心。

        【讨论】:

        • 我喜欢这个答案,因为它让事情变得简单。然而,由于 MVC 层实际上只是用于表示,因此将业务逻辑应用到 MVC 层之外是有意义的(因为稍后可以使用不同的表示)。此外,由于每个服务类不引用任何其他服务类,因此将逻辑放在域类上是有意义的,因为任何服务都可以访问。
        • 如果我需要更改我的表示层会发生什么?我必须重新解决所有安全问题。
        • 这个答案已经很久了,我今天可能不会走这条路,但我仍然认为它有优点,因为它让事情变得简单。正确的做法很大程度上取决于项目的规模,这可能适用于较小的项目。如果您将授权推送到域中,您还需要“未授权”异常层次结构或类似机制,以及在表示层中处理此反馈。总而言之,这将非常复杂,所以你应该确定你需要它:-)
        猜你喜欢
        • 2015-02-08
        • 2017-06-11
        • 2020-05-21
        • 1970-01-01
        • 1970-01-01
        • 2015-09-12
        • 2019-04-16
        • 2014-10-21
        • 1970-01-01
        相关资源
        最近更新 更多