IsAuthenticated is false! weird behaviour + review

2019-07-20 13:16发布

问题:

This is the login function (after I validate user name and password, I load user data into "user" variable and call Login function:

public static void Login(IUser user)
{
    HttpResponse Response = HttpContext.Current.Response;
    HttpRequest Request = HttpContext.Current.Request;

    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
        user.UserId.ToString(), DateTime.Now, DateTime.Now.AddHours(12), false,
        UserResolver.Serialize(user));

    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
        FormsAuthentication.Encrypt(ticket));
    cookie.Path = FormsAuthentication.FormsCookiePath;

    Response.Cookies.Add(cookie);

    string redirectUrl = user.HomePage;

    Response.Redirect(redirectUrl, true);
}

UserResolver is the following class:

public class UserResolver
{
    public static IUser Current
    {
        get
        {
            IUser user = null;
            if (HttpContext.Current.User.Identity.IsAuthenticated)
            {
                FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
                FormsAuthenticationTicket ticket = id.Ticket;
                user = Desrialize(ticket.UserData);
            }
            return user;
        }
    }

    public static string Serialize(IUser user)
    {
        StringBuilder data = new StringBuilder();
        StringWriter w = new StringWriter(data);
        string type = user.GetType().ToString();
        //w.Write(type.Length);
        w.WriteLine(user.GetType().ToString());
        StringBuilder userData = new StringBuilder();
        XmlSerializer serializer = new XmlSerializer(user.GetType());
        serializer.Serialize(new StringWriter(userData), user);
        w.Write(userData.ToString());
        w.Close();
        return data.ToString();
    }

    public static IUser Desrialize(string data)
    {
        StringReader r = new StringReader(data);
        string typeStr = r.ReadLine();
        Type type=Type.GetType(typeStr);
        string userData = r.ReadToEnd();
        XmlSerializer serializer = new XmlSerializer(type);
        return (IUser)serializer.Deserialize(new StringReader(userData));
    }
}

And the global.asax implements the following:

void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
    IPrincipal p = HttpContext.Current.User;
    if (p.Identity.IsAuthenticated)
    {
        IUser user = UserResolver.Current;
        Role[] roles = user.GetUserRoles();
        HttpContext.Current.User = Thread.CurrentPrincipal =
            new GenericPrincipal(p.Identity, Role.ToString(roles));
    }
}

First question: Am I do it right?

Second question - weird thing! The user variable I pass to Login has 4 members: UserName, Password, Name, Id. When UserResolver.Current executed, I got the user instance. I descided to change the user structure - I add an array of Warehouse object. Since that time, when UserResolver.Current executed (after Login), HttpContext.Current.User.Identity.IsAuthenticated was false and I couldn't get the user data. When I removed the Warehouse[] from user structure, it starts to be ok again and HttpContext.Current.User.Identity.IsAuthenticated become true after I Login.

What is the reason to this weird behaviour?

回答1:

First, you don't need to do an HttpContext.Current from Global.asax. Global.asax derives from HttpApplication. So all you need to do is to get the Context property. This might help make that code a little cleaner.

    //this is all you need in your global.asax
    void Application_PostAuthenticateRequest(Object sender, EventArgs e)
    {
        if(Context.User.Identity.IsAuthenticated)
        {
            var user = UserResolver.Current;
            Context.User = Thread.CurrentPrincipal = new UserWrapperPrincipal(user, Context.User.Identity);
        }
    }

    //this helper class separates the complexity
    public class UserWrapperPrincipal: IPrincipal, IUser
    {
        private readonly IUser _user;
        private readonly IIdentity _identity;

        public UserWrapperPrincipal(IUser user, IIdentity identity)
        {
            _user = user;
            _identity = identity;
        }

        private IList<string> RoleNames
        {
            get { return _user.GetUserRoles().Select(role => role.ToString()); }
        }

        public IIdentity Identity { get { return _identity; } }

        public bool IsInRole(string role) { return RoleNames.Contains(role); }

    }

Based on your error, it seems like the issue is that either your serializing function or your deserializing function corrupts the data. However, the problem area is probably not those functions. Either there is an issue in serializing the Warehouse object (serializing complex types can sometimes be tricky), or in the serialization of the actual array. Since you are using the default .NET XmlSerializer, There is a good article on customizing and controlling the way different objects are handled available at http://www.diranieh.com/NETSerialization/XMLSerialization.htm .

On another note, are you sure that this is the best way for you to store this data in your application? Storing a user-id and name makes sense. When you start storing serialized arrays of complex objects in your cookie, it might indicate you are not approaching the problem correctly to begin with.



回答2:

I am guessing that your code is in a log on event somewhere and your building a custom forms auth.

You also need to then build the User object from the cookie on every page request

public class AuthHttpModule : IHttpModule { 
    public virtual void Init(HttpApplication app) {
        app.AuthenticateRequest += new EventHandler(app_AuthenticateRequest);
    }
    private void app_AuthenticateRequest(object source, EventArgs e) {  
        HttpCookie cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
        if (cookie == null) {
            HttpContext.Current.User = null;
        } else {
            cookie = HttpContext.Current.Response.Cookies[FormsAuthentication.FormsCookieName];
            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
            HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new FormsIdentity(ticket), new string[0]);
        }
        bool result = HttpContext.Current.Request.IsAuthenticated;
    }
}

EDIT

Try adding this to your global

void Application_AuthenticateRequest(Object sender, EventArgs e)
    HttpCookie cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (cookie != null) {
        FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
        HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new FormsIdentity(ticket), new string[0]);
    }
}