iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 24
0
Modern Web

今晚,我想來點Blazor系列 第 24

Day 24:Blazor jwt登入範例(3) -- 產生jwt token

  • 分享至 

  • xImage
  •  

jwt全名是json web token,是基於開放標準RFC 7519的機制,用來提供身分認證與訊息交換。
由於htttp的無狀態特性,server端和client是不會記得每個request是誰發過來的,也不會知道是否已驗證過身分,如果每次user發request都要到DB驗證身分,會增加server與DB的成本。因此因應這個狀況一般會用session cookie的方式解決,另外還有一種就是這篇要介紹的 jwt token,整個驗證流程如下:

  1. 使用者第一次登入,確認身分。
  2. 確認沒問題後產生jwt token,並將user的資訊,包括帳號、暱稱等等較不機敏的資訊記在jwt 中。
  3. 將jwt傳回client端,並記在storage中
  4. 之後每次的request都連同這個jwt一起發出,server端看到token就知道已通過認證,並從jwt中讀取user資訊。

關於jwt結構與運作方式,可參考jwt.io的說明

登入API

在我們前天建立的Server專案中,從nuget取得官方的Microsoft.AspNetCore.Authentication.JwtBearer擴充套件https://ithelp.ithome.com.tw/upload/images/20201008/20130058ACNDQcFTw4.jpg

修改Startup.cs的ConfigureServices方法,這邊設定要驗證系統的有效期限和符合對稱式加密的簽章:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddRazorPages();

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(option =>
            {
                option.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["jwt:Issuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["jwt:Key"])),
                    ClockSkew = TimeSpan.Zero
                };
            });
        }

Start.cs的Configure方法中,加上身分驗證授權的middleware,確保每次進來的request都會進行身分驗證

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //略...
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapControllers();
                endpoints.MapFallbackToFile("index.html");
            });
        }

建立AuthController

[Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private readonly IConfiguration configuration;

        public AuthController(IConfiguration configuration)
        {
            this.configuration = configuration;
        }

        [HttpPost("Login")]
        public async Task<ActionResult<UserToken>> Login([FromBody] UserInfo userInfo)
        {
            //驗證方式純為Demo用
            if (userInfo.Email == "abc@gmail.com" && userInfo.Password == "abc123")
            {
                return BuildToken(userInfo);
            }

            return BadRequest("Login failed");
        }

        /// <summary>
        /// 建立Token
        /// </summary>
        /// <param name="userInfo"></param>
        /// <returns></returns>
        private UserToken BuildToken(UserInfo userInfo)
        {
            //記在jwt payload中的聲明,可依專案需求自訂Claim
            var claims = new List<Claim>()
            {
                new Claim(ClaimTypes.Name, userInfo.Email),
                new Claim(ClaimTypes.Role,"admin")
            };

            //取得對稱式加密 JWT Signature 的金鑰
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["jwt:Key"]));
            var credential = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            //設定token有效期限
            DateTime expireTime = DateTime.Now.AddMinutes(30);

            //產生token
            JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(
                issuer: null,
                audience: null,
                claims: claims,
                expires: expireTime,
                signingCredentials: credential
                );
            string jwtToken = jwtSecurityTokenHandler.WriteToken(jwtSecurityToken);

            //建立UserToken物件後回傳client
            UserToken userToken = new UserToken()
            {
                StatusCode = System.Net.HttpStatusCode.OK,
                token = jwtToken,
                ExpireTime = expireTime
            };

            return userToken;
        }
    }

現在透過/api/Auth/Login這個Api,只要登入成功就可以取得token,用postman測試看看
https://ithelp.ithome.com.tw/upload/images/20201008/20130058bFy6XonG92.jpg

在輸入帳密後,也確實取得token了。如果要將Api啟用驗證,只要掛上[Authorize]就可以了

例如我們新增一個ValueController,並加上[Authorize]:

[Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class ValueController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "aaa", "bbb" };
        }
    }

使用postman在Authorization頁籤中,TYPE選擇Bearer Token,再將剛剛server回傳的token填入,再發出request,可以看到通過驗證並且取得回傳內容。
https://ithelp.ithome.com.tw/upload/images/20201008/20130058U8FuJFs19X.jpg

順利產生完jwt token之後,明天我們來學習在blazor WebAssembly中,要怎麼使用AuthenticationStateProvider來進行jwt認證。


上一篇
Day 23:Blazor jwt登入範例(2) -- 了解實作驗證流程
下一篇
Day 25:Blazor jwt登入範例(4) -- 驗證及授權
系列文
今晚,我想來點Blazor30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言