使用OAuth、Identity建立WebApi認證接口供客戶端調用

前言

      如今的web app基本上都是先後端分離,以前接觸的大部分應用場景最終產品都是部署在同一個站點下,那麼隨着WebApi(Restful api)的發展先後端實現的徹底分離,前端不在後端框架的頁面基礎上開發,也就告別傳統上的Session判斷客戶端登錄用戶的狀況。OAuth已發佈好久,Asp.Net  Identity也發佈好久。看了幾篇朋友寫的博客才把這幾個sample寫完,也解決了以前我對先後端徹底分離產生的一些疑惑。html

 

OAuth2.0的4種角色

  • resource owner資源全部者:好比twitter用戶,他在twitter的數據就是資源,他本身就是這些資源的全部者
  • resource server資源服務器:保存資源的服務器,別人要訪問受限制的資源就要出示 Access Token(訪問另牌)
  • client客戶端:一個通過受權後,能夠表明資源全部者訪問資源服務器上受限制資源的一方。好比 開發者開發的應用
  • authorization server受權服務器:對 資源全部者進行認證,認證經過後,向 客戶端發放 Access Token(訪問另牌

OAuth2.0取得Access Token的4種方式

  • 受權碼模式(authorization code)
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

使用Owin實現密碼模式(OAuth2.0密碼模式)

一、使用VS2015建立一個Empty WebApi項目。前端

二、使用Nuget導入核心命名空間。web

    Install-Package Microsoft.AspNet.WebApi.Owin
數據庫

    Install-Package Microsoft.Owin.Host.SystemWeb
三、添加Owin入口類
     Startup類上增長了OwinStartup屬性,表明這個類做爲Owin的入口。
[assembly:OwinStartup(typeof(AspNet_Identity_Demo.Startup))]
namespace AspNet_Identity_Demo
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration config = new HttpConfiguration();  
            WebApiConfig.Register(config);
            app.UseWebApi(config);
        }
    }
}

  

四、修改WebApiConfig。修改最後兩句代碼,主要以CamelCase命名法序列化webApi的返回結果。json

namespace AspNet_Identity_Demo
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服務

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            //用json的方式返回webapi接口返回值
            var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
            jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        }
    }
}

  

五、刪除Global.asax。添加Startup類後暫時用不到這個類。後端

六、添加Asp.Net Identity。先添加Identity類庫。api

    Install-Package Microsoft.AspNet.Identity.Owin
跨域

    Install-Package Microsoft.AspNet.Identity.EntityFramework
    第一個包提供Asp.Net Identity Owin支持,第二個則提供了基於EF SQL Server的Owin實現。這裏要提下Microsoft.AspNet.Identity.Core包,這裏主要是Asp.net Identity實現的相關接口,好比IUser、IRole、IPasswordHasher、IUserStore<TUser>、IUseRoleStore<TUser>、IRoleStore<TUser>、IClamisIdentityFactory<TUser>、UserManager<TUser>、IdentiyResult。 
   在第二個包中咱們會首先看到IdentityDbContext<TUser>、IdentityUser、IdentityRole、UserStore。你想基於本身的用戶系統擴展建立用戶類繼承IUser或者IdentityUser. 若是想換其餘數據庫則自定義DbContext。 
 
七、建立AuthContext。 
namespace AspNet_Identity_Demo.Models
{
    public class AuthContext:IdentityDbContext<IdentityUser>
    {
        public AuthContext() : base("AuthContext")
        { }
    }
}

 Web.config中增長connectionString服務器

<add name="AuthContext" connectionString="Data Source=.;User Id=sa;password=111111;Initial Catalog=AspNet_Identity;Integrated Security=SSPI;" providerName="System.Data.SqlClient" />

  

八、在Models文件夾中建立UserModel.csapp

public class UserModel
    {
        [Required]
        [Display(Name ="User Name")]
        public string UserName { get; set; }
        [Required]
        [DataType(DataType.Password)]
        [StringLength(100,ErrorMessage ="The {0} must be at least {2} characters long",MinimumLength =6)]
        public string Password { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [Compare("Password",ErrorMessage ="The password and confirmpassword are not matched...")]
        public string ConfirmPassword { get; set; }
    }

 

九、添加Asp.Net Identity 倉儲支持類。

    這裏用到了策略模式,把你實現的UserStore.cs做爲參數傳進UserManager構造函數中。

namespace AspNet_Identity_Demo.Models
{
    public class AuthRepository : IDisposable
    {
        private AuthContext _ctx;
        private UserManager<IdentityUser> _userManager;
        public AuthRepository()
        {
            _ctx = new AuthContext();
            _userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(_ctx));
        }


        public async Task<IdentityResult> Register(UserModel model)
        {
            IdentityUser user = new IdentityUser()
            {
                UserName = model.UserName
            };

            IdentityResult result = await _userManager.CreateAsync(user,model.Password);

            return result;
        }

        public async Task<IdentityUser> FindUser(UserModel model)
        {
            IdentityUser user = await _userManager.FindAsync(model.UserName, model.Password);

            return user;
        }

        public async Task<IdentityUser> FindUserByName(string username)
        {
            IdentityUser user = await _userManager.FindByNameAsync(username);
            return user;
        }


        public void Dispose()
        {
            _ctx.Dispose();
            _userManager.Dispose();
        }
    }
}

  

十、添加AccountController.cs

       給Controller添加webapi訪問前綴,個人是apix,訪問時也就是http://localhost:8083/apix/account/register。

namespace AspNet_Identity_Demo.Controllers
{
    [RoutePrefix("apix/Account")]
    public class AccountController : ApiController
    {
        private AuthRepository _authRepo;
        public AccountController()
        {
            _authRepo = new AuthRepository();
        }

        [AllowAnonymous]
        [Route("Register")]
        public async Task<IHttpActionResult> Register(UserModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            IdentityResult result = await _authRepo.Register(model);
            IHttpActionResult errorResult = GetError(result);
            if (errorResult != null)
            {
                return errorResult;
            }
            return Ok();
        }

        private IHttpActionResult GetError(IdentityResult result)
        {
            if (result == null)
            {
                return InternalServerError();
            }

            if (!result.Succeeded)
            {
                foreach (string err in result.Errors)
                {
                    ModelState.AddModelError("", err);
                }

                if (ModelState.IsValid)
                {
                    return BadRequest();
                }

                return BadRequest(ModelState);
            }

            return null;
        }
    }
}

   OK,到了這一步就能夠在你的視線之上註冊用戶了,使用Postman調用接口並調用接口http://localhost:8080/apix/account/register。post方式調用。參數傳UserName、Password。 調用成功返回接口返回200.打開你的SQL Server。調用成功的話數據庫用到的幾張表都會生成。用戶表是dbo.AspNetUsers.

 

十一、添加一個數據訪問controller,OrdersController。

namespace AspNet_Identity_Demo.Controllers
{
    [Authorize]
    [RoutePrefix("apix/orders")]
    public class OrdersController : ApiController
    {
        [Route]
        public IHttpActionResult Get()
        {
            return Ok(Order.CreateOrders());
        }
    }

    public class Order
    {
        public int OrderID { get; set; }
        public string CustomerName { get; set; }
        public string ShipperCity { get; set; }
        public Boolean IsShipped { get; set; }

        public static List<Order> CreateOrders()
        {
            List<Order> OrderList = new List<Order>
            {
                new Order {OrderID = 10248, CustomerName = "Taiseer Joudeh", ShipperCity = "Amman", IsShipped = true },
                new Order {OrderID = 10249, CustomerName = "Ahmad Hasan", ShipperCity = "Dubai", IsShipped = false},
                new Order {OrderID = 10250,CustomerName = "Tamer Yaser", ShipperCity = "Jeddah", IsShipped = false },
                new Order {OrderID = 10251,CustomerName = "Lina Majed", ShipperCity = "Abu Dhabi", IsShipped = false},
                new Order {OrderID = 10252,CustomerName = "Yasmeen Rami", ShipperCity = "Kuwait", IsShipped = true}
            };

            return OrderList;
        }
    }
}

 

十二、添加OAuth Bearer Token支持類庫 Install-Package Microsoft.Owin.Security.OAuth

1三、回到Startup。添加建立token方法,主要涉及到了兩個類SimpleAuthorizationServerProvider、OAuthAuthorizationServerOptions。

[assembly:OwinStartup(typeof(AspNet_Identity_Demo.Startup))]
namespace AspNet_Identity_Demo
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration config = new HttpConfiguration();
            ConfigAuth(app);
            WebApiConfig.Register(config);
            app.UseCors(CorsOptions.AllowAll);
            app.UseWebApi(config);
        }

        public void ConfigAuth(IAppBuilder app)
        {
            OAuthAuthorizationServerOptions option = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp=true,
                TokenEndpointPath=new PathString("/token"),
                AccessTokenExpireTimeSpan=TimeSpan.FromDays(1),
                Provider=new SimpleAuthorizationServerProvider()
            };

            app.UseOAuthAuthorizationServer(option);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        }
    }

    public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated();
        }
        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
            using (AuthRepository _repo = new AuthRepository())
            {
                IdentityUser user =await _repo.FindUser(
                    new UserModel() { UserName=context.UserName,Password=context.Password});
                if (user == null)
                {
                    context.SetError("invalid_grant", "The username or password is incorrect");
                    return;
                }
            }

            var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            identity.AddClaim(new Claim("sub", context.UserName));
            identity.AddClaim(new Claim("role", "user"));

            context.Validated(identity);
        }
    }
}

  訪問http://localhost:8083/token  http接口生成token。過時時間24小時。SimpleAuthorizationServerProvider 在該類中實現用戶驗證和口令生成。 注意這裏的ClamisIdentity。該類在命名空間:System.Security.Claims。 生成token主要是context.Validated(identity);這句代碼。

      OK,如今能夠註冊用戶,也能夠生成token了。那麼如今有個問題來了,先後端徹底分離後,那麼確定要實現跨域訪問(CORS)。因此你看到重寫GrantResourceOwnerCredentials第一句就是添加Access-Control-Allow-Origin支持。

1三、添加Asp.Net WebApi Install-Package Microsoft.Owin.Cors。在Startup.cs Configuration方法中添加app.UseCors(CorsOptions.AllowAll);

1四、生成客戶端token。

      

  

1五、拿到token後,訪問數據接口。注意參數Authorization值有前綴Bearer。

    

總結

      總的來講Owin和Identity的設計仍是有點複雜的,約定的東西多一些。相比微軟早起的Membership則要優雅不少,原理和實現背後的細節還要多多挖掘,才能體會到其中的魅力。好比ClamisIdentity、 UserManager、UserStore。

      Demo下載地址:https://yunpan.cn/c6yNPKhzpQgmx (提取碼:0575)

 

參考資料

http://www.cnblogs.com/richieyang/p/4918819.html

http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/

http://www.haomou.net/2014/08/13/2014_bare_token/

http://www.cnblogs.com/pengyingh/articles/2377968.html

http://www.cnblogs.com/keepfool/p/5665953.html

相關文章
相關標籤/搜索