使用带有表单身份验证的角色

时间:2014-03-03 18:27:03

标签: asp.net-mvc-4 authorization forms-authentication

我在MVC应用程序中使用表单身份验证。这工作正常。但不是我想调整授权只允许某些角色的人。登录对应于活动目录中的用户,角色对应于用户所在的组。

对于身份验证,我只需在验证登录后调用FormsAuthentication.SetAuthCookie(用户名,true)。

对于授权,我首先将该属性应用于我想要保护的控制器

[Authorize(Roles = "AllowedUsers")]
public class MyController
...

接下来,我正在处理global.asax中的OnAuthenticate事件。

protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
    if (FormsAuthentication.CookiesSupported)
        {
            if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
            {
                try
                {
                    FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
                    Request.Cookies[FormsAuthentication.FormsCookieName].Value);

                    // Create WindowsPrincipal from username.  This adds active directory
                    // group memberships as roles to the user.
                    args.User = new WindowsPrincipal(new WindowsIdentity(ticket.Name));

                    FormsAuthentication.SetAuthCookie(ticket.Name, true);
                }
                catch (Exception e)
                {
                    // Decrypt method failed.
                }
        }
    }
    else
    {
        throw new HttpException("Cookieless Forms Authentication is not " + "supported for this application.");
    }
}

当有人访问该网站时,他们会获得登录屏幕。从那里他们实际上可以登录。然而,不知何故,它不保存auth cookie并且他们在他们点击的下一个链接之后获得登录屏幕。我尝试在OnAuthenticate()中添加对SetAuthCookie()的调用,但它们没有区别。

在我添加此事件处理程序以处理授权之前,身份验证工作正常。所以在框架中的某个地方正在设置用户。我想知道这是否是正确的做法,我只是遗漏了一些东西,或者我是否需要采用不同的方法。

我需要做些什么才能让它发挥作用?

谢谢, 斯科特

2 个答案:

答案 0 :(得分:1)

似乎我最初的方法不起作用。我试图让ASP.NET从他们的AD帐户自动加载用户角色。没有评论是否可能这样做。但是,我所做的研究表明,我必须编写代码来将AD组成员资格加载到用户角色中。

创建ASP.NET MVC使用的用户主体的解决方案似乎是在FormsAuthentication_OnAuthenticate()中创建它并将其分配给Context.User。如果我没有设置Context.User ASP.NET MVC在FormsAuthentication_OnAuthenticate()返回后基于auth票据创建用户主体,则会显示。此外,如果我在FormsAuthentication_OnAuthenticate()中设置它,ASP.NET MVC似乎对Context.User什么都不做。

以下是我最终要做的事情。

这是处理身份验证的代码

public ActionResult LogOn(FormCollection collection, string returnUrl)
{
    // Code that authenticates user against active directory
    if (authenticated)
    {
        var authTicket = new FormsAuthenticationTicket(username, true, 20);

        string encryptedTicket = FormsAuthentication.Encrypt(authTicket);

        var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
        authCookie.Expires = DateTime.Now.AddMinutes(30);
        Response.Cookies.Add(authCookie);

        if (Url.IsLocalUrl(returnUrl)
            && returnUrl.Length > 1
            && returnUrl.StartsWith("/", StringComparison.OrdinalIgnoreCase)
            && !returnUrl.StartsWith("//", StringComparison.OrdinalIgnoreCase)
            && !returnUrl.StartsWith("/\\", StringComparison.OrdinalIgnoreCase))
        {
            return Redirect(returnUrl);
        }
        else
        {
            return Redirect("~/");
        }
   }
   return View();
}

我最初尝试调用FormsAuthentication.SetAuthCookie(用户名,true),而不是手动创建,加密并将其添加到Response cookie集合。这在开发环境中起作用。但是,在我发布到网站之后,它没有。

这是注销码

public ActionResult LogOff()
{
    var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
    if (authCookie != null)
    {
        authCookie.Expires = DateTime.Today.AddDays(-1);
    }

    Response.Cookies.Add(authCookie);
    FormsAuthentication.SignOut();

    return RedirectToAction("Index", "Home");
}
在我切换到在登录代码中手动创建,加密和添加auth票证到响应cookie集合后,

FormsAuthentication.SignOut()似乎没有做任何事情。所以我不得不手动使cookie过期。

这是我对FormsAuthentication_OnAuthenticate()

的代码
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
    HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (authCookie == null || string.IsNullOrWhiteSpace(authCookie.Value))
        return;

    FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);

    UserData userData = null;
    if (Application["UserData_" + authTicket.Name] == null)
    {
        userData = new UserData(authTicket.Name);
        Application["UserData_" + authTicket.Name] = userData;
    }
    else
    {
        userData = (UserData)Application["UserData_" + authTicket.Name];
    }

    Context.User = new GenericPrincipal(new GenericIdentity(authTicket.Name), userData.Roles);
}

UserData是我创建的用于处理缓存用户角色的类。这是必需的,因为活动目录返回用户所属的组成员资格所需的时间。为了完整起见,以下是我对UserData的代码。

public class UserData
{
    private int _TimeoutInMinutes;
    private string[] _Roles = null;

    public string UserName { get; private set; }
    public DateTime Expires { get; private set; }
    public bool Expired { get { return Expires < DateTime.Now; } }
    public string[] Roles
    {
        get
        {
            if (Expired || _Roles == null)
            {
                _Roles = GetADContainingGroups(UserName).ToArray();
                Expires = DateTime.Now.AddMinutes(_TimeoutInMinutes);
            }
            return _Roles;
        }
    }

    public UserData(string userName, int timeoutInMinutes = 20)
    {
        UserName = userName;
        _TimeoutInMinutes = timeoutInMinutes;
    }
}

答案 1 :(得分:0)

角色也可以存储在Cookie中,您至少有两个选项:

  • 角色提供程序Cookie(另一个支持表单Cookie的Cookie),在cacheRolesInCookie="true"中的角色提供程序配置中使用web.config设置。第一次授权模块要求角色时会读取角色,然后发出cookie

  • 自定义角色提供程序,用于在表单cookie的userdata部分中存储角色,必须手动将角色添加到表单cookie的用户数据部分

Authorization模块询问当前主体的用户角色,如果启用了角色提供程序,则会扫描角色cookie(第一个选项)或触发自定义角色提供程序方法。

另一种推荐方法是切换到可以替换表单身份验证的会话身份验证模块(SAM)。有一些重要的优点,包括SAM从cookie中重新创建ClaimsPrincipal,角色只是Role声明:

// create cookie

SessionAuthenticationModule sam = 
   (SessionAuthenticationModule)
   this.Context.ApplicationInstance.Modules["SessionAuthenticationModule"];

ClaimsPrincipal principal = 
   new ClaimsPrincipal( new GenericPrincipal( new GenericIdentity( "username" ), null ) );

// create any userdata you want. by creating custom types of claims you can have
// an arbitrary number of your own types of custom data
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Role, "role1" ) );
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Role, "role2" ) );

var token = 
    sam.CreateSessionSecurityToken( 
        principal, null, DateTime.Now, DateTime.Now.AddMinutes( 20 ), false );
sam.WriteSessionTokenToCookie( token );

从现在开始,身份存储在cookie中并自动管理,是的,控制器上的Authorization属性按预期工作。

阅读更多关于在我的博客上用SAM替换表单模块的信息:

http://www.wiktorzychla.com/2012/09/forms-authentication-revisited.html