完美解決asp.net core 3.1 兩個AuthenticationScheme(cookie,jwt)共存在一個項目中,基於領域驅動設計(DDD)超輕量級快速開發架構

內容

在我的項目中有mvc controller(view 和 razor Page)同時也有webapi,那麼就需要網站同時支持2種認證方式,web頁面的需要傳統的cookie認證,webapi則需要使用jwt認證方式,兩種默認情況下不能共存,一旦開啟了jwt認證,cookie的登錄界面都無法使用,原因是jwt是驗證http head “Authorization” 這屬性.所以連login頁面都無法打開.

解決方案

實現web通過login頁面登錄,webapi 使用jwt方式獲取認證,支持refreshtoken更新過期token,本質上背後都使用cookie認證的方式,所以這樣的結果是直接導致token沒用,認證不是通過token唯一的作用就剩下refreshtoken了

通過nuget 安裝組件包

Microsoft.AspNetCore.Authentication.JwtBearer

下面是具體配置文件內容

//Jwt Authentication
      services.AddAuthentication(opts =>
      {
        //opts.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        //opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
      })
      //這裡是關鍵,添加一個Policy來根據http head屬性或是/api來確認使用cookie還是jwt chema
        .AddPolicyScheme(settings.App, "Bearer or Jwt", options =>
        {
          options.ForwardDefaultSelector = context =>
          {
            var bearerAuth = context.Request.Headers["Authorization"].FirstOrDefault()?.StartsWith("Bearer ") ?? false;
            // You could also check for the actual path here if that's your requirement:
            // eg: if (context.HttpContext.Request.Path.StartsWithSegments("/api", StringComparison.InvariantCulture))
            if (bearerAuth)
              return JwtBearerDefaults.AuthenticationScheme;
            else
              return CookieAuthenticationDefaults.AuthenticationScheme;
          };
        })
//這裏和傳統的cookie認證一致       .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
       {
         options.LoginPath = "/Identity/Account/Login";
         options.LogoutPath = "/Identity/Account/Logout";
         options.AccessDeniedPath = "/Identity/Account/AccessDenied";
         options.Cookie.Name = "CustomerPortal.Identity";
         options.SlidingExpiration = true;
         options.ExpireTimeSpan = TimeSpan.FromSeconds(10); //Account.Login overrides this default value
       })
        .AddJwtBearer(x =>
      {
        x.RequireHttpsMetadata = false;
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
          ValidateIssuerSigningKey = true,
          IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Jwt:Key"])),
          ValidateIssuer = true,
          ValidateAudience = true,
          ValidateLifetime = true,
          ValidIssuer = Configuration["Jwt:Issuer"],
          ValidAudience = Configuration["Jwt:Issuer"],
        };
      });

 //這裏需要對cookie做一個配置
      services.ConfigureApplicationCookie(options =>
      {
        // Cookie settings
        options.Cookie.Name = settings.App;
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromSeconds(10);
        options.LoginPath = "/Identity/Account/Login";
        options.LogoutPath = "/Identity/Account/Logout";
        options.Events = new CookieAuthenticationEvents()
        {
          OnRedirectToLogin = context =>
          {
           //這裏區分當訪問/api 如果cookie過期那麼 不重定向到login登錄界面
            if (context.Request.Path.Value.StartsWith("/api"))
            {
              context.Response.Clear();
              context.Response.StatusCode = 401;
              return Task.FromResult(0);
            }
            context.Response.Redirect(context.RedirectUri);
            return Task.FromResult(0);
          }
        };
        //options.AccessDeniedPath = "/Identity/Account/AccessDenied";
      });        

startup.cs

下面userscontroller 認證方式

重點:我簡化了refreshtoken的實現方式,原本規範的做法是通過第一次登錄返回一個token和一個唯一的隨機生成的refreshtoken,下次token過期后需要重新發送過期的token和唯一的refreshtoken,同時後台還要比對這個refreshtoken是否正確,也就是說,第一次生成的refreshtoken必須保存到數據庫里,這裏我省去了這個步驟,這樣做是不嚴謹的的.

[ApiController]
  [Route("api/users")]
  public class UsersEndpoint : ControllerBase
  {
    private readonly ILogger<UsersEndpoint> _logger;
    private readonly ApplicationDbContext _context;
    private readonly UserManager<ApplicationUser> _manager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly SmartSettings _settings;
    private readonly IConfiguration _config;

    public UsersEndpoint(ApplicationDbContext context,
      UserManager<ApplicationUser> manager,
      SignInManager<ApplicationUser> signInManager,
      ILogger<UsersEndpoint> logger,
      IConfiguration config,
      SmartSettings settings)
    {
      _context = context;
      _manager = manager;
      _settings = settings;
      _signInManager = signInManager;
      _logger = logger;
      _config = config;
    }
    [Route("authenticate")]
    [AllowAnonymous]
    [HttpPost]
    public async Task<IActionResult> Authenticate([FromBody] AuthenticateRequest model)
    {
      try
      {
        //Sign user in with username and password from parameters. This code assumes that the emailaddress is being used as the username. 
        var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, true, true);

        if (result.Succeeded)
        {
          //Retrieve authenticated user's details
          var user = await _manager.FindByNameAsync(model.UserName);

          //Generate unique token with user's details
          var accessToken = await GenerateJSONWebToken(user);
          var refreshToken = GenerateRefreshToken();
          //Return Ok with token string as content
          _logger.LogInformation($"{model.UserName}:JWT登錄成功");
          return Ok(new { accessToken = accessToken, refreshToken = refreshToken });
        }
        return Unauthorized();
      }
      catch (Exception e)
      {
        return StatusCode(500, e.Message);
      }
    }
    [Route("refreshtoken")]
    [AllowAnonymous]
    [HttpPost]
    public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest model)
    {
      var principal = GetPrincipalFromExpiredToken(model.AccessToken);
      var nameId = principal.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value;
      var user = await _manager.FindByNameAsync(nameId);
      await _signInManager.RefreshSignInAsync(user);

        //Retrieve authenticated user's details
        //Generate unique token with user's details
        var accessToken = await GenerateJSONWebToken(user);
        var refreshToken = GenerateRefreshToken();
        //Return Ok with token string as content
        _logger.LogInformation($"{user.UserName}:RefreshToken");
        return Ok(new { accessToken = accessToken, refreshToken = refreshToken });


    }

    private async Task<string> GenerateJSONWebToken(ApplicationUser user)
    {
      //Hash Security Key Object from the JWT Key
      var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
      var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

      //Generate list of claims with general and universally recommended claims
      var claims = new List<Claim>  {
           new Claim(ClaimTypes.NameIdentifier, user.UserName),
           new Claim(ClaimTypes.Name, user.UserName),
                new Claim(JwtRegisteredClaimNames.Sub, user.Email),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(ClaimTypes.NameIdentifier, user.Id),
                //添加自定義claim
                new Claim(ClaimTypes.GivenName, string.IsNullOrEmpty(user.GivenName) ? "" : user.GivenName),
                new Claim(ClaimTypes.Email, user.Email),
                new Claim("http://schemas.microsoft.com/identity/claims/tenantid", user.TenantId.ToString()),
                new Claim("http://schemas.microsoft.com/identity/claims/avatars", string.IsNullOrEmpty(user.Avatars) ? "" : user.Avatars),
                new Claim(ClaimTypes.MobilePhone, user.PhoneNumber)
      };
      //Retreive roles for user and add them to the claims listing
      var roles = await _manager.GetRolesAsync(user);
      claims.AddRange(roles.Select(r => new Claim(ClaimsIdentity.DefaultRoleClaimType, r)));
      //Generate final token adding Issuer and Subscriber data, claims, expriation time and Key
      var token = new JwtSecurityToken(_config["Jwt:Issuer"]
          , _config["Jwt:Issuer"],
          claims,
          null,
          expires: DateTime.Now.AddDays(30),
          signingCredentials: credentials
      );

      //Return token string
      return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public string GenerateRefreshToken()
    {
      var randomNumber = new byte[32];
      using (var rng = RandomNumberGenerator.Create())
      {
        rng.GetBytes(randomNumber);
        return Convert.ToBase64String(randomNumber);
      }
    }

    private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
    {
      var tokenValidationParameters = new TokenValidationParameters
      {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_config["Jwt:Key"])),
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidIssuer = _config["Jwt:Issuer"],
        ValidAudience = _config["Jwt:Issuer"],
      };

      var tokenHandler = new JwtSecurityTokenHandler();
      SecurityToken securityToken;
      var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
      var jwtSecurityToken = securityToken as JwtSecurityToken;
      if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
      {
        throw new SecurityTokenException("Invalid token");
      }

      return principal;
    }
....
}
}

ControllerBase

下面是測試

獲取token

 refreshtoken

 

獲取數據

 

 這裏獲取數據的時候,其實可以不用填入token,因為調用authenticate或refreshtoken是已經記錄了cookie到客戶端,所以在postman測試的時候都可以不用加token也可以訪問

 推廣一下我的開源項目

基於領域驅動設計(DDD)超輕量級快速開發架構

https://www.cnblogs.com/neozhu/p/13174234.html

源代碼

https://github.com/neozhu/smartadmin.core.urf

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

程序員如何高效學Python,如何高效用Python掙錢

    本人在1年半之前,不熟悉Python(不過有若干年Java開發基礎),由於公司要用Python,所以學習了一通。現在除了能用Python做本職工作外,還出了本Python書,《基於股票大數據分析的Python入門實戰 視頻教學版》,京東鏈接:https://item.jd.com/69241653952.html,還在某網站錄製了視頻課,後面還有其它線上線下課的機會。

    

    本人的感受是,哪怕上班用不到Python,程序員也應該學Python,因為Python能給大家帶來更多的主業副業機會,而且現在做Python的人還沒Java多。在本文里將結合本人的經驗,一方面分享下如何高效學Python,另一方面分享下用Python掙錢的經驗。

1 先說Python的尷尬地位,首先要明確掙錢方式

    尷尬體現在哪裡?一些大廠雖然有專門做Python的高薪崗位,但一般會直接找些深度學習機器學習方向的碩士博士,而且是要名校的,而小公司一般限於成本的原因,無法直接設置單做Python的崗位,而且也就是做些技術含量較低的應用, 比如做個爬蟲或者簡單調個機器學習的庫,所以一般是讓做其它語言的人順帶做掉。

    而且Python用庫和方法的形式包裝掉了一些很複雜的算法,程序員一般只需要調用方法就可以實現基本的機器學習和深度學習之類的活,而在大廠里高薪的Python崗,絕非是簡單地調用Python,而是需要深入了解算法,從而根據業務定製模型,所以一般社會上的程序員很難通過自學,達到大廠里高薪Python程序員的標準。

    總之,你在工作后通過自學Python,未必能達到大廠高薪職位的標準,因為由於你數學基礎不行,未必能深入算法,而一般公司也不會單獨開設Python崗位,所以對應地,學Python之前大家應該明確靠Python的掙錢方式。

    1 主業上,還得以Java等語言為主,但如果你能在簡歷和面試中證明自己很精通一般的Python爬蟲、數據分析和機器學習等方面的應用,絕對能幫你更好地找到工作,並且個人提升也會很快。

    2 雖然Python底層包含的深度學習等方面的算法很難,但用Python做案例並不難,大家可以通過Python做些副業的活。

2 再說Python該怎麼學,該學哪些技能?

    第一步,了解Python的基本語法,比如集合,讀寫文件,讀寫數據庫和異常處理等,如果大家有Java等語言的開發基礎,這塊很簡單,本人也就用了2個星期。但正是因為簡單,所以這些技能很不值錢,別人學起來也快。

    第二步,了解數據分析三劍客,具體來說就是Numpy, Pandas和matplotlib,用Numpy和Pandas清洗數據,用Pandas的DataFrame存儲數據,再用matplotlib繪製柱狀圖餅圖之類的圖形。

    第三步,了解爬蟲技能,這裏除了需要了解自帶的urllib庫之外,還需要了解一種框架,比如Scrapy,需要到能根據需求定製爬蟲代碼的程序。

    其實數據分析和爬蟲相關的語法技能,也不複雜,本人用1個半月也就達到能幹活的程度了,相信大家應該更快。而且,學到這種程序,應該就可以去做些案例以此掙錢了,比如寫分析xx網站的案例,錄成視頻去賣了,而且也能完成公司里大多數數據獲取和數據分析的功能需求了。

    第四步去了解機器學習庫,具體而言就去學習sklearn庫,這個庫里不僅包含了線性回歸嶺回歸和SVM分類等機器學習算法,還包含了波士頓房價、鳶尾花和手寫體識別等的數據集,而且由於已經包裝了相關算法,用sklearn庫學習機器學習的過程並不難,不需要過多的數學知識。學好這個庫,外帶結合爬蟲和數據分析的技能,就更在某個領域幹活掙錢了,比如本人在股票分析領域出了本書,並且也出了些視頻,後繼還可以繼續深入股票量化分析領域。

    第五步,可以去了解深度學習,無非是人工神經網絡,自然語言分析,圖像識別等,這方面雖然包含的數學知識更複雜,但由於也經過包裝,所以直接用接口也不難。這方面學好以後,雖然說高不成低不就,即沒法進大廠,同時小公司也用不到,但用這些知識準備些案例,出書講課錄視頻,甚至做企業培訓,還是能帶來一定的收益的。

    在學上述知識的時候,千萬不能只學語法,因為沒用,一定得結合實例,同時把這些知識變現的時候,也不能單講語法,也是要準備若干案例,比如像我這樣的股票分析,或者是scrapy+數據分析+深度學習的xx網站數據分析案例,這些技能雖然很高大上,但其實做到調用接口實踐案例的程度就能掙錢,如果再有機緣以此進入大廠,那就真的前途無量了。

3 可以先從公眾號做起

    之前講的是如何學,學什麼,這裏就開始講如何掙錢。當然最簡單的就是建個公眾號,在上面發文,吸引粉絲,這個門檻相對低。

    但注意如果僅僅發表入門級的文章,比如numpy庫怎麼用,怎麼用matplotlib庫繪製基本圖形,這絕對不夠,因為此類文章太多,哪些文章能吸引人?

    1 綜合應用類,比如scrapy+數據分析三劍客。

    2 實戰案例類,比如用scrapy爬個網站數據,然後分析。

    3 專業領域類,比如量化分析股票,分析房價等。

    4 深度學習機器學習這些領域現在還很火,這些領域如果把某個算法通俗易懂地講透也行,或者這些方面給寫案例,比如用自然語言分析技術分析某網站的評論等。

    如果能定期發表此類文章,公眾號一定能聚集到不少粉絲,同樣也可以做視頻的up主。

4 更可以出本屬於你的書

    如何讓別人認為你是python某個領域的大牛?要麼有大廠架構師職位加持,這不是每個人都能達到,或者是著名博主公眾號主,但似乎這也需要經歷來積澱,不過如果你在python數據分析和機器學習等方面出本書,那說服力自然就上來了。

    出書可以偏重案例,比如講爬蟲數據分析的書,在合法的前提下給出爬取分析若干知名網頁的案例,如果講機器學習的書,甚至可以結合sklearn庫自帶的數據集,講清楚常用算法的案例應用,如果有時間有機會,我甚至打算再出版本基於python股票量化的書。

    相對而言,寫一本講述包括語法、結合小案例講(機器學習等)庫的用法和結合綜合案例講機器學習算法和數據分析綜合應用的書,並不難。對於一個有5年開發經驗的程序員而言,從零基礎積累個半年,就完全可以達到出書的地步,如果資歷稍微弱些,只有2,3年開發經歷,估計學個1年也應該可以達到出書的地步。

    還是這句話,出書掙的錢不多,但絕對能證明你在python某個領域的能力,小到聯繫副業,大到以此找工作,一定能幫到你。具體操作的話,可以直接在清華出版社,机械工業出版社,人民郵電出版社和电子工業出版社的官網找聯繫方式,然後直接和編輯溝通,至於一些有中介性質的圖書公司,大家自己看着辦。

5 也可以做其它副業

    包括到各大視頻網站去錄製數據分析、爬蟲、機器學習和深度學習等方面的系列課,也可以找你所在城市的線下培訓班去講課,如果你有相關大公司背景,有自己的書,或者業內知名,你就可以聯繫些做企業培訓的公司。這樣做下個半年後,月入1萬應該不是問題。

    在做各種副業的時候,一般來說也是要偏重案例,比如你有若干個深度學習的案例外帶相關算法的說明,再加上些好的文案,應該很能吸引人。當然也可以直接找項目做,目前python方面比較熱門的項目可能還是用爬蟲,但這塊做的時候就要非常慎重了,不能做違法的事情,而當前用到深度學習機器學習技術的項目倒不多,可能因為這些應用更集中在大公司吧。

6 總結:下班后不能總放鬆,更得找點事干

    說實話,python方面的活,哪怕門檻最低的做公眾號,要做好也不簡單,更何況出書了。至於,自己聯繫平台出視頻,或者做線下培訓或者做項目,就不僅得靠技術,更得靠人脈了,經營這類活需要的時間更多。

    不過掙錢拿有容易的,況且,如果下班后總是看手機或者混日子,可能一天天就很快過去了,與其下班做些沒收益的消遣,還不如學些python幹些活,這樣多少好歹也有收益,或者指不定無心插柳柳成蔭,你經營python一段時間后,或者真就以此進了大廠,或者也通過各種途徑成為業內知名人事,拓展了不少副業渠道,也算是不負好時光吧。

    感謝大家看完此文,如果感覺有一定道理,請點贊此文。如果要轉載,也請全文轉載,別刪節本人辛苦寫成的文章。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!