前言
現在的web app基本上都是前后端分離,之前接觸的大部分應用場景最終產品都是部署在同一個站點下,那么隨着WebApi(Restful api)的發展前后端實現的完全分離,前端不在后端框架的頁面基礎上開發,也就告別傳統上的Session判斷客戶端登陸用戶的情況。OAuth已發布很久,Asp.Net Identity也發布很久。看了幾篇朋友寫的博客才把這幾個sample寫完,也解決了之前我對前后端完全分離產生的一些疑惑。
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密碼模式)
1、使用VS2015創建一個Empty WebApi項目。
2、使用Nuget導入核心命名空間。
Install-Package Microsoft.AspNet.WebApi.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);
}
}
}
4、修改WebApiConfig。修改最后兩句代碼,主要以CamelCase命名法序列化webApi的返回結果。
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();
}
}
}
5、刪除Global.asax。添加Startup類后暫時用不到這個類。
6、添加Asp.Net Identity。先添加Identity類庫。
Install-Package Microsoft.AspNet.Identity.Owin
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" />
8、在Models文件夾中創建UserModel.cs
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; }
}
9、添加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();
}
}
}
10、添加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.

11、添加一個數據訪問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;
}
}
}
12、添加OAuth Bearer Token支持類庫 Install-Package Microsoft.Owin.Security.OAuth
13、回到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支持。
13、添加Asp.Net WebApi Install-Package Microsoft.Owin.Cors。在Startup.cs Configuration方法中添加app.UseCors(CorsOptions.AllowAll);
14、生成客戶端token。

15、拿到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/
