Microsoft ASP.NET Coding Strategies with the Microsoft ASP.NET Team (Pro-Developer)
Most security decisions do not focus on the rights of the individual user. Instead, a user belongs to a group, and application security focuses on authorizing these group roles for access to information and functionality. The application can customize the data displayed based on the groups to which the authenticated user belongs. Role management is driven by examination of the object stored by ASP.NET in the HttpContext.User property. This object implements the IPrincipal interface, which has just two members: the Identity of the user; and the IsInRole method, which checks for group membership.
Using Roles with Windows Authentication
When the authentication mode is Windows Authentication, ASP.NET creates a WindowsPrincipal object that is available in the HttpContext.User property. The WindowsPrincipal object is used to access the identity of the authenticated user. The identity can get you more information about the user, including access to the Windows account token, which can be used to access resources on behalf of the user. This IsInRole method takes the name of a role and returns a Boolean value indicating whether the user is a member of the role.
Group membership can be used to customize an application as well as restrict access. Code Listing 8-5 demonstrates programmatically retrieving the IPrincipal object and using it to customize the output when the user is in the Windows Administrators group. For the customization to work, both IIS and ASP.NET must be configured correctly. If IIS is configured to allow anonymous access, the IPrincipal object will correspond to the IUSER_WebServer account, which should not be a member of the Administrators group. If the authentication mode in the ASP.NET configuration is not Windows Authentication, the cast from HttpContext.User to WindowsPrincipal will fail. Notice that the code sample does not guard against the WindowsPrincipal object being null. A null value indicates that the configuration is incorrect.
Code Listing 8-5: CheckWindowsPrincipal.aspx
<%@Page language="C#" %><%@Import Namespace="System.Security.Principal" %><script runat="server">protected void Page_Load(object o, EventArgs e) { WindowsPrincipal principal = (WindowsPrincipal)HttpContext.Current.User; if(principal.IsInRole(WindowsBuiltInRole.Administrator)) { message.Text = "Secret message for administrators only!"; }}</script><form runat="server"><asp:Label runat="server" Text="Text that everyone can see!" /><br> </form>
Using Roles with Forms Authentication
ASP.NET Forms Authentication and Windows Authentication both make the IPrincipal object available from the HttpContext.User property; however, this object is just a GenericPrincipal object, and it doesn’t use the Windows operating system as a back end for validating roles. Instead, role management is implemented as part of the application. In the example that follows, we modify the way that the FormsAuthenticationTicket object is constructed in the login page so that it includes a role for the user. Then we add code to the global.asax file so that when subsequent requests come in for the authenticated user, the code creates GenericPrincipal with the role that we get back from the ticket. The GenericPrincipal object is then made available through the rest of the request processing.
Code Listing 8-6 is like Code Listing 8-4 except that the call to RedirectFromLoginPage is replaced with our own code (the differences are highlighted in the code listing). First, we create the ticket. Notice that the last argument to the FormsAuthenticationTicket constructor is user-defined data, our superusers role for this user. Then we encrypt the ticket and place it in the Response cookie collection and perform the redirect.
Code Listing 8-6: PrincipalLogin.aspx
<%@Page language="C#" %><script runat="server">protected void Page_Load(object o, EventArgs e) { if(IsPostBack) { if(AuthenticateUser(username.Text, password.Text)) { FormsAuthenticationTicket ticket = new FormsAuthenticationTicket( 1, username.Text, DateTime.Now, DateTime.Now.AddMinutes(30), false, "superusers" ); string encryptedTicket = FormsAuthentication.Encrypt(ticket); Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)); Response.Redirect(FormsAuthentication.GetRedirectUrl( username.Text, false)); } else { instructions.Text = "Please try again!"; instructions.ForeColor = System.Drawing.Color.Red; } }} bool AuthenticateUser(string username, string password) { if((username == "TheUsername") && (password == "ThePassword")) { return true; } return false;}</script> <form runat="server"><asp:Label runat="server" Text="Please input your credentials:" /><br> Username: <asp:Textbox runat="server" /><br>Password: <asp:Textbox runat="server" TextMode="Password" /><br><asp:button runat="server" Text="LOGIN" /></form>
Unless we also take over creating the GenericPrincipal class (which implements IPrincipal) on subsequent requests, the role will not be populated. Code Listing 8-7 provides an EventHandler for the AuthenticateRequest event. Because global.asax, when installed at the application root, responds to application-level events, ASP.NET raises this event at the beginning of every page request. This example neglects error handling completely while extracting the ticket from the Forms Authentication cookie. It constructs the GenericPrincipal used throughout the request by using a FormsIdentity object created with the ticket and the role that was stored in the ticket’s user data.
Code Listing 8-7: Global.asax
<%@import namespace="System.Security.Principal" %><script runat="server" language="C#"> protected void Application_AuthenticateRequest(object o, EventArgs e) { HttpCookie cookie = Request.Cookies[FormsAuthentication.FormsCookieName]; if(cookie != null) { FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value); string[] role = new string[]{ticket.UserData}; Context.User = new GenericPrincipal(new FormsIdentity(ticket), role); }}</script>
For completeness, Code Listing 8-8 is included. Its use of the WindowsPrincipal is nearly identical to the use of IPrincipal object in Code Listing 8-5, except that Code Listing 8-8 no longer relies on built-in Windows groups for role management. Rather, it uses an arbitrary role that we created when the user was logging on and persisted during the authentication phase of each request. For the cast to this GenericPrincipal object to work, you must have the global.asax file in the application’s root directory and change the authentication mode attribute in web.config to Forms as it was in Code Listing 8-3. Also, the PrincipalLogin.aspx page must be used for authentication, which can be configured by renaming it to the default login.aspx value or by setting loginUrl attribute of the forms element to the page name in the web.config.
Code Listing 8-8: FormsAuthRoles.aspx
<%@Page language="C#" %><%@Import Namespace="System.Security.Principal" %><script runat="server">protected void Page_Load(object o, EventArgs e) { GenericPrincipal principal = (GenericPrincipal)HttpContext.Current.User; if(principal.IsInRole("superusers")) { message.Text = "Message for forms auth superusers."; }}</script> <form runat="server"><asp:Label runat="server" Text="Text the everyone can see!" /><br> </form>
Категории