續上篇 https://dotblogs.com.tw/yc421206/2016/08/02/asp_net_identity_basic 了解 ASP.NET Identity 的 UserManager 的運作方式後,這裡就要把 UserManager 丟到 OWIN 執行
開發環境
- Windows 10 Enterprise x64 CHT
- VS2015 Update3 ENG
本文連結
- #Step1.從 Nuget 安裝套件
- #Step2.加入空的 Web API2 Controller
- #Step3.在 AccountApiController 加入 Register 方法
- #Step4.加入 AuthorizationServerProvider
- #Step5.加入 Startup
- #Step6.使用 Fiddler 測試
Step1.從 Nuget 安裝套件
在 Simple.OAuthServer 專案安裝以下套件
Install-Package Microsoft.Owin.Host.SystemWeb
Dependencies
- Owin (>= 1.0.0)
- Microsoft.Owin (>= 3.0.1)
Install-Package Microsoft.AspNet.Identity.Owin
Dependencies
- Microsoft.AspNet.Identity.Core (>= 2.2.1)
- Microsoft.Owin.Security (>= 2.1.0)
- Microsoft.Owin.Security.Cookies (>= 2.1.0)
- Microsoft.Owin.Security.OAuth (>= 2.1.0)
Install-Package Microsoft.Owin.Security.OAuth
Dependencies
- Owin (>= 1.0.0)
- Microsoft.Owin (>= 3.0.1)
- Newtonsoft.Json (>= 6.0.4)
- Microsoft.Owin.Security (>= 3.0.1)
Install-Package Microsoft.AspNet.WebApi.Owin
Dependencies
- Microsoft.AspNet.WebApi.Core (>= 5.2.3 && < 5.3.0)
- Microsoft.Owin (>= 2.0.2)
- Owin (>= 1.0.0)
Step2.加入空的 Web API2 Controller
完成後,會新增 WebApiConfig.cs,必須要在 Global.asax 的 Application_Start 方法調用 GlobalConfiguration.Configure(WebApiConfig.Register);
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
Step3.在 AccountApiController 加入 Register 方法
ModelState.IsValid 用來檢查 Model 欄位的 Attribute
[RoutePrefix("api/account")]
public class AccountApiController : ApiController
{
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get { return _userManager ?? Request.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
private set { _userManager = value; }
}
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationIdentityUser
{
UserName = model.Email,
Email = model.Email
};
var result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
private IHttpActionResult GetErrorResult(IdentityResult result)
{
if (result == null)
{
return InternalServerError();
}
if (!result.Succeeded)
{
if (result.Errors != null)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
if (ModelState.IsValid)
{
// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();
}
return BadRequest(ModelState);
}
return null;
}
}
RegisterBindingModel
欄位上的 Atturibute 會被檢查(ModelState.IsValid)
public class RegisterBindingModel
{
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
Step4.加入 AuthorizationServerProvider
GrantResourceOwnerCredentials 方法:用來取得資源,比如 Token Access
ValidateClientAuthentication 方法:通常會先頒發 client_id 和 client_secret 給 Client端,然後再用這個方法判斷 Client,這裡就不處理
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationIdentityUser user;
try
{
user = await userManager.FindAsync(context.UserName, context.Password);
}
catch
{
context.SetError("server_error");
context.Rejected();
return;
}
if (user != null)
{
var identity = await userManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ExternalBearer);
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "Invalid User Id or password'");
context.Rejected();
}
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
}
Step5.加入 Startup
ConfigureOAuth 方法:定義 OAuth Server 組態
CreateUserManager 方法:定義 UserManager 的密碼、帳號檢查規則
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
}
private void ConfigureOAuth(IAppBuilder app)
{
app.CreatePerOwinContext(() => new ApplicationDbContext());
app.CreatePerOwinContext<ApplicationUserManager>(CreateUserManager);
//app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
var oauthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/oauth/token"),
Provider = new AuthorizationServerProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
AllowInsecureHttp = true
};
app.UseOAuthAuthorizationServer(oauthOptions);
}
private static ApplicationUserManager CreateUserManager(IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
var dbContext = context.Get<ApplicationDbContext>();
var userStore = new ApplicationUserStore(dbContext);
var userManager = new ApplicationUserManager(userStore);
userManager.UserValidator = new UserValidator<ApplicationIdentityUser>(userManager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
userManager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true
};
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
userManager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationIdentityUser>(
dataProtectionProvider.Create("ASP.NET Identity"));
}
return userManager;
}
}
Step6.使用 Fiddler 測試
建立帳號
http://localhost:14695/Api/Account/Register
Header:
Content-Type: application/json; charset=utf8
Request Body:
{
"Email": "test@aa.bb",
"Password": "Pass@w0rd1",
"ConfirmPassword": "Pass@w0rd1"
}
操作步驟如下圖:
取得 Access Token
http://localhost:14695/oauth/token
Request Body:
grant_type=password&username=test@aa.bb&password=Pass@w0rd1
操作步驟如下圖:
專案位置:
https://dotblogsamples.codeplex.com/SourceControl/latest#Simple.OAuthServer/
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET