A simple Token verification of Asp.Net WebApi

1. Preface:

WebAPI mainly opens data to mobile APP, Pad, other systems or software applications that need to know data. Authentication of Web users and authentication of page operation authority are the basic functions of B/S system. Last time I wrote< Creation of Asp.Net MVC WebAPI and details of Jquery ajax in the foreground and HttpClient in the background >This is not as good as the obvious security, so here comes the API that users need to access. In other words, the Token that contains user information after login. Start rolling

2. Create a new WebApi project, and create a BaseApiController under the app start folder. This is the basic Api controller, and the interfaces to be verified will inherit this controller:

using LoginReqToken.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;

namespace LoginReqToken.App_Start
{
    /// <summary>
    /// Basics Api The controller inherits everything
    /// </summary>
    public class BaseApiController : ApiController
    {
       
       /// <summary>
       /// Constructor assignment
       /// </summary>
        public BaseApiController()
        {
            TokenValue = HttpContext.Current.Session[LoginID] ?? "";
            HttpContext.Current.Request.Headers.Add("TokenValue", TokenValue.ToString());
        }
        /// <summary>
        /// Database context
        /// </summary>
        public WYDBContext db = WYDBContextFactory.GetDbContext();
        /// <summary>
        /// token Value post login assignment request api Add to header in
        /// </summary>
        public static object TokenValue { get; set; } = "";
        /// <summary>
        /// Login account
        /// </summary>
        public static string LoginID { get; set; } = "";
    }
}

In this constructor, a header header is added actively, because the constructor is executed every time you access it, and you need to take it out of the header when you verify there, and calculate whether the user name is consistent with the Session cache

3. A TokenCheckFilter.cs under construction inherits the authizeattribute and rewrites the verification method of the base class. It rewrites HandleUnauthorizedRequest

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web;
using System.Web.Helpers;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Security;
namespace LoginReqToken.App_Start
{
    /// <summary>
    /// token verification
    /// </summary>
    public class TokenCheckFilter: AuthorizeAttribute
    {

        /// <summary>
        /// Override the validation method of the base class and add the custom Ticket verification
        /// </summary>
        /// <param name="actionContext"></param>
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            var content = actionContext.Request.Properties["MS_HttpContext"] as HttpContextBase;
            //obtain token(Value in request header)
            var token = HttpContext.Current.Request.Headers["TokenValue"] ?? "";
            //Empty or not
            if (!string.IsNullOrEmpty(token.ToString()))
            {
                //Decrypt user ticket,And verify whether the user name and password match
                if (ValidateTicket(token.ToString()))
                    base.IsAuthorized(actionContext);
                else
                    HandleUnauthorizedRequest(actionContext);
            }
            //If the authentication information is not available and anonymous access is not allowed, an unauthenticated 403 is returned
            else
            {
                var attributes = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().OfType<AllowAnonymousAttribute>();
                bool isAnonymous = attributes.Any(a => a is AllowAnonymousAttribute);
                if (isAnonymous) base.OnAuthorization(actionContext);
                else HandleUnauthorizedRequest(actionContext);
            }
        }

        //Verify user name and password (yes Session Match, or database data match)
        private bool ValidateTicket(string encryptToken)
        {
            //decrypt Ticket
            var strTicket = FormsAuthentication.Decrypt(encryptToken).UserData;
            //from Ticket Get user name and password
            var index = strTicket.IndexOf("&");
            string userName = strTicket.Substring(0, index);
            string password = strTicket.Substring(index + 1);
            //obtain session,Exit without specifying the user, or session Expired
            var token = HttpContext.Current.Session[userName];
            if (token == null)
                return false;
            //Contrast session Token in
            if (token.ToString() == encryptToken)
                return true;
            return false;
        }
        /// <summary>
        /// rewrite HandleUnauthorizedRequest
        /// </summary>
        /// <param name="filterContext"></param>
        protected override void HandleUnauthorizedRequest(HttpActionContext filterContext)
        {
            base.HandleUnauthorizedRequest(filterContext);

            var response = filterContext.Response = filterContext.Response ?? new HttpResponseMessage();
            //The status code 401 is changed to another status code to avoid redirection. The most reasonable is to change it to 403, indicating that the server refuses.
            response.StatusCode = HttpStatusCode.Forbidden;
            var content = new 
            {
                success = false,
                errs = new[] { "Server access denied: you do not have permission?,Or lost the line?" }
            };
            response.Content = new StringContent(Json.Encode(content), Encoding.UTF8, "application/json");
        }

    }
}

4. In the WebApiConfig.cs configuration file, modify the route and add / {action}, so that you can call the specific one

 

 

 

Webapi does not support Session by default, so we need to add support for Session during Global loading. In Global.asax, we need to rewrite application ﹣ postauthorizerequest, otherwise running the call will directly cause an exception

    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
        /// <summary>
        /// rewrite Application_PostAuthorizeRequest
        /// </summary>
        protected void Application_PostAuthorizeRequest()
        {
            //yes Session Otherwise, running the call will directly cause an exception
            HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
        }
    }

 

 

5. Now write a Login, create a new controller LoginController, inherit BaseApiController, write a Login method, Login login Login page, directly write a simple one in Home index, the access of this controller will not be limited, and add the annotation AllowAnonymous

[AllowAnonymous]
    public class LoginController : BaseApiController
    {
        [HttpGet]
        public object Login(string uName, string uPassword)
        {
            var user = db.Users.Where(x => x.LoginID == uName && x.Password == uPassword).FirstOrDefault();
            if (user==null)
            {
                return Json(new { ret = 0, data = "", msg = "Wrong user name and password" });
            }
            FormsAuthenticationTicket token = new FormsAuthenticationTicket(0, uName, DateTime.Now, DateTime.Now.AddHours(12), true, $"{uName}&{uPassword}", FormsAuthentication.FormsCookiePath);
            //Return login result, user information and user verification bill information
            var _token = FormsAuthentication.Encrypt(token);
            //Save identity information in session Verify that the current request is a valid request
            LoginID = uName;
            TokenValue = _token;
            HttpContext.Current.Session[LoginID] = _token;
            return Json(new { ret = 1, data = _token, msg = "Login succeeded!" });
        }
    }

 

Landing page is simple and rough

 

<br /><br />
<input type="text" name="txtLoginID" id="txtLoginID"  />
<br /><br />
<input type="password" name="txtPassword" id="txtPassword"  />
<br /><br />
<input type="button" id="btnSave" value="validate logon" />
<script type="text/javascript" src="~/Scripts/jquery-3.3.1.js"></script>
<script type="text/javascript">
    $(document).ready(function () {
        $("#btnSave").click(function () {
            $.ajax({
                type: "GET",
                url: "/Api/Login/Login",
                dataType: "json",
                data: { "uName": $("#txtLoginID").val(), "uPassword": $("#txtPassword").val()},
                success: function (data) {
                    if (data.ret > 0) {
                        alert(data.msg+"Token:  "+data.data);
                    }
                    else {
                        alert(data.msg);
                    }
                },
                error: function (ret) {
                    console.log(ret);
                }
            });
        });
    });
</script>

 

 

 

Log in. I wrote my own practice of linking the database. It's the easiest to change a fixed value. Now I can see the Token data returned

 

 

 

6. Now you can write Api. All methods of inheriting BaseApiController need to be verified and annotated. I want the whole control to be written directly on the class. Just write an example, such as the query of provinces, cities and counties in China

using LoginReqToken.App_Start;
using LoginReqToken.Models;
using LoginReqToken.Models.DTO;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace LoginReqToken.Controllers
{

    /// <summary>
    /// Area query
    /// </summary>
    [TokenCheckFilter]
    public class AreaOpController : BaseApiController
    {
        /// <summary>
        /// Get all areas
        /// </summary>
        /// <returns></returns>
        public Result GetAllAreas()
        {
            var data = db.AddressAll.OrderBy(x => x.ID);
            if(data.Count()>0)
            {
                var ret = new Result()
                {
                    Ret = 1,
                    Code = "200",
                    Msg = "Data acquisition successful",
                    Data = JsonConvert.SerializeObject(data)

                };
                return ret;
            }
            else
            {
                var ret = new Result()
                {
                    Ret = 0,
                    Code = "400",
                    Msg = "Interface failure exception",
                    Data = ""

                };
                return ret;
            }
        }
        /// <summary>
        /// Query all information under a municipality
        /// </summary>
        /// <param name="name">Province name (full name)</param>
        /// <returns></returns>
        public Result GetProvinceByName(string name)
        {
            var codeID = db.AddressAll.FirstOrDefault(x => x.Name == name)?.ID;
            if(codeID<=0)
            {
                var ret = new Result()
                {
                    Ret = 1,
                    Code = "F",
                    Msg = "No relevant data found",
                    Data = ""

                };
                return ret;
            }
            var bb = db.AddressAll.Where(x=>x.ID>0).AsEnumerable();
            var data = GetProvinceCity(bb,codeID).ToList();
            if (data.Count() > 0)
            {
                var ret = new Result()
                {
                    Ret = 1,
                    Code = "200",
                    Msg = "Data acquisition successful",
                    Data = JsonConvert.SerializeObject(data)

                };
                return ret;
            }
            else
            {
                var ret = new Result()
                {
                    Ret = 0,
                    Code = "500",
                    Msg = "No data to query or interface call error",
                    Data = ""

                };
                return ret;
            }

        }
        /// <summary>
        /// Get tree data recursively
        /// </summary>
        /// <param name="areasDTOs"></param>
        /// <param name="parentID"></param>
        /// <returns></returns>
        public IEnumerable<object> GetProvinceCity(IEnumerable<AddressAll> areasDTOs,int? parentID)
        {
            var data = areasDTOs as AddressAll[] ?? areasDTOs.ToArray();
            var ret = data.Where(n => n.ParentID == parentID).Select(n => new
            {
                n.ID,
                n.Code,
                n.ParentID,
                n.Name,
                n.LevelNum,
                n.OrderNum,
                children = GetProvinceCity(data, n.ID)
            });
            return ret;
        }
    }
}

 

To record the information about the number of entries in an EF database, VAR data = dB. Cnblogslist. Orderby (P = > guid. Newguid()). Take (100);

Now look at the renderings

 

 

You can't get in when you don't log in. Look at the effect on postman

 

 

The effect is the same. If you log in, you can directly access it without adding parameters. Only if the method needs parameters, you can add parameters

 

 

 

 

 

Paste a calling code here:

 HttpClient bb = new HttpClient();
            //Get port
            HttpContent httpContent = new StringContent("");
            httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
            
             var dl = bb.GetAsync("http://localhost:63828/api/Login/login?uName=admin&uPassword=admin888").Result.Content.ReadAsStringAsync().Result;
            var token = JsonConvert.DeserializeObject<Result>(dl);
           
            for (var i=0;i<100;i++)
            {
                
                var ret = bb.GetAsync("http://localhost:63828/api/Cnblog/GetAllArtic").Result.Content.ReadAsStringAsync().Result;
            }

 

7. Summary:

1) general idea: if it is a legal Http request, there will be bill information of user identity in the Http request header. The server will read the bill information and verify whether the bill information is complete and valid. If it meets the verification requirements, the business data will be processed and returned to the request initiator;

2) if there is no bill information or the bill information is not legal, the "unauthorized access" exception message will be returned to the front end, which will handle the exception.

3) when logging in, judge whether the user name and password are correct. If they are correct, a Token of user information will be generated. The Session saves a Token, and the login name and Token in BaseApiController are also assigned. Save the ticket information.

4) when the user has permission to operate the page or page element, jump to the page, and the page Controller submits the business data processing request to the api server; if the user does not have permission to access the page or page element, the "unauthorized access operation" will be displayed and jump to the system exception processing page.

5) the api business service processes the business logic, returns the result with Json data, returns the rendered page to the browser front end, and presents the business data to the page;

8. Test address:

http://www.yijianlan.com:8001/ Log in first, the user name test password 123456 can call the debugging interface and then visit it. Other js calls, I haven't tried other platforms, and I don't know the problem. Welcome to give me some advice!

http://www.yijianlan.com:8001/api/AreaOp/GetProvinceByName?name= Full name of province view all subsets of a province

http://www.yijianlan.com:8001/api/AreaOp/GetAllAreas Obtain all regions (the first province, city and county in China)

http://www.yijianlan.com:8001/api/Cnblog/GetAllArtic Get blog Park data (it's been two years since crawler catches it), random 100

Http://www.yijianlan.com: 8001 / API / cnblog / getarticlebyname? Name = title --------------------->Query data by title

Tags: ASP.NET Session JSON Database JQuery

Posted on Tue, 17 Dec 2019 04:48:26 -0500 by BSkopnik