<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【WEB API項目實戰干貨系列】- API登錄與身份驗證(三)

      上一篇: 【WEB API項目實戰干貨系列】- 接口文檔與在線測試(二)

      這篇我們主要來介紹我們如何在API項目中完成API的登錄及身份認證. 所以這篇會分為兩部分, 登錄API, API身份驗證.

      這一篇的主要原理是: API會提供一個單獨的登錄API, 通過用戶名,密碼來產生一個SessionKey, SessionKey具有過期時間的特點, 系統會記錄這個SessionKey, 在后續的每次的API返回的時候,客戶端需帶上這個Sessionkey, API端會驗證這個SessionKey.

      登錄API

      我們先來看一下登錄API的方法簽名

      image

       

      SessionObject是登錄之后,給客戶端傳回的對象, 里面包含了SessionKey及當前登錄的用戶的信息

      image

      這里每次的API調用,都需要傳SessionKey過去, SessionKey代表了用戶的身份信息,及登錄過期信息。

       

      登錄階段生成的SessionKey我們需要做保存,存儲到一個叫做UserDevice的對象里面, 從語意上可以知道用戶通過不同的設備登錄會產生不同的UserDevice對象.

      image

       

      最終的登錄代碼如下:

      [RoutePrefix("api/accounts")]
          public class AccountController : ApiController
          {
              private readonly IAuthenticationService _authenticationService = null;
      
              public AccountController()
              {
                  //this._authenticationService = IocManager.Intance.Reslove<IAuthenticationService>();
              }
      
              [HttpGet]
              public void AccountsAPI()
              {
      
              }
      
              /// <summary>
              /// 登錄API
              /// </summary>
              /// <param name="loginIdorEmail">登錄帳號(郵箱或者其他LoginID)</param>
              /// <param name="hashedPassword">加密后的密碼,這里避免明文,客戶端加密后傳到API端</param>
              /// <param name="deviceType">客戶端的設備類型</param>
              /// <param name="clientId">客戶端識別號, 一般在APP上會有一個客戶端識別號</param>
              /// <remarks>其他的登錄位置啥的,是要客戶端能傳的東西,都可以在這里擴展進來</remarks>
              /// <returns></returns>
              [Route("account/login")]
              public SessionObject Login(string loginIdorEmail, string hashedPassword, int deviceType = 0, string clientId = "")
              {
                  if (string.IsNullOrEmpty(loginIdorEmail))
                      throw new ApiException("username can't be empty.", "RequireParameter_username");
                  if (string.IsNullOrEmpty(hashedPassword))
                      throw new ApiException("hashedPassword can't be empty.", "RequireParameter_hashedPassword");
      
                  int timeout = 60;
      
                  var nowUser = _authenticationService.GetUserByLoginId(loginIdorEmail);
                  if (nowUser == null)
                      throw new ApiException("Account Not Exists", "Account_NotExits");
      
                  #region Verify Password
                  if (!string.Equals(nowUser.Password, hashedPassword))
                  {
                      throw new ApiException("Wrong Password", "Account_WrongPassword");
                  }
                  #endregion
      
                  if (!nowUser.IsActive)
                      throw new ApiException("The user is inactive.", "InactiveUser");
      
                  UserDevice existsDevice = _authenticationService.GetUserDevice(nowUser.UserId, deviceType);// Session.QueryOver<UserDevice>().Where(x => x.AccountId == nowAccount.Id && x.DeviceType == deviceType).SingleOrDefault();
                  if (existsDevice == null)
                  {
                      string passkey = MD5CryptoProvider.GetMD5Hash(nowUser.UserId + nowUser.LoginName + DateTime.UtcNow.ToString() + Guid.NewGuid().ToString());
                      existsDevice = new UserDevice()
                      {
                          UserId = nowUser.UserId,
                          CreateTime = DateTime.UtcNow,
                          ActiveTime = DateTime.UtcNow,
                          ExpiredTime = DateTime.UtcNow.AddMinutes(timeout),
                          DeviceType = deviceType,
                          SessionKey = passkey
                      };
      
                      _authenticationService.AddUserDevice(existsDevice);
                  }
                  else
                  {
                      existsDevice.ActiveTime = DateTime.UtcNow;
                      existsDevice.ExpiredTime = DateTime.UtcNow.AddMinutes(timeout);
                      _authenticationService.UpdateUserDevice(existsDevice);
                  }
                  nowUser.Password = "";
                  return new SessionObject() { SessionKey = existsDevice.SessionKey, LogonUser = nowUser };
              }
          }

       

      API身份驗證

      身份信息的認證是通過Web API 的 ActionFilter來實現的, 每各需要身份驗證的API請求都會要求客戶端傳一個SessionKey在URL里面丟過來。

      在這里我們通過一個自定義的SessionValidateAttribute來做客戶端的身份驗證, 其繼承自 System.Web.Http.Filters.ActionFilterAttribute, 把這個Attribute加在每個需要做身份驗證的ApiControler上面,這樣該 Controller下面的所有Action都將擁有身份驗證的功能, 這里會存在如果有少量的API不需要身份驗證,那該如何處理,這個會做一些排除,為了保持文章的思路清晰,這會在后續的章節再說明.

      public class SessionValidateAttribute : System.Web.Http.Filters.ActionFilterAttribute
          {
              public const string SessionKeyName = "SessionKey";
              public const string LogonUserName = "LogonUser";
      
              public override void OnActionExecuting(HttpActionContext filterContext)
              {
                  var qs = HttpUtility.ParseQueryString(filterContext.Request.RequestUri.Query);
                  string sessionKey = qs[SessionKeyName];
      
                  if (string.IsNullOrEmpty(sessionKey))
                  {
                      throw new ApiException("Invalid Session.", "InvalidSession");
                  }
      
                  IAuthenticationService authenticationService = IocManager.Intance.Reslove<IAuthenticationService>();
      
                  //validate user session
                  var userSession = authenticationService.GetUserDevice(sessionKey);
      
                  if (userSession == null)
                  {
                      throw new ApiException("sessionKey not found", "RequireParameter_sessionKey");
                  }
                  else
                  {
                      //todo: 加Session是否過期的判斷
                      if (userSession.ExpiredTime < DateTime.UtcNow)
                          throw new ApiException("session expired", "SessionTimeOut");
      
                      var logonUser = authenticationService.GetUser(userSession.UserId);
                      if (logonUser == null)
                      {
                          throw new ApiException("User not found", "Invalid_User");
                      }
                      else
                      {
                          filterContext.ControllerContext.RouteData.Values[LogonUserName] = logonUser;
                          SetPrincipal(new UserPrincipal<int>(logonUser));
                      }
      
                      userSession.ActiveTime = DateTime.UtcNow;
                      userSession.ExpiredTime = DateTime.UtcNow.AddMinutes(60);
                      authenticationService.UpdateUserDevice(userSession);
                  }
              }
      
              private void SetPrincipal(IPrincipal principal)
              {
                  Thread.CurrentPrincipal = principal;
                  if (HttpContext.Current != null)
                  {
                      HttpContext.Current.User = principal;
                  }
              }
          }

       

      OnActionExcuting方法:

      這個是在進入某個Action之前做檢查, 這個時候我們剛好可以同RequestQueryString中拿出SessionKey到UserDevice表中去做查詢,來驗證Sessionkey的真偽, 以達到身份驗證的目的。

       

      用戶的過期時間:

      在每個API訪問的時候,會自動更新Session(也就是UserDevice)的過期時間, 以保證SessionKey不會過期,如果長時間未更新,則下次訪問會過期,需要重新登錄做處理。

       

      Request.IsAuthented:

      上面代碼的最后一段SetPrincipal就是來設置我們線程上下文及HttpContext上下文中的用戶身份信息, 在這里我們實現了我們自己的用戶身份類型

      public class UserIdentity<TKey> : IIdentity
          {
              public UserIdentity(IUser<TKey> user)
              {
                  if (user != null)
                  {
                      IsAuthenticated = true;
                      UserId = user.UserId;
                      Name = user.LoginName.ToString();
                      DisplayName = user.DisplayName;
                  }
              }
      
              public string AuthenticationType
              {
                  get { return "CustomAuthentication"; }
              }
      
              public TKey UserId { get; private set; }
      
              public bool IsAuthenticated { get; private set; }
      
              public string Name { get; private set; }
      
              public string DisplayName { get; private set; }
          }
      
          public class UserPrincipal<TKey> : IPrincipal
          {
              public UserPrincipal(UserIdentity<TKey> identity)
              {
                  Identity = identity;
              }
      
              public UserPrincipal(IUser<TKey> user)
                  : this(new UserIdentity<TKey>(user))
              {
      
              }
      
              /// <summary>
              /// 
              /// </summary>
              public UserIdentity<TKey> Identity { get; private set; }
      
              IIdentity IPrincipal.Identity
              {
                  get { return Identity; }
              }
      
      
              bool IPrincipal.IsInRole(string role)
              {
                  throw new NotImplementedException();
              }
          }
      
          public interface IUser<T>
          {
              T UserId { get; set; }
              string LoginName { get; set; }
              string DisplayName { get; set; }
          }

      這樣可以保證我們在系統的任何地方,通過HttpContext.User 或者 System.Threading.Thread.CurrentPrincipal可以拿到當前線程上下文的用戶信息, 方便各處使用

       

      加入身份認證之后的Product相關API如下:

      [RoutePrefix("api/products"), SessionValidate]
          public class ProductController : ApiController
          {
              [HttpGet]
              public void ProductsAPI()
              { }
      
              /// <summary>
              /// 產品分頁數據獲取
              /// </summary>
              /// <returns></returns>
              [HttpGet, Route("product/getList")]
              public Page<Product> GetProductList(string sessionKey)
              {
                  return new Page<Product>();
              }
      
              /// <summary>
              /// 獲取單個產品
              /// </summary>
              /// <param name="productId"></param>
              /// <returns></returns>
              [HttpGet, Route("product/get")]
              public Product GetProduct(string sessionKey, Guid productId)
              {
                  return new Product() { ProductId = productId };
              }
      
              /// <summary>
              /// 添加產品
              /// </summary>
              /// <param name="product"></param>
              /// <returns></returns>
              [HttpPost, Route("product/add")]
              public Guid AddProduct(string sessionKey, Product product)
              {
                  return Guid.NewGuid();
              }
      
              /// <summary>
              /// 更新產品
              /// </summary>
              /// <param name="productId"></param>
              /// <param name="product"></param>
              [HttpPost, Route("product/update")]
              public void UpdateProduct(string sessionKey, Guid productId, Product product)
              {
      
              }
      
              /// <summary>
              /// 刪除產品
              /// </summary>
              /// <param name="productId"></param>
              [HttpDelete, Route("product/delete")]
              public void DeleteProduct(string sessionKey, Guid productId)
              {
      
              }

       

      可以看到我們的ProductController上面加了SessionValidateAttribute, 每個Action參數的第一個位置,加了一個string sessionKey的占位, 這個主要是為了讓Swagger.Net能在UI上生成測試窗口

      image

      這篇并沒有使用OAuth等授權機制,只是簡單的實現了登錄授權,這種方式適合小項目使用.

      這里也只是實現了系統的登錄,API訪問安全,并不能保證 API系統的絕對安全,我們可以透過 路由的上的HTTP消息攔截, 攔截到我們的API請求,截獲密碼等登錄信息, 因此我們還需要給我們的API增加SSL證書,實現 HTTPS加密傳輸。

      另外在前幾天的有看到結合客戶端IP地址等后混合生成 Sessionkey來做安全的,但是也具有一定的局限性, 那種方案合適,還是要根據自己的實際項目情況來確定.

       

      由于時間原因, 本篇只是從原理方面介紹了API用戶登錄與訪問身份認證,因為這部分真實的測試設計到數據庫交互, Ioc等基礎設施的支撐,所以這篇的代碼只能出現在SwaggerUI中,但是無法實際測試接口。在接下來的代碼中我會完善這部分.

      代碼: 代碼下載(代碼托管在CSDN Code)

      posted @ 2015-10-13 16:37  DukeCheng  閱讀(38679)  評論(48)    收藏  舉報
      主站蜘蛛池模板: 午夜在线欧美蜜桃| 国产亚洲天堂另类综合| 亚洲精品国产福利一区二区| 精品 无码 国产观看| 久久国产精品波多野结衣av| 国产午夜亚洲精品国产成人| 国产成熟女人性满足视频| 在线观看中文字幕码国产| 日韩人妻少妇一区二区三区| 无码人妻丰满熟妇啪啪网不卡| 午夜福利精品国产二区| 人妻激情另类乱人伦人妻| 国产首页一区二区不卡| 高中女无套中出17p| 国产真实younv在线| 人妻出轨av中文字幕| 中文字幕日韩精品国产| 欧美性猛交xxxx富婆| 久久久久久久久久久久中文字幕| 靖州| 久久精品蜜芽亚洲国产av| 亚洲激情av一区二区三区| 亚洲男人电影天堂无码| 91福利一区福利二区| 肥大bbwbbw高潮抽搐| 激烈的性高湖波多野结衣| 欧美大bbbb流白水| 亚洲天堂男人影院| 久久热这里只有精品国产| 国产精品中文字幕一区| 亚洲天堂av 在线| 射洪县| 麻豆国产成人AV在线播放| 欧美人人妻人人澡人人尤物| 国产高颜值不卡一区二区| 国产99青青成人A在线| 国产美女午夜福利视频| 普陀区| 国产a在视频线精品视频下载 | 99精品国产成人一区二区| 久久中文字幕一区二区|