Detailed ASP.net MVC form verification _ practical Tips

Source: Internet
Author: User
Tags anonymous decrypt httpcontext ticket

First, the preface

There have been a lot of articles about form validation, and I believe that Web developers are basically writing about it, and have recently been used in a personal project to share it with you. Originally wanted to start from user registration, but found more things, involving the interface, front-end verification, front-end encryption, background decryption, user password hash, permission verification, etc., the article may be very long, so here is the main introduction is the login verification and permission Control section, interested friends Welcome to exchange.

General authentication methods include Windows validation and form validation, and Web projects use more forms validation. The principle is simple, simply to use the browser cookie, the authentication token stored in the client browser, the cookie will be sent to the server each time, the server verifies the token. Usually a user of a system can be divided into multiple roles: Anonymous, ordinary, and admin; there is a breakdown, such as the user can be ordinary users or VIP users, administrators can be ordinary administrators or super administrators. In the project, some of our pages may only allow the administrator to view, some only allow the logged-in user to view, this is the role distinction (Roles); In some special cases, some pages may only be allowed to be viewed by people named "John", which is the user distinction (users).

Let's take a look at the last effect we want to achieve:

1. This is the control at the action level.

public class Home1controller:controller
{
  //anonymous access public
  actionresult Index ()
  {return
    View ();
  }
  //Login user access
  [Requestauthorize]
  Public ActionResult Index2 ()
  {return
    View ();
  }
  Login user, John to access
  [Requestauthorize (users= "John")] public
  actionresult Index3 ()
  {return
    View ()
  }
  //administrator access
  [Requestauthorize (roles= "Admin")]
  Public ActionResult Index4 ()
  {return
    View ();
  }
}

2. This is controlled at the controller level. of course, if an action requires anonymous access, it is also allowed because the action priority is greater than controller at the control level.

Controller-level permission Control
[Requestauthorize (user= "John")] public
class Home2controller:controller
{
  The logged-on user accesses public
  actionresult Index ()
  {return
    View ();
  }
  Allow anonymous access to
  [allowanonymous] public
  actionresult Index2 ()
  {return
    View ();
  }
}

3.Area level of control. sometimes we make partitions of some of the modules, of course, we can also mark the Controller and action in area.

As you can see from the above, we need to mark permissions everywhere, and it's not a good idea to write roles and users hard in the program. I would like to be a little simpler in the configuration file for the description. For example, configure the following:

<?xml version= "1.0" encoding= "Utf-8"?>
<!--
  1. You can transfer permission control to a configuration file so that you don't have to write roles and users in the program
  2. If the program is also written, the configuration file will be overwritten.
  3.action-Level Priority > Controller level > Area level  
-->
<root>
 <!--area level-->
 <area Name= "Admin" >
  <roles>Admin</roles>
 </area>
  
 <!--controller level-->
 <controller name= "Home2" >
  <user> John </user>
 </controller>
  
 <!-- Action level-->
 <controller name= "Home1" >
  <action name= "Inde3" >
   <users> John </ users>
  </action>
  <action name= "Index4" >
   <roles>Admin</roles>
  </action>
 </controller>
</root>

Written in the configuration file, is to facilitate management, if the program is also written, will overwrite the configuration file. OK, let's get down to business.

Second, the main interface

First look at the two main interfaces to use.

IPrincipal defines the basic functionality of a user object, and the interface is defined as follows:

Public interface IPrincipal
{
  //Identity Object
  iidentity identity {get;}
  Determines whether the current role belongs to the specified role
  bool IsInRole (string roles);
}

It has two primary members, IsInRole to determine whether the current object belongs to a specified role, IIdentity defines identity object information. The HttpContext user attribute is the IPrincipal type.

IIdentity defines the basic functionality for identifying objects, and interfaces are defined as follows:

Public interface IIdentity
{  
  //authentication type
  string AuthenticationType {get;}
  Whether to verify through
  bool isauthenticated {get;} 
  User name
  string name {get;}
}

IIdentity contains some user information, but sometimes we need to store more information, such as user IDs, user roles, and so on, which are encrypted and stored in a cookie, which can be decoded and deserialized when passed, and the state is saved. For example, define a userdata.

public class Userdata:iuserdata
{public
  long UserID {get; set;}
  public string UserName {get; set;}
  public string Userrole {get; set;}
 
  public bool IsInRole (string role)
  {
    if (string). IsNullOrEmpty
    {return
      true;
    }
    Return to role. Split (', '). Any (item => item. Equals (this. Userrole, StringComparison.OrdinalIgnoreCase));
 
  public bool Isinuser (string user)
  {
    if (string). IsNullOrEmpty (user))
    {return
      true;
    }
    return user. Split (', '). Any (item => item. Equals (this. UserName, StringComparison.OrdinalIgnoreCase));
  }

UserData implements the IUserData interface, which defines two methods: IsInRole and Isinuser, which are used to determine whether the current user role and user name meet the requirements. The interface is defined as follows:

Public interface IUserData
{
  bool IsInRole (string role);
  BOOL Isinuser (string user);
Next, define a Principal implementation IPrincipal interface, as follows: Public
class Principal:iprincipal    
{public
  iidentity identity{get; private set;}
  Public IUserData Userdata{get;set;}
 
  Public Principal (FormsAuthenticationTicket ticket, IUserData userData)
  {
    Ensurehelper.ensurenotnull ( Ticket, "ticket");
    Ensurehelper.ensurenotnull (UserData, "UserData");
    This. Identity = new FormsIdentity (ticket);
    This. UserData = UserData;
  }
 
  public bool IsInRole (string role)
  {return this
    . Userdata.isinrole (role);      
  }   
 
  public bool Isinuser (string user)
  {return this
    . Userdata.isinuser (user);
  }

Principal contains iuserdata rather than specific userdata, which makes it easy to replace a userdata without affecting other code. Principal's IsInRole and Isinuser indirectly invoke IUserData's method of the same name.

Third, write cookies and read cookies

Next, what needs to be done is to create UserData, serialize, use FormsAuthentication encryption, write to a cookie after the user logs on, and try to decrypt and deserialize the cookie when the request arrives. is as follows:

public class Httpformsauthentication {public static void Setauthenticationcookie (String userName, IUserData Userda
    TA, double rememberdays = 0) {ensurehelper.ensurenotnullorempty (UserName, "userName");
    Ensurehelper.ensurenotnull (UserData, "userData");
 
    Ensurehelper.ensurerange (Rememberdays, "Rememberdays", 0);
 
    Information stored in a cookie string Userjson = Jsonconvert.serializeobject (UserData); Create user Bill Double tickekdays = Rememberdays = = 0?
    7:rememberdays; var ticket = new FormsAuthenticationTicket (2, UserName, DateTime.Now, DateTime.Now.AddDays (Tickekdays), False, Userj
 
    son);
 
    FormsAuthentication provides the Web Forms authentication Service//encrypted string encryptvalue = Formsauthentication.encrypt (ticket);
    Create cookie HttpCookie cookie = new HttpCookie (Formsauthentication.formscookiename, Encryptvalue); Cookie.
    HttpOnly = true; Cookie.
 
    Domain = Formsauthentication.cookiedomain; if (Rememberdays > 0) {cookie. Expires = DateTime.Now.AddDays (rememberdays); } HttpContext.Current.Response.Cookies.Remove (Cookie.
    Name);
  HTTPCONTEXT.CURRENT.RESPONSE.COOKIES.ADD (cookie); public static Principal Tryparseprincipal<tuserdata> (HttpContext context) where Tuserdata:
 
    IUserData {ensurehelper.ensurenotnull (context, "context"); HttpRequest request = context.
    Request; HttpCookie cookies = Request.
    Cookies[formsauthentication.formscookiename]; if (cookie = = NULL | | string. IsNullOrEmpty (cookies.
    Value)) {return null; //Decrypt Cookie value FormsAuthenticationTicket ticket = Formsauthentication.decrypt (cookie).
    Value); if (ticket = NULL | | string. IsNullOrEmpty (ticket.            
    UserData)) {return null; } IUserData UserData = jsonconvert.deserializeobject<tuserdata> (ticket.       
    UserData);
  return new Principal (ticket, userData);
 }
}

When logging in, we can do something like this:

Public ActionResult Login (string username,string password)
{
  //verify user name and password some logic ... 
 
  UserData UserData = new UserData ()
  {
    UserName = UserName,
    UserID = UserID,
    userrole = "Admin"
  };
  Httpformsauthentication.setauthenticationcookie (UserName, UserData, 7);
   
  Verify pass ...
}

Once the login is successful, the information is written to the cookie, and the request can be viewed through the browser, and there will be a cookie called "Form" (which also requires a simple configuration profile), the value of which is an encrypted string, and subsequent requests are validated against this cookie request. The practice is to invoke the above Tryparseprincipal in the HttpApplication authenticaterequest validation event, such as:

protected void Application_AuthenticateRequest (object sender, EventArgs e)
{
  HttpContext.Current.User = Httpformsauthentication.tryparseprincipal<userdata> (httpcontext.current);
}

Here if validation does not pass, HttpContext.Current.User is NULL, indicating that the current user is not identified. But you can't do anything about permissions here, because there are pages that allow anonymous access.

Third, Authorizeattribute

This is a filter that executes before the action executes, and it implements the Iactionfilter interface. About the filter, you can see my previous article, here is not more introduction. We define a Requestauthorizeattribute inheritance authorizeattribute, and rewrite its Onauthorization method, if a controller or action marks the attribute, The method is then executed before the action is executed, where it is judged whether it is logged in and has permissions, and if not, do so accordingly. The specific code is as follows:

[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] public class Requestauthorizeattribute:authorizeattribute {//verify public override void Onau      
    Thorization (AuthorizationContext context) {Ensurehelper.ensurenotnull (context, "httpcontent"); Whether to allow anonymous access to the if (context).
    Actiondescriptor.isdefined (typeof (Allowanonymousattribute), false)) {return; }//Login authentication Principal Principal = context.
    HttpContext.User as Principal;
      if (principal = = null) {Setunauthorizedresult (context);
      Handleunauthorizedrequest (context);
    Return }//permission validation if (!principal. IsInRole (base. Roles) | | !principal. Isinuser (base.
      Users)) {Setunauthorizedresult (context);
      Handleunauthorizedrequest (context);
    Return //Verify the configuration file if (!
      Validateauthorizeconfig (principal, context)) {Setunauthorizedresult (context);
      Handleunauthorizedrequest (context);
    Return }//Priv When validation is not passedate void Setunauthorizedresult (AuthorizationContext context) {Httprequestbase request = context.
    HttpContext.Request; if (request).        
      Isajaxrequest ()) {//Processing ajax request String result = Jsonconvert.serializeobject (Jsonmodel.error (403)); Context.
    result = new Contentresult () {Content = result}; else {//Jump to login page string loginurl = Formsauthentication.loginurl + "?
      Returnurl= "+ Preurl; Context.
    result = new Redirectresult (loginurl); }//override protected override void Handleunauthorizedrequest (AuthorizationContext filtercontext) {if (fi
    Ltercontext.result!= null) {return; Base.
  Handleunauthorizedrequest (Filtercontext);
 }
}

Note: The code here is excerpted from a personal project, some code is abbreviated, some are helper classes, the code is not posted, but it should not affect reading.

1. If the IPrincipal we obtained in the HttpApplication AuthenticateRequest event is null, then validation does not pass.

2. If validation passes, the program validates the roles and user properties of the Authorizeattribute.

3. If validation passes, the program verifies the corresponding roles and users properties in the configuration file.

The method for verifying the configuration file is as follows:

  private bool Validateauthorizeconfig (Principal Principal, AuthorizationContext context) {//action may have overloads that should be marked with AC when overloaded Tionname distinguishes Actionnameattribute actionnameattr = context. Actiondescriptor. GetCustomAttributes (typeof (Actionnameattribute), false). Oftype<actionnameattribute> ().
    FirstOrDefault (); String actionname = Actionnameattr = null?
    Null:actionNameAttr.Name; Authorizationconfig ac = parseauthorizeconfig (actionname, context.
    Routedata); if (AC!= null) {if (!principal). IsInRole (AC.
      Roles)) {return false; } if (!principal. Isinuser (AC.
      Users)) {return false;
  } return true; Private Authorizationconfig Parseauthorizeconfig (string actionname, Routedata routedata) {string areaname = R
    outedata.datatokens["Area"] as String;
    string controllername = null;
    Object Controller, action; if (string. IsNullOrEmpty (ActionName)) {if (RouteData.Values.TryGetValuE ("Action", out action) {actionname = action.
      ToString (); } if (RouteData.Values.TryGetValue ("Controller", out Controller)) {controllername = controller.
    ToString (); } if (!string. IsNullOrEmpty (controllername) &&!string. IsNullOrEmpty (ActionName)) {return Authorizationconfig.parseauthorizationconfig (AreaName, ControllerNa
    Me, ActionName);
  return null;
 }
}

The

can see that it is validated by a Authorizationconfig class based on the area, controller, and action names of the current request, which is defined as follows:

public class Authorizationconfig {public string Roles {get; set;}
 
  public string Users {get; set;}
 
  private static XDocument _doc;
 
  Configuration file path private static string _path = "~/identity/authorization.xml";
    First Use load configuration file static Authorizationconfig () {String abspath = HttpContext.Current.Server.MapPath (_path);
    if (file.exists (Abspath)) {_doc = Xdocument.load (Abspath);  }//Parse configuration file for information that contains roles and users public static Authorizationconfig Parseauthorizationconfig (string areaname, String
    Controllername, String actionname) {Ensurehelper.ensurenotnullorempty (controllername, "controllername");
 
    Ensurehelper.ensurenotnullorempty (ActionName, "actionname");
    if (_doc = = null) {return null; } XElement rootelement = _doc.
    Element ("root");
    if (rootelement = = null) {return null;
    } authorizationconfig info = new Authorizationconfig ();
    XElement roleselement = null; XElement userselement = Null XElement areaelement = rootelement.elements ("area"). Where (e => comparename (E, areaname)).
    FirstOrDefault (); XElement targetelement = areaelement??
    RootElement; XElement controllerelement = targetelement.elements ("Controller"). Where (e => comparename (E, controllername)).
 
    FirstOrDefault (); Returns null if no area node and controller node (areaelement = = NULL && controllerelement = null) {return null
    ;
      ///At this time get the marked area if (Controllerelement = null) {rootelement = Areaelement.element ("roles");
    Userselement = Areaelement.element ("users"); else {XElement actionelement = controllerelement.elements ("action"). Where (e => comparename (E, ActionName)).
      FirstOrDefault ();
        if (actionelement!= null) {//the Roleselement = actionelement.element ("Roles") of the token action is obtained at this time;
      Userselement = Actionelement.element ("users"); else {//this time get tag contrOller roleselement = controllerelement.element ("roles");
      Userselement = Controllerelement.element ("users"); } info. Roles = Roleselement = = null?
    Null:rolesElement.Value; Info. Users = Userselement = null?
    Null:usersElement.Value;
  return info;
    private static bool Comparename (XElement E, String value) {XAttribute attribute = E.attribute ("name"); if (attribute = = NULL | | string. IsNullOrEmpty (attribute.
    Value)) {return false; } return attribute.
  Value.equals (value, stringcomparison.ordinalignorecase);
 }
}

The code here is relatively long, but the primary logic is to parse the configuration information at the beginning of the article.

Briefly summarize the steps to implement the program:

1. When the proofing user name and password are correct, call Setauthenticationcookie to write some state information to the cookie.

2. In the HttpApplication authentication event, call Tryparseprincipal to obtain state information.

3. Requestauthorizeattribute attributes are marked with the action (or controller) that need to be validated, and settings roles and Users;roles and users can also be configured in the configuration file.

4. Validation and permissions logic are handled in the Requestauthorizeattribute onauthorization method.

Iv. Summary

The above is the entire login authentication core implementation process, simply need to configure a little bit can be achieved. But the whole process from user registration to user management in real project is more complicated, and it involves the verification and decryption of the front and back. On the security issue, FormsAuthentication in the encryption, according to the server machinekey and other information to encrypt, so relatively safe. Of course, if a request is maliciously intercepted and then forged, it is possible that this is a later issue, such as using the secure HTTP protocol https.

The above is the entire content of this article, I hope to help you learn.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.