[web Api]實做token without identity
前言
以前我們在用不管是web form或mvc的時候,我們大部份會使用session的方式來實做登入,但是到了web api之後,則建議使用標準的token方式來做身份驗證,而前陣子跟朋友聊天,發覺很多人在用web api的認證時,並不是用token的方式,總認為實做token很麻煩,之前有寫一些是使用identity和token的方式,但我相信大部份實際狀況,很多公司或個人都是使用自己的會員機制,所以想記錄一下自行實做token的方式,以下範例只是簡單範例,但請focus在實做token的部份。
必裝元件
先把owin相關的元件裝一裝,如圖示
會員建立
在Model的資料夾建立兩支類別,一個是使用者另一個是角色
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public Guid Salt { get; set; }
public int RoleId { get; set; }
public virtual Role Role { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string Name { get; set; }
public ICollection<User> User { get; set; }
}
接著建立TokenDbContext.cs,我是用code first的方式建立db
public partial class TokenDbContext : DbContext
{
public TokenDbContext()
: base("name=TokenDbConnection")
{
}
public virtual DbSet<User> User { get; set; }
public virtual DbSet<Role> Role { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
密碼加密
建立一支PasswordHelper.cs,程式碼如下,此程式碼只是為了要加密和解密
{
public class PasswordHelper
{
public static string SaltPassword(string password, Guid salt)
{
byte[] passwordAndSaltBytes = System.Text.Encoding.UTF8.GetBytes(password + salt.ToString());
byte[] hashBytes = new System.Security.Cryptography.SHA256Managed().ComputeHash(passwordAndSaltBytes);
return Convert.ToBase64String(hashBytes);
}
}
}
ApiController部份
預設在controller會有一支ValuesController.cs,我是直接在此api做登入和建立使用者,程式碼如下
public class ValuesController : ApiController
{
/// <summary>
/// 必須要經過token才可以訪問,並且角色必須是Admin
/// </summary>
/// <returns></returns>
[Authorize(Roles = "Admin")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
/// <summary>
/// 建立使用者
/// </summary>
/// <param name="user"></param>
public async Task Post(User user)
{
var db = new TokenDbContext();
Guid salt = Guid.NewGuid();
user.Password = PasswordHelper.SaltPassword(user.Password, salt);
user.Salt = salt;
db.User.Add(user);
await db.SaveChangesAsync();
}
}
實作token部份
建立一支Startup.cs,在asp.net5也已經預設都用owin的方式來實做了,所以這個也是必學的了,程式碼如下
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"), //這是要認證token的url
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), //到期日多久,此範例為一天
Provider = new AuthorizationServerProvider() //實作此provider
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
接著再建立AuthorizationServerProvider.cs
public class AuthorizationServerProvider: OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//支援cors
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
TokenDbContext db = new TokenDbContext();
var user = await db.User.FirstOrDefaultAsync(x => x.Name == context.UserName);
if (user == null)
{
context.SetError("invalid_grant", "登入帳號或密碼錯誤");
return;
}
//解開密碼
string deCodePassword = PasswordHelper.SaltPassword(context.Password, user.Salt);
if (user.Password == deCodePassword)
{
var ticket = new ClaimsIdentity(context.Options.AuthenticationType);
//加入已驗證的使用者名稱
ticket.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
ticket.AddClaim(new Claim(ClaimTypes.Role, user.Role.Name));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(ticket);
}
}
}
使用PostMan模擬client呼叫web api
接著一樣開起postman先建立一個使用者,可以看到就算我輸入建立一樣的密碼,也是會產生不同的密碼
接著就先測試如果我們還未登入取得token之前,登入預設的api,可見到是不可登入的
試著登入然後取得token,再登入就顯示資料了,如下圖
示例一下如果我沒有在header傳入token的話,要訪問values的get會回傳401
如果我有在header放token的話,就會成功了,得放的是"bearer token"哦
結論
我這邊先是簡單做個登入範例,重點只是在如何自訂token的機制,希望對各位有幫助,再請各位多多指教囉。