jwt net core

  • 57
  • 0
  • 2025-06-02

startup

 using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.FileProviders;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode;

namespace $NAMESPACE$
{
public class Startup
{
public IConfiguration configRoot { get; }

public Startup(IConfiguration configuration)
{
 configRoot = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
 services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(@"C:\Users\name\AppData\Local\ASP.NET\DataProtection-Keys"));

 //回應壓縮
 services.AddResponseCompression(options =>
 {
  options.EnableForHttps = true;
  options.Providers.Add<BrotliCompressionProvider>();
  options.Providers.Add<GzipCompressionProvider>();
  options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/json" });
 });

//source : https://stackoverflow.com/questions/67891755/addresponsecompression-not-working-on-net-core-3-1-controllers

 //WebApi
 services.AddControllers() 
    .AddNewtonsoftJson();//Newtonsoft.Json 支援
 services.AddControllersWithViews(); //MVC
 services.AddRazorPages() //Razor
    .AddViewComponentsAsServices(); //視圖組件注冊為服務

 //註冊 IAntiforgery 服務
 services.AddAntiforgery(o =>
 {
  o.HeaderName = "X-XSRF-TOKEN"; // 客户端要向服務端發送 Header 名稱,XSRF驗證;
  o.Cookie.SameSite = SameSiteMode.Unspecified;
  o.Cookie.HttpOnly = false;
 });

 services.AddDirectoryBrowser(); //啟用靜態文件、默認文件和目錄瀏覽的服務

 //Session
 services.AddSession(options =>
 {
  options.IdleTimeout = TimeSpan.FromMinutes(60);
  options.Cookie.HttpOnly = true;
  options.Cookie.IsEssential = true;
  options.Cookie.SecurePolicy = CookieSecurePolicy.None;  //iis 上https後,要改屬性 Always
 });

 //webApi json
 services.AddControllers().AddJsonOptions(option =>
 {
  option.JsonSerializerOptions.WriteIndented = true;
  option.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
  option.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
 });

 //跨來源資源共用
 services.AddCors(options =>
 {
  options.AddPolicy("MyAllowHeadersPolicy", policy =>
  {
   policy.AllowCredentials();
  }); ;
  options.AddDefaultPolicy(builder =>
   builder.SetIsOriginAllowed(_ => true)
    .AllowAnyMethod()
    .AllowAnyHeader()
    .AllowCredentials());
 });

 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
 .AddCookie(options =>
 {
  options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
  options.SlidingExpiration = true;
  options.LoginPath = "/Home/Index";
  options.LogoutPath = "/Home/Logout";
 });
 
 services
 .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
 .AddJwtBearer(options =>
 {
  options.IncludeErrorDetails = true; // 預設值為 true,有時會特別關閉

  options.TokenValidationParameters = new TokenValidationParameters
  {
   ValidateIssuer = true,
   ValidIssuer = configRoot.GetValue<string>("JwtSettings:Issuer"),
   ValidateAudience = true,
   ValidAudience = "audience",
   RequireExpirationTime = true, // 是不是有過期時間
   ValidateLifetime = true,
   ValidateIssuerSigningKey = false, // 如果 Token 中包含 key 才需要驗證,一般都只有簽章而已
   IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configRoot.GetValue<string>("JwtSettings:SignKey"))),
   ClockSkew = TimeSpan.Zero,
  };
 });

 //appsettings
 services.AddDbContext<TestContext>(options => options.UseSqlServer(configRoot.GetSection("TestConn").Value), ServiceLifetime.Scoped);
 //httpClient
 services.AddHttpClient();
 //ssl
 services.AddHttpClient("", m => {})
   .ConfigureHttpMessageHandlerBuilder(builder =>
   {
    builder.PrimaryHandler = new HttpClientHandler
    {
     ServerCertificateCustomValidationCallback = (m, c, ch, e) => true
    };
   });

 services.AddHttpContextAccessor(); //HttpContext
 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
 services.AddSingleton<IMemoryCache, MemoryCache>(); //MemoryCache

 services.AddOpenApiDocument(); //OpenAPI 
}

public void Configure(WebApplication app, IWebHostEnvironment env)
{
 if (env.IsDevelopment())
 {
  app.UseDeveloperExceptionPage();
 }
 else
 {
  app.UseHsts();
 }
 app.UseOpenApi(); //OpenAPI 
 app.UseSwaggerUi3(); //NSwag

 app.UseResponseCompression(); //回應壓縮中介軟體

 app.UseSession();

 app.UseHttpsRedirection(); //HTTP 重新導向至 HTTPS

 var rewrite = new RewriteOptions()
   .AddRedirect("api/TestUrlWrite/(.*)/(.*)/(.*)", "api/TestUrlWrite?p1=$1&p2=$2&p3=$3");
 app.UseRewriter(rewrite);

 var path = Path.Combine(env.ContentRootPath, "folder");
 if (!Directory.Exists(path)) Directory.CreateDirectory(path);

 app.UseStaticFiles(); //靜態檔案
 app.UseStaticFiles(new StaticFileOptions
 {
  FileProvider = new PhysicalFileProvider(path),
  RequestPath = "/folder"
 });

//source : https://ithelp.ithome.com.tw/articles/10193208

 app.UseRouting();
 app.UseCors();
 app.UseCookiePolicy();

 app.UseAuthentication(); //驗證
 app.UseAuthorization(); //授權

 app.Use((context, next) =>
 {
  context.Response.Headers.Server = "";
  context.Response.Headers.Add("X-XSS-Protection", "0"); //xss 不啟用 XSS 過濾
  context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); //xss 通知瀏覽器用header提供的 Content-Type
  context.Response.Headers.Add("X-Frame-Options", "DENY"); //xss 不讓網頁載入 frame
  context.Response.Headers.Add("Referrer-Policy", "no-referrer"); //xss 伺服器接收到的請求中就不會有來源資訊
  context.Response.Headers.Add("Content-Security-Policy", "frame-ancestors 'none'");

  var requestPath = context.Request.Path.Value;

  if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase) || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
  {
   var tokenSet = antiforgery.GetAndStoreTokens(context);
   context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!, new CookieOptions { HttpOnly = false });
  }

  return next(context);
 });

public string GenerateToken(string userName, int expireMinutes = 30)
{
var issuer = Configuration.GetValue<string>("JwtSettings:Issuer");
var signKey = Configuration.GetValue<string>("JwtSettings:SignKey");
var claims = new List<Claim>();
//claims.Add(new Claim(JwtRegisteredClaimNames.Iss, issuer));
//claims.Add(new Claim(JwtRegisteredClaimNames.Sub, sub));
//claims.Add(new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds().ToString())); // 必須為數字
//claims.Add(new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddHours(expireMinutes)).ToUnixTimeMilliseconds().ToString()));
//claims.Add(new Claim(JwtRegisteredClaimNames.Aud, "auth"));
//claims.Add(new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())); // 必須為數字
claims.Add(new Claim(JwtRegisteredClaimNames.Name, name));
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); // JWT ID

var dtNow = DateTime.Now;
var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey)), SecurityAlgorithms.HmacSha512)),
new JwtPayload(issuer: issuer, audience: userName, claims: claims, notBefore: dtNow, expires: dtNow.AddMinutes(expireMinutes))
);

return new JwtSecurityTokenHandler().WriteToken(token);
}
 //Web.config
 //<system.webServer>
 // <httpProtocol>
 //   <customHeaders>
 //    <remove name="X-Powered-By" />
 //   </customHeaders>
 //  </httpProtocol>
 // <security>
 //    <requestFiltering removeServerHeader="true" />
 // </security>
 //</system.webServer>

 var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
 app.Use(async (context, next) =>
 {
  if (context.Request.Path.StartsWithSegments("/api/csrf") || context.Request.Path.StartsWithSegments("/"))
  {
   var tokens = antiforgery.GetAndStoreTokens(context);
   context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
   context.Response.StatusCode = 200;
   await context.Response.WriteAsync("OK");
  }
  else await next.Invoke();
 });

 app.MapRazorPages(); //razor pages

 app.MapControllerRoute(
  name: "default",
  pattern: "{controller=Home}/{action=Index}/{id?}");

 app.Run();
}
}
}
 

Layout

<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/Project.styles.css" asp-append-version="true" />
<!-- bootstrap table -->
<link rel="stylesheet" href="~/lib/bootstrap-table/bootstrap-table.min.css">
<!-- font-awesome -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">

<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/bootstrap-table/bootstrap-table.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script src="~/lib/sweetalert2/sweetalert2.all.min.js"></script>
<script src="~/lib/moment.js/moment.min.js"></script>

source : https://sweetalert2.github.io/

https://ithelp.ithome.com.tw/m/articles/10318757

https://blog.darkthread.net/blog/sweetalert2/

function SweetAlert(type, text, timer) {
  Swal.fire({
      icon: type,
      text: text,
      timer: timer
  });
}

function SweetAlert(type, text, timer, fun) {
  Swal.fire({
          icon: type,
          text: text,
          timer: timer
      })
      .then(function() {
          fun();
      });
}

function SweetAlertConfirm(type, text, confirmedFun) {
  Swal.fire({
      icon: type,
      text: text,
      //showDenyButton: true,
      showCancelButton: true,
  }).then(function(result) {
      if (result.isConfirmed) {
          confirmedFun();
      }
  });
}

function SweetAlertConfirmDeny(type, title, confirmText, denyText, confirmedFun, deniedFun) {
  Swal.fire({
      icon: type,
      title: title,
      showDenyButton: true,
      showCancelButton: true,
      confirmButtonText: confirmText,
      denyButtonText: denyText
  }).then((result) => {
      if (result.isConfirmed) {
          confirmedFun();
      } else if (result.isDenied) {
          deniedFun();
      }
  });
}

source : https://momentjs.com/

https://pjchender.dev/npm/npm-moment-js/

function setDate24Format(date) {
  if (date == null) return '';

  var date = moment(date).format('YYYY/MM/DD HH:mm:ss');
  if (date == '0001/01/01 12:00:00') return '';

  return date;
}

function setDateFormat(date) {
  if (date == null) return '';

  var date = moment(date).format('YYYY/MM/DD hh:mm:ss');
  if (date == '0001/01/01 12:00:00') return '';

  return date;
}

function setDateFormatToMins(date) {
  if (date == null) return '';

  var mins = moment(date).format('YYYY/MM/DD hh:mm');
  if (mins == '0001/01/01 12:00') return '';

  return mins;
}

function setDateFormatToDay(date) {
  if (date == null) return '';

  var today = moment(date).format('YYYY/MM/DD');
  if (today == '0001/01/01') return '';

  return today;
}

function setDateFormatToDayBindDate(date) {
  if (date == null) return '';

  var today = moment(date).format('YYYY-MM-DD');
  if (today == '0001/01/01') return '';

  return today;
}

function DateIsBefore(endDate, startDate) {
  return moment(endDate).isBefore(startDate);
}

function IsData(date) {
  return moment.isDate(date);
}

function DateDiff(startdate, enddate, format) {
  var start = moment(startdate);
  var end = moment(enddate);
  return end.diff(start, format);
}

function LoadingStatus(isShow) {
  if (isShow) {
      $('#loading').modal('show');
  } else {
      $('#loading').modal('hide');
      setTimeout(function() {
              $('#loading').modal('hide');
          },
          3000);
  }
}

function CheckIdno(value) {
  if (value == null || value.length < 10) return false;

  var regex = /^[A-Z]{1}[0-9]{9}$/;
  var reg = new RegExp(regex);
  return reg.test(value);
}

<div class="container" style="margin-left: 10px !important;">
  <main role="main" class="pb-3">
      @RenderBody()
  </main>
</div>

source : https://fontawesome.com/start

<!-- The Modal Loading-->
<div class="modal fade main-content bd-example-modal-lg" tabindex="-1" id="loading">
   <div class="modal-dialog modal-sm">
       <div class="modal-content" style="width: 48px">
           <span class="fa fa-spinner fa-spin fa-3x"></span>
       </div>
   </div>
</div>

BLL

HtmlAgilityPack 1.11.71 拆解html 元素

LinqKit 1.2.5      PredicateBuilder.True

NPOI 2.6.2        Read Write Excel

EntityFrameworkCore.Design 、EntityFrameworkCore.SqlServer 、EntityFrameworkCore.Tools 7

string url = "";
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(url);
var content = new FormUrlEncodedContent(new[]{
 new KeyValuePair<string, string>("A", A),
});
var result = client.PostAsync("/ProjectName/api/Controller/Action", content).Result;
}

string url2 = "";
using (HttpClient client = new HttpClient())
{
   client.BaseAddress = new Uri(url2);
   client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
   StringContent content = new StringContent(JsonConvert.SerializeObject(new { A = a, B = b }), Encoding.UTF8, "application/json");
   var result = client.PostAsync("/ProjectName/api/Controller/Action", content).Result;