ASP.NET MVC 2 网关页面授权

发布于 2024-11-30 19:11:36 字数 418 浏览 0 评论 0原文

我有一个 MVC 2 应用程序,它不会进行自己的身份验证,但会从 HTTP 请求标头检索用户 ID,因为用户在到达应用程序之前必须通过网关。

进入应用程序后,我们需要将用户 ID 与“用户”表中的信息进行匹配,其中包含应用程序使用的一些安全详细信息。

我熟悉在 ASP.NET 中设置自定义成员身份和角色提供程序,但这感觉很不同,因为用户一旦通过网关应用程序就永远不会看到登录页面。

问题:

  1. 如果有的话,如何保留用户 ID?它从请求标头开始,但我必须将其放入 cookie 中吗?会话状态怎么样?
  2. 我在哪里/什么时候可以获得这些信息?母版页显示用户的名称,因此它应该随处可用。

如果可能的话,我仍然想在控制器中使用 [Authorize(Roles="...")] 标签。

I've got an MVC 2 application which won't be doing its own authentication, but will retrieve a user ID from the HTTP request header, since users must pass through a gateway before reaching the application.

Once in the app, we need to match up the user ID to information in a "users" table, which contains some security details the application makes use of.

I'm familiar with setting up custom membership and roles providers in ASP.NET, but this feels so different, since the user never should see a login page once past the gateway application.

Questions:

  1. How do I persist the user ID, if at all? It starts out in the request header, but do I have to put it in a cookie? How about SessionState?
  2. Where/when do I get this information? The master page shows the user's name, so it should be available everywhere.

I'd like to still use the [Authorize(Roles="...")] tag in my controller if possible.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

极致的悲 2024-12-07 19:11:36

我们的工作环境非常相似。正如 @Mystere Man 提到的,此设置存在风险,但如果整个基础设施设置并正确运行,我们发现它是一个安全的设置(我们确实关心安全性)。需要确保的一件事是,SiteMinder 代理正在您尝试保护的 ​​IIS 节点上运行,因为它将验证也在标头中传递的加密 SMSESSION 密钥,这将使请求安全(这将极其困难)欺骗 SMSESSION 标头的值)。

我们正在使用 ASP.NET MVC3,它具有全局操作过滤器,这正是我们正在使用的。但是使用 MVC2,您可以创建一个普通的控制器级别操作过滤器,该过滤器可以应用于基本控制器类,以便所有控制器/操作都得到保护。

我们创建了一个自定义配置部分,允许我们通过 web.config 打开和关闭此安全过滤器。如果它被关闭,我们的配置部分的属性将允许您“模拟”具有给定角色的给定用户以进行测试和调试。此配置部分还允许我们存储我们在配置中查找的标头键的值,以防供应商更改我们的标头键名称。

public class SiteMinderConfiguration : ConfigurationSection
{
    [ConfigurationProperty("enabled", IsRequired = true)]
    public bool Enabled
    {
        get { return (bool)this["enabled"]; }
        set { this["enabled"] = value; }
    }

    [ConfigurationProperty("redirectTo", IsRequired = true)]
    public RedirectToElement RedirectTo
    {
        get { return (RedirectToElement)this["redirectTo"]; }
        set { this["redirectTo"] = value; }
    }

    [ConfigurationProperty("sessionCookieName", IsRequired = true)]
    public SiteMinderSessionCookieNameElement SessionCookieName
    {
        get { return (SiteMinderSessionCookieNameElement)this["sessionCookieName"]; }
        set { this["sessionCookieName"] = value; }
    }

    [ConfigurationProperty("userKey", IsRequired = true)]
    public UserKeyElement UserKey
    {
        get { return (UserKeyElement)this["userKey"]; }
        set { this["userKey"] = value; }
    }

    [ConfigurationProperty("rolesKey", IsRequired = true)]
    public RolesKeyElement RolesKey
    {
        get { return (RolesKeyElement)this["rolesKey"]; }
        set { this["rolesKey"] = value; }
    }

    [ConfigurationProperty("firstNameKey", IsRequired = true)]
    public FirstNameKeyElement FirstNameKey
    {
        get { return (FirstNameKeyElement)this["firstNameKey"]; }
        set { this["firstNameKey"] = value; }
    }

    [ConfigurationProperty("lastNameKey", IsRequired = true)]
    public LastNameKeyElement LastNameKey
    {
        get { return (LastNameKeyElement)this["lastNameKey"]; }
        set { this["lastNameKey"] = value; }
    }

    [ConfigurationProperty("impersonate", IsRequired = false)]
    public ImpersonateElement Impersonate
    {
        get { return (ImpersonateElement)this["impersonate"]; }
        set { this["impersonate"] = value; }
    }
}

public class SiteMinderSessionCookieNameElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class RedirectToElement : ConfigurationElement
{
    [ConfigurationProperty("loginUrl", IsRequired = false)]
    public string LoginUrl
    {
        get { return (string)this["loginUrl"]; }
        set { this["loginUrl"] = value; }
    }
}

public class UserKeyElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class RolesKeyElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class FirstNameKeyElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class LastNameKeyElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class ImpersonateElement : ConfigurationElement
{
    [ConfigurationProperty("username", IsRequired = false)]
    public UsernameElement Username
    {
        get { return (UsernameElement)this["username"]; }
        set { this["username"] = value; }
    }

    [ConfigurationProperty("roles", IsRequired = false)]
    public RolesElement Roles
    {
        get { return (RolesElement)this["roles"]; }
        set { this["roles"] = value; }
    }
}

public class UsernameElement : ConfigurationElement 
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class RolesElement : ConfigurationElement 
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

所以我们的 web.config 看起来像这样

<configuration>
  <configSections>
    <section name="siteMinderSecurity" type="MyApp.Web.Security.SiteMinderConfiguration, MyApp.Web" />
    ...
  </configSections>
  ...
  <siteMinderSecurity enabled="false">
    <redirectTo loginUrl="https://example.com/login/?ReturnURL={0}"/>
    <sessionCookieName value="SMSESSION"/>
    <userKey value="SM_USER"/>
    <rolesKey value="SN-AD-GROUPS"/>
    <firstNameKey value="SN-AD-FIRST-NAME"/>
    <lastNameKey value="SN-AD-LAST-NAME"/>
    <impersonate>
      <username value="ImpersonateMe" />
      <roles value="Role1, Role2, Role3" />
    </impersonate>
  </siteMinderSecurity>
  ...
</configuration>

我们有一个自定义的 SiteMinderIdentity...

public class SiteMinderIdentity : GenericIdentity, IIdentity
{
    public SiteMinderIdentity(string name, string type) : base(name, type) { }
    public IList<string> Roles { get; set; }
}

和一个自定义的 SiteMinderPrincipal...

public class SiteMinderPrincipal : GenericPrincipal, IPrincipal
{
    public SiteMinderPrincipal(IIdentity identity) : base(identity, null) { }
    public SiteMinderPrincipal(IIdentity identity, string[] roles) : base(identity, roles) { }
}

我们填充 HttpContext.Current.UserThread.CurrentPrincipal使用我们根据从操作过滤器中的请求标头中提取的信息构建的 SiteMinderPrincipal 实例...

public class SiteMinderSecurity : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        var request = filterContext.HttpContext.Request;
        var response = filterContext.HttpContext.Response;

        if (MyApp.SiteMinderConfig.Enabled)
        {
            string[] userRoles = null; // default to null
            userRoles = Array.ConvertAll(request.Headers[MyApp.SiteMinderConfig.RolesKey.Value].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries), r => r.Trim());

            var identity = new SiteMinderIdentity(request.Headers[MyApp.SiteMinderConfig.UserKey.Value];, "SiteMinder");
            if (userRoles != null)
                identity.Roles = userRoles.ToList();
            var principal = new SiteMinderPrincipal(identity, userRoles);

            HttpContext.Current.User = principal;
            Thread.CurrentPrincipal = principal;
        }
        else
        {
            var roles = Array.ConvertAll(MyApp.SiteMinderConfig.Impersonate.Roles.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries), r => r.Trim());
            var identity = new SiteMinderIdentity(MyApp.SiteMinderConfig.Impersonate.Username.Value, "SiteMinder") { Roles = roles.ToList() };
            var principal = new SiteMinderPrincipal(identity, roles);

            HttpContext.Current.User = principal;
            Thread.CurrentPrincipal = principal;
        }
    }
}

MyApp 是一个静态类,它获取在应用程序启动时初始化,缓存配置信息,因此我们不会在每个请求时从 web.config 中读取它...

public static class MyApp
{
    private static bool _isInitialized;
    private static object _lock;

    static MyApp()
    {
        _lock = new object();
    }

    private static void Initialize()
    {
        if (!_isInitialized)
        {
            lock (_lock)
            {
                if (!_isInitialized)
                {
                    // Initialize application version number
                    _version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
                    _siteMinderConfig = (SiteMinderConfiguration)ConfigurationManager.GetSection("siteMinderSecurity");

                    _isInitialized = true;
                }
            }
        }
    }

    private static string _version;
    public static string Version
    {
        get
        {
            Initialize();
            return _version;
        }
    }

    private static SiteMinderConfiguration _siteMinderConfig;
    public static SiteMinderConfiguration SiteMinderConfig
    {
        get
        {
            Initialize();
            return _siteMinderConfig;
        }
    }
}

根据我收集的情况,您在数据库中有信息,您需要根据以下信息进行查找标题中的信息来获取您需要的一切,因此这并不完全是您所需要的,但看起来它至少应该让您开始。

希望这有帮助。

We have a very similar setup where I work. As @Mystere Man mentioned, there are risks with this setup, but if the whole infrastructure is setup and running correctly, we have found it to be a secure setup (we do care about security). One thing to ensure, is that the SiteMinder agent is running on the IIS node you're trying to secure, as it will validate an encrypted SMSESSION key also passed in the headers, which will make the requests secure (it would be extremely difficult to spoof the value of the SMSESSION header).

We are using ASP.NET MVC3, which has global action filters, which is what we're using. But with MVC2, you could create a normal, controller level action filter that could be applied to a base controller class so that all of your controllers/actions will be secured.

We have created a custom configuration section that allows us to turn this security filter on and off via web.config. If it's turned off, our configuration section has properties that will allow you to "impersonate" a given user with given roles for testing and debugging purposes. This configuration section also allows us to store the values of the header keys we're looking for in config as well, in case the vendor ever changes the header key names on us.

public class SiteMinderConfiguration : ConfigurationSection
{
    [ConfigurationProperty("enabled", IsRequired = true)]
    public bool Enabled
    {
        get { return (bool)this["enabled"]; }
        set { this["enabled"] = value; }
    }

    [ConfigurationProperty("redirectTo", IsRequired = true)]
    public RedirectToElement RedirectTo
    {
        get { return (RedirectToElement)this["redirectTo"]; }
        set { this["redirectTo"] = value; }
    }

    [ConfigurationProperty("sessionCookieName", IsRequired = true)]
    public SiteMinderSessionCookieNameElement SessionCookieName
    {
        get { return (SiteMinderSessionCookieNameElement)this["sessionCookieName"]; }
        set { this["sessionCookieName"] = value; }
    }

    [ConfigurationProperty("userKey", IsRequired = true)]
    public UserKeyElement UserKey
    {
        get { return (UserKeyElement)this["userKey"]; }
        set { this["userKey"] = value; }
    }

    [ConfigurationProperty("rolesKey", IsRequired = true)]
    public RolesKeyElement RolesKey
    {
        get { return (RolesKeyElement)this["rolesKey"]; }
        set { this["rolesKey"] = value; }
    }

    [ConfigurationProperty("firstNameKey", IsRequired = true)]
    public FirstNameKeyElement FirstNameKey
    {
        get { return (FirstNameKeyElement)this["firstNameKey"]; }
        set { this["firstNameKey"] = value; }
    }

    [ConfigurationProperty("lastNameKey", IsRequired = true)]
    public LastNameKeyElement LastNameKey
    {
        get { return (LastNameKeyElement)this["lastNameKey"]; }
        set { this["lastNameKey"] = value; }
    }

    [ConfigurationProperty("impersonate", IsRequired = false)]
    public ImpersonateElement Impersonate
    {
        get { return (ImpersonateElement)this["impersonate"]; }
        set { this["impersonate"] = value; }
    }
}

public class SiteMinderSessionCookieNameElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class RedirectToElement : ConfigurationElement
{
    [ConfigurationProperty("loginUrl", IsRequired = false)]
    public string LoginUrl
    {
        get { return (string)this["loginUrl"]; }
        set { this["loginUrl"] = value; }
    }
}

public class UserKeyElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class RolesKeyElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class FirstNameKeyElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class LastNameKeyElement : ConfigurationElement
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class ImpersonateElement : ConfigurationElement
{
    [ConfigurationProperty("username", IsRequired = false)]
    public UsernameElement Username
    {
        get { return (UsernameElement)this["username"]; }
        set { this["username"] = value; }
    }

    [ConfigurationProperty("roles", IsRequired = false)]
    public RolesElement Roles
    {
        get { return (RolesElement)this["roles"]; }
        set { this["roles"] = value; }
    }
}

public class UsernameElement : ConfigurationElement 
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

public class RolesElement : ConfigurationElement 
{
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
        get { return (string)this["value"]; }
        set { this["value"] = value; }
    }
}

So our web.config looks something like this

<configuration>
  <configSections>
    <section name="siteMinderSecurity" type="MyApp.Web.Security.SiteMinderConfiguration, MyApp.Web" />
    ...
  </configSections>
  ...
  <siteMinderSecurity enabled="false">
    <redirectTo loginUrl="https://example.com/login/?ReturnURL={0}"/>
    <sessionCookieName value="SMSESSION"/>
    <userKey value="SM_USER"/>
    <rolesKey value="SN-AD-GROUPS"/>
    <firstNameKey value="SN-AD-FIRST-NAME"/>
    <lastNameKey value="SN-AD-LAST-NAME"/>
    <impersonate>
      <username value="ImpersonateMe" />
      <roles value="Role1, Role2, Role3" />
    </impersonate>
  </siteMinderSecurity>
  ...
</configuration>

We have a custom SiteMinderIdentity...

public class SiteMinderIdentity : GenericIdentity, IIdentity
{
    public SiteMinderIdentity(string name, string type) : base(name, type) { }
    public IList<string> Roles { get; set; }
}

And a custom SiteMinderPrincipal...

public class SiteMinderPrincipal : GenericPrincipal, IPrincipal
{
    public SiteMinderPrincipal(IIdentity identity) : base(identity, null) { }
    public SiteMinderPrincipal(IIdentity identity, string[] roles) : base(identity, roles) { }
}

And we populate HttpContext.Current.User and Thread.CurrentPrincipal with an instance of SiteMinderPrincipal that we build up based on information that we pull from the request headers in our action filter...

public class SiteMinderSecurity : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        var request = filterContext.HttpContext.Request;
        var response = filterContext.HttpContext.Response;

        if (MyApp.SiteMinderConfig.Enabled)
        {
            string[] userRoles = null; // default to null
            userRoles = Array.ConvertAll(request.Headers[MyApp.SiteMinderConfig.RolesKey.Value].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries), r => r.Trim());

            var identity = new SiteMinderIdentity(request.Headers[MyApp.SiteMinderConfig.UserKey.Value];, "SiteMinder");
            if (userRoles != null)
                identity.Roles = userRoles.ToList();
            var principal = new SiteMinderPrincipal(identity, userRoles);

            HttpContext.Current.User = principal;
            Thread.CurrentPrincipal = principal;
        }
        else
        {
            var roles = Array.ConvertAll(MyApp.SiteMinderConfig.Impersonate.Roles.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries), r => r.Trim());
            var identity = new SiteMinderIdentity(MyApp.SiteMinderConfig.Impersonate.Username.Value, "SiteMinder") { Roles = roles.ToList() };
            var principal = new SiteMinderPrincipal(identity, roles);

            HttpContext.Current.User = principal;
            Thread.CurrentPrincipal = principal;
        }
    }
}

MyApp is a static class that gets initialized at application startup that caches the configuration information so we're not reading it from web.config on every request...

public static class MyApp
{
    private static bool _isInitialized;
    private static object _lock;

    static MyApp()
    {
        _lock = new object();
    }

    private static void Initialize()
    {
        if (!_isInitialized)
        {
            lock (_lock)
            {
                if (!_isInitialized)
                {
                    // Initialize application version number
                    _version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
                    _siteMinderConfig = (SiteMinderConfiguration)ConfigurationManager.GetSection("siteMinderSecurity");

                    _isInitialized = true;
                }
            }
        }
    }

    private static string _version;
    public static string Version
    {
        get
        {
            Initialize();
            return _version;
        }
    }

    private static SiteMinderConfiguration _siteMinderConfig;
    public static SiteMinderConfiguration SiteMinderConfig
    {
        get
        {
            Initialize();
            return _siteMinderConfig;
        }
    }
}

From what I gather of your situation, you have information in a database that you'll need to lookup based on the information in the headers to get everything you need, so this won't be exactly what you need, but it seems like it should at least get you started.

Hope this helps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文