[web Api]實做token without identity

[web Api]實做token without identity

前言

 

以前我們在用不管是web form或mvc的時候,我們大部份會使用session的方式來實做登入,但是到了web api之後,則建議使用標準的token方式來做身份驗證,而前陣子跟朋友聊天,發覺很多人在用web api的認證時,並不是用token的方式,總認為實做token很麻煩,之前有寫一些是使用identity和token的方式,但我相信大部份實際狀況,很多公司或個人都是使用自己的會員機制,所以想記錄一下自行實做token的方式,以下範例只是簡單範例,但請focus在實做token的部份。

 

必裝元件

 

先把owin相關的元件裝一裝,如圖示

 

image

 

會員建立

 

在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先建立一個使用者,可以看到就算我輸入建立一樣的密碼,也是會產生不同的密碼

image

 

接著就先測試如果我們還未登入取得token之前,登入預設的api,可見到是不可登入的

image

試著登入然後取得token,再登入就顯示資料了,如下圖

示例一下如果我沒有在header傳入token的話,要訪問values的get會回傳401

如果我有在header放token的話,就會成功了,得放的是"bearer token"哦

結論

我這邊先是簡單做個登入範例,重點只是在如何自訂token的機制,希望對各位有幫助,再請各位多多指教囉。